Compare commits
5 Commits
54c66efe9b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a79017810f | |||
| da64f769da | |||
| 2ee257c630 | |||
| 44b5d814d4 | |||
| 3ee83389bd |
@@ -0,0 +1,250 @@
|
|||||||
|
# Design Spec: /build Skill — Universal Feature Implementation Command
|
||||||
|
|
||||||
|
**Date:** 2026-05-14
|
||||||
|
**Status:** Draft — Awaiting user review
|
||||||
|
**Scope:** A single Cursor slash command that orchestrates the full feature development lifecycle
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Problem Statement
|
||||||
|
|
||||||
|
Today, implementing any feature (notifications, auth, payments, etc.) requires the user to manually:
|
||||||
|
- Specify TDD in the prompt
|
||||||
|
- Remember to include integration test scenarios
|
||||||
|
- Know which external services need configuring
|
||||||
|
- Know which files to touch for their specific stack
|
||||||
|
- Invoke the right skills in the right order
|
||||||
|
|
||||||
|
This leads to inconsistent implementations, missed test scenarios, and forgotten native setup steps.
|
||||||
|
|
||||||
|
**Goal:** One command — `/build implement [anything]` — that handles everything from research through PR, following every rule, skill, hook, and architecture pattern defined in the project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Decision
|
||||||
|
|
||||||
|
**Form:** Cursor slash command skill file (`.md.tmpl` inside the generator's template system)
|
||||||
|
**Scope:** Universal + context-aware (reads `project-brief.yaml` to adapt behavior)
|
||||||
|
**Integration test gate:** Auto-generate `integration_test/` files + pause for device run
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Architecture
|
||||||
|
|
||||||
|
### 3.1 File Placement
|
||||||
|
|
||||||
|
```
|
||||||
|
flutter-cursor-templates/
|
||||||
|
generator/
|
||||||
|
templates/
|
||||||
|
skills/
|
||||||
|
build/
|
||||||
|
SKILL.md.tmpl ← The skill (single new file)
|
||||||
|
test/
|
||||||
|
golden/
|
||||||
|
bloc-clean-firebase/skills/build/SKILL.md
|
||||||
|
riverpod-ff-supabase/skills/build/SKILL.md
|
||||||
|
getx-mvc-rest/skills/build/SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The generator renders this to `.cursor/skills/build/SKILL.md` in the user's project — same pattern as every other skill.
|
||||||
|
|
||||||
|
### 3.2 Template Variables
|
||||||
|
|
||||||
|
| Placeholder | Source in project-brief.yaml |
|
||||||
|
|---|---|
|
||||||
|
| `{{PROJECT_NAME}}` | `project.name` |
|
||||||
|
| `{{ARCHITECTURE}}` | `stack.architecture` |
|
||||||
|
| `{{STATE_MANAGEMENT}}` | `stack.state_management` |
|
||||||
|
| `{{ROUTING}}` | `stack.routing` |
|
||||||
|
| `{{BACKEND}}` | `stack.backend` |
|
||||||
|
| `{{PLATFORMS_LIST}}` | `stack.platforms` |
|
||||||
|
| `{{CODEGEN_LIST}}` | `stack.codegen` |
|
||||||
|
| `{{TESTING_DEPTH}}` | `testing.depth` |
|
||||||
|
| `{{E2E_TOOL}}` | `testing.e2e_tool` |
|
||||||
|
| `{{FEATURES_LIST}}` | `features.modules` |
|
||||||
|
| `{{SPECIAL_FEATURES}}` | `features.special` |
|
||||||
|
| `{{FLAVORS_LIST}}` | `environments.flavors` |
|
||||||
|
| `{{CICD_TOOL}}` | `environments.cicd` |
|
||||||
|
| `{{PACKAGE_ID}}` | `project.package` |
|
||||||
|
|
||||||
|
Conditional blocks: `{{#if backend_firebase}}`, `{{#if platforms_ios}}`, `{{#if platforms_android}}`, `{{#if codegen_freezed}}`, `{{#if codegen_injectable}}`, `{{#if special_push_notifications}}`, `{{#if special_deep_linking}}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. The 7 Phases
|
||||||
|
|
||||||
|
### Phase 1: Context Loading
|
||||||
|
1. Read `project-brief.yaml` — extract stack, platforms, existing features
|
||||||
|
2. Parse user's request → match against FEATURE_REGISTRY keywords
|
||||||
|
3. Print context summary table (feature type, stack, platforms, rules loaded)
|
||||||
|
4. If feature already in `features.modules`, confirm extending vs. rebuilding
|
||||||
|
|
||||||
|
### Phase 2: Deep Research
|
||||||
|
1. Enumerate required packages + correct versions for current Flutter stable
|
||||||
|
2. Build complete file manifest: new files (by architecture pattern) + modified files
|
||||||
|
3. List all external service configuration required per platform
|
||||||
|
4. Print research results before touching any file
|
||||||
|
|
||||||
|
### Phase 3: TDD Implementation
|
||||||
|
**Invokes: `superpowers:test-driven-development`**
|
||||||
|
|
||||||
|
- **Red:** Write all failing unit + widget tests from FEATURE_REGISTRY scenarios. Run to confirm red. Show actual output.
|
||||||
|
- **Green:** Implement domain → data → presentation layers. Run tests to green. Show actual output.
|
||||||
|
- **Refactor:** Clean up per `flutter-core.mdc`. Run `flutter analyze`. Show output.
|
||||||
|
|
||||||
|
### Phase 4: Integration Test Generation
|
||||||
|
1. Create `integration_test/[feature_type]/` directory
|
||||||
|
2. Generate one test file per logical scenario cluster
|
||||||
|
3. Each file: standard boilerplate with `IntegrationTestWidgetsFlutterBinding` (or Patrol if `{{E2E_TOOL}} = patrol`)
|
||||||
|
4. Generate `integration_test/[feature]/README.md` — test matrix with run commands
|
||||||
|
|
||||||
|
**PAUSE GATE:** Print table of all integration test files, what they cover, whether hardware is required, and the exact `flutter test` commands. Wait for user to paste device output before Phase 6.
|
||||||
|
|
||||||
|
### Phase 5: External Setup Checklist
|
||||||
|
- Grouped by service/platform (Firebase, iOS, Android, backend)
|
||||||
|
- iOS/Android: table format — Step | Where | What | How to Verify
|
||||||
|
- Console steps: numbered checklist with exact menu paths and URLs
|
||||||
|
|
||||||
|
### Phase 6: Verification Gate
|
||||||
|
**Invokes: `superpowers:verification-before-completion`**
|
||||||
|
|
||||||
|
Required evidence (real output pasted by user):
|
||||||
|
- `flutter test test/features/[feature]/ --no-pub` → all tests pass
|
||||||
|
- `flutter analyze` → no issues
|
||||||
|
- Integration test device output from Phase 4
|
||||||
|
- `lefthook run pre-commit` → all hooks pass
|
||||||
|
|
||||||
|
If any failure: invoke `superpowers:systematic-debugging` before retrying.
|
||||||
|
|
||||||
|
### Phase 7: PR Preparation
|
||||||
|
**Invokes: `superpowers:finishing-a-development-branch`**
|
||||||
|
|
||||||
|
- Conventional commit: `feat([feature_type]): implement [feature] end-to-end`
|
||||||
|
- PR description: what built, test count, integration test matrix, external setup status
|
||||||
|
- CI/CD advice for `{{CICD_TOOL}}` (secret scoping per flavor)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. FEATURE_REGISTRY
|
||||||
|
|
||||||
|
Seven feature types with full metadata:
|
||||||
|
|
||||||
|
| Feature Type | Keywords (sample) | Packages | Integration Test Scenarios |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `notifications` | notification, push, FCM, APNs | firebase_messaging, flutter_local_notifications | foreground/background/killed receipt, tap redirect in all 3 states, payload parsing |
|
||||||
|
| `auth` | login, sign in, biometric, OAuth, JWT | firebase_auth, local_auth, flutter_secure_storage | full login flow, biometric + fallback, token refresh, cold start with session |
|
||||||
|
| `payments` | payment, Stripe, IAP, checkout | flutter_stripe, purchases_flutter | test-card checkout, 3DS, Apple/Google Pay, subscription restore |
|
||||||
|
| `deep_links` | deep link, universal link, app link | app_links, go_router | cold/background/foreground link handling, auth-guarded routes |
|
||||||
|
| `analytics` | analytics, event, tracking, screen view | firebase_analytics, mixpanel_flutter | event triggered on action, screen view on nav, opt-out disables tracking |
|
||||||
|
| `storage` | file upload, Firebase Storage, Hive, Isar | firebase_storage, isar, drift | upload + retrieve, offline cache, background upload restore |
|
||||||
|
| `camera_media` | camera, photo, QR, barcode, video | camera, image_picker, mobile_scanner | photo capture, gallery pick, QR decode, permission denied UI |
|
||||||
|
|
||||||
|
Each entry also specifies: `rules_to_load`, `files_to_touch` (new + modified), `external_setup` (per service/platform), `unit_test_scenarios`, `widget_test_scenarios`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Integration Test Template Structure
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// integration_test/[feature]/[scenario]_test.dart
|
||||||
|
// Generated by /build — {{PROJECT_NAME}}
|
||||||
|
// Run: flutter test integration_test/[feature]/[scenario]_test.dart -d <device_id>
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:{{PACKAGE_ID}}/main.dart' as app;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('[FeatureType] — [Scenario Name]', () {
|
||||||
|
setUp(() async { /* seed state / reset storage */ });
|
||||||
|
tearDown(() async { /* cleanup */ });
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'given [precondition], when [action], then [expected outcome]',
|
||||||
|
(tester) async {
|
||||||
|
app.main();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// test body
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For `{{E2E_TOOL}} = patrol`: imports swap to `package:patrol`, uses `$` Patrol selector syntax.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Skill File Sections (exact headings)
|
||||||
|
|
||||||
|
```
|
||||||
|
# Build — {{PROJECT_NAME}}
|
||||||
|
## Usage
|
||||||
|
## FEATURE_REGISTRY
|
||||||
|
## Phase 1: Context Loading
|
||||||
|
## Phase 2: Deep Research
|
||||||
|
## Phase 3: TDD Implementation
|
||||||
|
## Phase 4: Integration Test Generation
|
||||||
|
## Phase 5: External Setup Checklist
|
||||||
|
## Phase 6: Verification Gate
|
||||||
|
## Phase 7: PR Preparation
|
||||||
|
## Rules applied every phase
|
||||||
|
## Code generation notes
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Cross-Skill Orchestration
|
||||||
|
|
||||||
|
The `/build` skill orchestrates existing skills internally:
|
||||||
|
- Invokes `/scaffold-feature` patterns to create the initial file skeleton (Phase 3b)
|
||||||
|
- Borrows `/generate-tests` naming and structure conventions (Phase 3a)
|
||||||
|
- Invokes `/generate-api-client` if `api_docs.format != none` and feature needs API (Phase 2)
|
||||||
|
- References `/deploy` checklist for flavor-scoped secret handling (Phase 7)
|
||||||
|
|
||||||
|
Generated alongside `/build` (same `cursor_gen` bundle) for lifecycle support:
|
||||||
|
|
||||||
|
- **`/debug`** (`debug-issue` skill) — structured BugReport + evidence before fixes; use when Phase 6 checks fail with unclear errors or weak reproduction.
|
||||||
|
- **`/verify`** (`verify-change` skill) — pasted-evidence gate for tests/analyze/hooks without walking the full seven-phase build doc; use for small PRs or post-fix sanity checks.
|
||||||
|
- **`/explain`** (`explain-code` skill) — read-only walkthrough of code paths; use when research needs human answers about runtime or native behavior before implementation.
|
||||||
|
|
||||||
|
The generated `build` skill template also cross-links **Phase 3** (TDD loop) and **Phase 6** (verification failures) to **`/debug`** and **`/verify`** so agents escalate without re-reading the full seven-phase doc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Rules Applied Every Phase
|
||||||
|
|
||||||
|
Always active (no conditional):
|
||||||
|
- `.cursor/rules/flutter-core.mdc`
|
||||||
|
- `.cursor/rules/security-standards.mdc`
|
||||||
|
- `.cursor/rules/project-context.mdc`
|
||||||
|
- Feature-type rules from `FEATURE_REGISTRY.rules_to_load`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Enhancements / Out of Scope for V1
|
||||||
|
|
||||||
|
- Reference project URL fetching and pattern extraction (Phase 1) — included in V1
|
||||||
|
- Multi-feature requests spanning 2+ feature types — detect and prompt decomposition
|
||||||
|
- Automatic CI secret rotation advice — Phase 7, advisory only
|
||||||
|
- Web platform integration test support — flagged as TODO in generated README
|
||||||
|
- Visual companion for architecture diagrams — post-V1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Verification
|
||||||
|
|
||||||
|
End-to-end test of the skill:
|
||||||
|
|
||||||
|
1. Run `cursor_gen --wizard` on a test project → choose `bloc/clean/firebase` stack
|
||||||
|
2. Confirm `.cursor/skills/build/SKILL.md` is generated
|
||||||
|
3. In Cursor, type `/build implement notification module end-to-end`
|
||||||
|
4. Verify Phase 1 prints correct context table from project-brief.yaml
|
||||||
|
5. Verify Phase 3 tests are red before implementation, green after
|
||||||
|
6. Verify Phase 4 generates `integration_test/notifications/` with 10 test files
|
||||||
|
7. Verify Phase 5 checklist lists Firebase Console + APNs steps
|
||||||
|
8. Verify Phase 6 requires real output before proceeding
|
||||||
|
9. Run golden tests: `dart test test/generator_test.dart` — all stacks must match golden SKILL.md
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Template pin (Pillar 1) — bump when you intentionally upgrade template output
|
# Template pin (Pillar 1) — bump when you intentionally upgrade template output
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
cursor_templates_version: "1.0.1"
|
cursor_templates_version: "1.0.5"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# project — identity and rough size (affects rule tone / scaffolding hints)
|
# project — identity and rough size (affects rule tone / scaffolding hints)
|
||||||
@@ -116,6 +116,17 @@ localization:
|
|||||||
enabled: true
|
enabled: true
|
||||||
locales: ["en", "es", "fr"]
|
locales: ["en", "es", "fr"]
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Integrations & conventions (optional)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
integrations:
|
||||||
|
mcp:
|
||||||
|
enabled: false # true → .cursor/mcp.json (secrets only via env vars)
|
||||||
|
preset: auto # auto | minimal
|
||||||
|
|
||||||
|
conventions:
|
||||||
|
strict_package_imports: false
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Telemetry (Pillar 6) — local-only generation / usage log under .cursor/
|
# Telemetry (Pillar 6) — local-only generation / usage log under .cursor/
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.0.4] - 2026-05-13
|
||||||
|
### Fixed
|
||||||
|
- Global `dart pub global` installs: locate `templates/` from the package root containing `pubspec.yaml` (not under `lib/`).
|
||||||
|
|
||||||
|
## [1.0.3] - 2026-05-13
|
||||||
|
### Fixed
|
||||||
|
- Template directory resolution for `dart pub global` snapshot installs; fail fast when the bundle is missing instead of emitting placeholders.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Generated repo-root `AGENTS.md` and `lefthook.yaml` (from `templates/root/`).
|
||||||
|
|
||||||
|
## [1.0.2] - 2026-05-13
|
||||||
|
### Changed
|
||||||
|
- Interactive wizard collects the remaining `project-brief.yaml` fields (design, API docs, features, conditional E2E tool and locales) and pins `cursor_templates_version` from a single `kCursorTemplatesVersion` constant.
|
||||||
|
|
||||||
## [1.0.1] - 2026-05-13
|
## [1.0.1] - 2026-05-13
|
||||||
### Fixed
|
### Fixed
|
||||||
- Resolve bundled templates for local, Git, and hosted/global installs so generated files contain real content instead of `Template not found` placeholders.
|
- Resolve bundled templates for local, Git, and hosted/global installs so generated files contain real content instead of `Template not found` placeholders.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.0.1
|
1.0.4
|
||||||
|
|||||||
@@ -309,8 +309,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generator": "pub",
|
"generator": "pub",
|
||||||
"generatorVersion": "3.8.1",
|
"generatorVersion": "3.11.3",
|
||||||
"flutterRoot": "file:///Users/maheshlalwani.devgmail.com/Documents/flutter",
|
"flutterRoot": "file:///Users/maheshlalwani.devgmail.com/Documents/flutter",
|
||||||
"flutterVersion": "3.32.5",
|
"flutterVersion": "3.41.5",
|
||||||
"pubCache": "file:///Users/maheshlalwani.devgmail.com/.pub-cache"
|
"pubCache": "file:///Users/maheshlalwani.devgmail.com/.pub-cache"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "cursor_gen",
|
"name": "cursor_gen",
|
||||||
"version": "1.0.1",
|
"version": "1.0.5",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"ansi_styles",
|
"ansi_styles",
|
||||||
"args",
|
"args",
|
||||||
@@ -25,6 +25,37 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"dependencies": []
|
"dependencies": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"version": "1.31.0",
|
||||||
|
"dependencies": [
|
||||||
|
"analyzer",
|
||||||
|
"async",
|
||||||
|
"boolean_selector",
|
||||||
|
"collection",
|
||||||
|
"coverage",
|
||||||
|
"http_multi_server",
|
||||||
|
"io",
|
||||||
|
"matcher",
|
||||||
|
"node_preamble",
|
||||||
|
"package_config",
|
||||||
|
"path",
|
||||||
|
"pool",
|
||||||
|
"shelf",
|
||||||
|
"shelf_packages_handler",
|
||||||
|
"shelf_static",
|
||||||
|
"shelf_web_socket",
|
||||||
|
"source_span",
|
||||||
|
"stack_trace",
|
||||||
|
"stream_channel",
|
||||||
|
"test_api",
|
||||||
|
"test_core",
|
||||||
|
"typed_data",
|
||||||
|
"web_socket_channel",
|
||||||
|
"webkit_inspection_protocol",
|
||||||
|
"yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ansi_styles",
|
"name": "ansi_styles",
|
||||||
"version": "0.3.2+1",
|
"version": "0.3.2+1",
|
||||||
@@ -71,93 +102,6 @@
|
|||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"dependencies": []
|
"dependencies": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "typed_data",
|
|
||||||
"version": "1.4.0",
|
|
||||||
"dependencies": [
|
|
||||||
"collection"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "web",
|
|
||||||
"version": "1.1.1",
|
|
||||||
"dependencies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "meta",
|
|
||||||
"version": "1.18.2",
|
|
||||||
"dependencies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "http_parser",
|
|
||||||
"version": "4.1.2",
|
|
||||||
"dependencies": [
|
|
||||||
"collection",
|
|
||||||
"source_span",
|
|
||||||
"string_scanner",
|
|
||||||
"typed_data"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "async",
|
|
||||||
"version": "2.13.1",
|
|
||||||
"dependencies": [
|
|
||||||
"collection",
|
|
||||||
"meta"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "string_scanner",
|
|
||||||
"version": "1.4.1",
|
|
||||||
"dependencies": [
|
|
||||||
"source_span"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "source_span",
|
|
||||||
"version": "1.10.2",
|
|
||||||
"dependencies": [
|
|
||||||
"collection",
|
|
||||||
"path",
|
|
||||||
"term_glyph"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "term_glyph",
|
|
||||||
"version": "1.2.2",
|
|
||||||
"dependencies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "test",
|
|
||||||
"version": "1.31.0",
|
|
||||||
"dependencies": [
|
|
||||||
"analyzer",
|
|
||||||
"async",
|
|
||||||
"boolean_selector",
|
|
||||||
"collection",
|
|
||||||
"coverage",
|
|
||||||
"http_multi_server",
|
|
||||||
"io",
|
|
||||||
"matcher",
|
|
||||||
"node_preamble",
|
|
||||||
"package_config",
|
|
||||||
"path",
|
|
||||||
"pool",
|
|
||||||
"shelf",
|
|
||||||
"shelf_packages_handler",
|
|
||||||
"shelf_static",
|
|
||||||
"shelf_web_socket",
|
|
||||||
"source_span",
|
|
||||||
"stack_trace",
|
|
||||||
"stream_channel",
|
|
||||||
"test_api",
|
|
||||||
"test_core",
|
|
||||||
"typed_data",
|
|
||||||
"web_socket_channel",
|
|
||||||
"webkit_inspection_protocol",
|
|
||||||
"yaml"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "webkit_inspection_protocol",
|
"name": "webkit_inspection_protocol",
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
@@ -176,6 +120,13 @@
|
|||||||
"web_socket"
|
"web_socket"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "typed_data",
|
||||||
|
"version": "1.4.0",
|
||||||
|
"dependencies": [
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "test_core",
|
"name": "test_core",
|
||||||
"version": "0.6.17",
|
"version": "0.6.17",
|
||||||
@@ -232,6 +183,15 @@
|
|||||||
"path"
|
"path"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "source_span",
|
||||||
|
"version": "1.10.2",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"path",
|
||||||
|
"term_glyph"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "shelf_web_socket",
|
"name": "shelf_web_socket",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@@ -293,6 +253,17 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"dependencies": []
|
"dependencies": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "matcher",
|
||||||
|
"version": "0.12.19",
|
||||||
|
"dependencies": [
|
||||||
|
"async",
|
||||||
|
"meta",
|
||||||
|
"stack_trace",
|
||||||
|
"term_glyph",
|
||||||
|
"test_api"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "io",
|
"name": "io",
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
@@ -334,6 +305,59 @@
|
|||||||
"string_scanner"
|
"string_scanner"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "async",
|
||||||
|
"version": "2.13.1",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "analyzer",
|
||||||
|
"version": "8.1.1",
|
||||||
|
"dependencies": [
|
||||||
|
"_fe_analyzer_shared",
|
||||||
|
"collection",
|
||||||
|
"convert",
|
||||||
|
"crypto",
|
||||||
|
"glob",
|
||||||
|
"meta",
|
||||||
|
"package_config",
|
||||||
|
"path",
|
||||||
|
"pub_semver",
|
||||||
|
"source_span",
|
||||||
|
"watcher",
|
||||||
|
"yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"version": "1.18.2",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "http_parser",
|
||||||
|
"version": "4.1.2",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"source_span",
|
||||||
|
"string_scanner",
|
||||||
|
"typed_data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string_scanner",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"dependencies": [
|
||||||
|
"source_span"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "logging",
|
"name": "logging",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
@@ -386,6 +410,11 @@
|
|||||||
"path"
|
"path"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "term_glyph",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "mime",
|
"name": "mime",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -406,43 +435,6 @@
|
|||||||
"yaml"
|
"yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "file",
|
|
||||||
"version": "7.0.1",
|
|
||||||
"dependencies": [
|
|
||||||
"meta",
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "matcher",
|
|
||||||
"version": "0.12.19",
|
|
||||||
"dependencies": [
|
|
||||||
"async",
|
|
||||||
"meta",
|
|
||||||
"stack_trace",
|
|
||||||
"term_glyph",
|
|
||||||
"test_api"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "analyzer",
|
|
||||||
"version": "8.1.1",
|
|
||||||
"dependencies": [
|
|
||||||
"_fe_analyzer_shared",
|
|
||||||
"collection",
|
|
||||||
"convert",
|
|
||||||
"crypto",
|
|
||||||
"glob",
|
|
||||||
"meta",
|
|
||||||
"package_config",
|
|
||||||
"path",
|
|
||||||
"pub_semver",
|
|
||||||
"source_span",
|
|
||||||
"watcher",
|
|
||||||
"yaml"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "watcher",
|
"name": "watcher",
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
@@ -464,6 +456,14 @@
|
|||||||
"dependencies": [
|
"dependencies": [
|
||||||
"meta"
|
"meta"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "file",
|
||||||
|
"version": "7.0.1",
|
||||||
|
"dependencies": [
|
||||||
|
"meta",
|
||||||
|
"path"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configVersion": 1
|
"configVersion": 1
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -1,5 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.0.5
|
||||||
|
|
||||||
|
- Generate `.cursor/mcp.json` from the project brief (MCP presets, backend-specific servers, env placeholders only — no secrets in the repo).
|
||||||
|
- Brief schema, validation, and resolver updates aligned with the current template set.
|
||||||
|
- Refreshed bundled templates (rules, agents, hooks, skills, commands, onboarding, root tooling).
|
||||||
|
|
||||||
|
## 1.0.4
|
||||||
|
|
||||||
|
- Fix package-root detection for template resolution: walk up from the resolved `package:cursor_gen/...` file until `pubspec.yaml` is found (global installs were incorrectly using `lib/templates`).
|
||||||
|
|
||||||
|
## 1.0.3
|
||||||
|
|
||||||
|
- Resolve template bundle by sentinel file (`rules/universal/flutter-core.mdc.tmpl`); prefer package `templates/` first for global installs; exit with a clear error instead of writing placeholder-only output.
|
||||||
|
- Generate repo-root `AGENTS.md` and `lefthook.yaml` from `templates/root/`.
|
||||||
|
- Golden tests now `await` async golden comparison so repo-root goldens are written reliably.
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
- Wizard aligns with full brief schema; shared `kCursorTemplatesVersion` for lock file and generated brief.
|
||||||
|
|
||||||
## 1.0.1
|
## 1.0.1
|
||||||
|
|
||||||
- Fix default template resolution so generated projects use bundled templates instead of writing `Template not found` placeholders.
|
- Fix default template resolution so generated projects use bundled templates instead of writing `Template not found` placeholders.
|
||||||
|
|||||||
@@ -130,8 +130,7 @@ Future<void> main(List<String> arguments) async {
|
|||||||
final templateFiles = Resolver.resolve(brief);
|
final templateFiles = Resolver.resolve(brief);
|
||||||
Logger.info('Resolved ${templateFiles.length} template files');
|
Logger.info('Resolved ${templateFiles.length} template files');
|
||||||
|
|
||||||
final templateDir =
|
final templateDir = await _resolveTemplateDir(templateSrc);
|
||||||
templateSrc.isEmpty ? await _defaultTemplateDir() : templateSrc;
|
|
||||||
final rendered = await Renderer.render(
|
final rendered = await Renderer.render(
|
||||||
brief: brief,
|
brief: brief,
|
||||||
templateFiles: templateFiles,
|
templateFiles: templateFiles,
|
||||||
@@ -147,7 +146,12 @@ Future<void> main(List<String> arguments) async {
|
|||||||
outputDir: outputDir,
|
outputDir: outputDir,
|
||||||
templateFiles: templateFiles);
|
templateFiles: templateFiles);
|
||||||
|
|
||||||
Logger.success('\n✔ Done! ${rendered.length} files written to $outputDir/');
|
final underCursor =
|
||||||
|
rendered.keys.where((k) => !k.startsWith('__root__/')).length;
|
||||||
|
final atRoot = rendered.length - underCursor;
|
||||||
|
Logger.success(
|
||||||
|
'\n✔ Done! $underCursor file(s) under $outputDir/'
|
||||||
|
'${atRoot > 0 ? ", $atRoot at project root" : ""}.');
|
||||||
if (overrideSnapshot.customFiles.isNotEmpty) {
|
if (overrideSnapshot.customFiles.isNotEmpty) {
|
||||||
Logger.success(
|
Logger.success(
|
||||||
' ↳ ${overrideSnapshot.customFiles.length} custom override(s) preserved untouched');
|
' ↳ ${overrideSnapshot.customFiles.length} custom override(s) preserved untouched');
|
||||||
@@ -162,12 +166,11 @@ Future<void> _runDiff(
|
|||||||
final rendered = await Renderer.render(
|
final rendered = await Renderer.render(
|
||||||
brief: brief,
|
brief: brief,
|
||||||
templateFiles: templateFiles,
|
templateFiles: templateFiles,
|
||||||
templateSrc:
|
templateSrc: await _resolveTemplateDir(templateSrc),
|
||||||
templateSrc.isEmpty ? await _defaultTemplateDir() : templateSrc,
|
|
||||||
);
|
);
|
||||||
Logger.info('Diff preview (no files written):\n');
|
Logger.info('Diff preview (no files written):\n');
|
||||||
for (final entry in rendered.entries) {
|
for (final entry in rendered.entries) {
|
||||||
final existing = File('$outputDir/${entry.key}');
|
final existing = File(_resolvedOutputPath(outputDir, entry.key));
|
||||||
if (!existing.existsSync()) {
|
if (!existing.existsSync()) {
|
||||||
Logger.success(' + ${entry.key}');
|
Logger.success(' + ${entry.key}');
|
||||||
} else {
|
} else {
|
||||||
@@ -187,7 +190,7 @@ Future<void> _writeOutput(
|
|||||||
bool isRefresh,
|
bool isRefresh,
|
||||||
) async {
|
) async {
|
||||||
for (final entry in rendered.entries) {
|
for (final entry in rendered.entries) {
|
||||||
final file = File('$outputDir/${entry.key}');
|
final file = File(_resolvedOutputPath(outputDir, entry.key));
|
||||||
await file.parent.create(recursive: true);
|
await file.parent.create(recursive: true);
|
||||||
if (isRefresh && file.existsSync()) {
|
if (isRefresh && file.existsSync()) {
|
||||||
final merged = OverrideManager.mergeCustomSections(
|
final merged = OverrideManager.mergeCustomSections(
|
||||||
@@ -210,27 +213,71 @@ Future<void> _writeMetadataJson(String outputDir, ProjectBrief brief) async {
|
|||||||
await file.writeAsString(encoder.convert(brief.toMetadataMap()));
|
await file.writeAsString(encoder.convert(brief.toMetadataMap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _defaultTemplateDir() async {
|
const _templateSentinel = 'rules/universal/flutter-core.mdc.tmpl';
|
||||||
|
|
||||||
|
Future<String> _resolveTemplateDir(String override) async {
|
||||||
|
if (override.isNotEmpty) {
|
||||||
|
final dir = p.normalize(override);
|
||||||
|
_assertTemplateBundle(dir);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
final tried = <String>[];
|
||||||
|
for (final dir in await _templateDirCandidates()) {
|
||||||
|
tried.add(dir);
|
||||||
|
if (File(p.join(dir, _templateSentinel)).existsSync()) return dir;
|
||||||
|
}
|
||||||
|
Logger.error('cursor_gen: No template bundle found (missing $_templateSentinel). Tried:');
|
||||||
|
for (final t in tried) Logger.error(' • $t');
|
||||||
|
Logger.error(
|
||||||
|
'Use: cursor_gen --templates /path/to/cursor_gen/templates (folder containing rules/, hooks/, …)');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _assertTemplateBundle(String dir) {
|
||||||
|
if (!File(p.join(dir, _templateSentinel)).existsSync()) {
|
||||||
|
Logger.error('Invalid template directory: $dir ($_templateSentinel not found)');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> _templateDirCandidates() async {
|
||||||
final binDir = p.dirname(Platform.script.toFilePath());
|
final binDir = p.dirname(Platform.script.toFilePath());
|
||||||
final candidates = <String>[
|
final out = <String>[];
|
||||||
// Monorepo layout: flutter-cursor-templates/generator/bin -> ../templates.
|
|
||||||
p.normalize(p.join(binDir, '..', '..', 'templates')),
|
|
||||||
// Published package layout: cursor_gen/templates.
|
|
||||||
p.normalize(p.join(binDir, '..', 'templates')),
|
|
||||||
];
|
|
||||||
|
|
||||||
final packageUri = await Isolate.resolvePackageUri(
|
final packageUri = await Isolate.resolvePackageUri(
|
||||||
Uri.parse('package:cursor_gen/src/renderer.dart'),
|
Uri.parse('package:cursor_gen/src/renderer.dart'),
|
||||||
);
|
);
|
||||||
if (packageUri != null && packageUri.isScheme('file')) {
|
if (packageUri != null && packageUri.isScheme('file')) {
|
||||||
final packageRoot = p.dirname(p.dirname(packageUri.toFilePath()));
|
final packageRoot = _packageRootDir(packageUri.toFilePath());
|
||||||
candidates.add(p.normalize(p.join(packageRoot, 'templates')));
|
if (packageRoot.isNotEmpty) {
|
||||||
|
out.add(p.normalize(p.join(packageRoot, 'templates')));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Published layout: <package>/templates next to bin/
|
||||||
|
out.add(p.normalize(p.join(binDir, '..', 'templates')));
|
||||||
|
// Monorepo: flutter-cursor-templates/templates
|
||||||
|
out.add(p.normalize(p.join(binDir, '..', '..', 'templates')));
|
||||||
|
|
||||||
return candidates.firstWhere(
|
return out;
|
||||||
(path) => Directory(path).existsSync(),
|
}
|
||||||
orElse: () => candidates.last,
|
|
||||||
);
|
/// From e.g. `.../cursor_gen-1.0.3/lib/src/renderer.dart`, walk up to the directory that contains `pubspec.yaml`.
|
||||||
|
String _packageRootDir(String resolvedLibFile) {
|
||||||
|
var dir = p.dirname(resolvedLibFile);
|
||||||
|
while (true) {
|
||||||
|
if (File(p.join(dir, 'pubspec.yaml')).existsSync()) return dir;
|
||||||
|
final parent = p.dirname(dir);
|
||||||
|
if (parent == dir) return '';
|
||||||
|
dir = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _resolvedOutputPath(String outputDir, String relativeKey) {
|
||||||
|
if (relativeKey.startsWith('__root__/')) {
|
||||||
|
final name = relativeKey.substring('__root__/'.length);
|
||||||
|
return p.normalize(p.join(p.dirname(p.absolute(outputDir)), name));
|
||||||
|
}
|
||||||
|
return p.normalize(p.join(p.absolute(outputDir), relativeKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _printBanner() {
|
void _printBanner() {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"cursor_templates_version": {
|
"cursor_templates_version": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Pillar 1: Pin to template version for reproducibility",
|
"description": "Pillar 1: Pin to template version for reproducibility",
|
||||||
"examples": ["1.0.1"]
|
"examples": ["1.0.5"]
|
||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -162,6 +162,38 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Pillar 6: Opt-in local telemetry for rule trigger analytics",
|
"description": "Pillar 6: Opt-in local telemetry for rule trigger analytics",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"integrations": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Optional third-party integrations for generated Cursor config",
|
||||||
|
"properties": {
|
||||||
|
"mcp": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "When true, emit .cursor/mcp.json with env-placeholder server stubs only"
|
||||||
|
},
|
||||||
|
"preset": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["auto", "minimal"],
|
||||||
|
"default": "auto",
|
||||||
|
"description": "minimal = empty mcpServers; auto = brief-derived stubs (still no committed secrets)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"conventions": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"strict_package_imports": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "When true, flutter-core rule enforces package: imports across feature boundaries"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: cursor_gen
|
name: cursor_gen
|
||||||
description: A CLI tool that generates project-specific Cursor AI configurations for Flutter projects.
|
description: A CLI tool that generates project-specific Cursor AI configurations for Flutter projects.
|
||||||
version: 1.0.1
|
version: 1.0.5
|
||||||
homepage: https://github.com/company/flutter-cursor-templates
|
homepage: https://github.com/company/flutter-cursor-templates
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class BriefLoader {
|
|||||||
final appCtx = yaml['app_context'] as YamlMap? ?? YamlMap();
|
final appCtx = yaml['app_context'] as YamlMap? ?? YamlMap();
|
||||||
final features = yaml['features'] as YamlMap? ?? YamlMap();
|
final features = yaml['features'] as YamlMap? ?? YamlMap();
|
||||||
final l10n = yaml['localization'] as YamlMap? ?? YamlMap();
|
final l10n = yaml['localization'] as YamlMap? ?? YamlMap();
|
||||||
|
final integrations = yaml['integrations'] as YamlMap? ?? YamlMap();
|
||||||
|
final mcp = integrations['mcp'] as YamlMap? ?? YamlMap();
|
||||||
|
final conventions = yaml['conventions'] as YamlMap? ?? YamlMap();
|
||||||
|
|
||||||
// Parse backends (can be "firebase+rest" shorthand or list)
|
// Parse backends (can be "firebase+rest" shorthand or list)
|
||||||
final backendRaw = stack['backend']?.toString() ?? 'rest';
|
final backendRaw = stack['backend']?.toString() ?? 'rest';
|
||||||
@@ -75,6 +78,11 @@ class BriefLoader {
|
|||||||
}(),
|
}(),
|
||||||
rolesEnabled: appCtx['roles_enabled'] as bool? ?? false,
|
rolesEnabled: appCtx['roles_enabled'] as bool? ?? false,
|
||||||
roleNames: _toStringList(appCtx['role_names']) ?? [],
|
roleNames: _toStringList(appCtx['role_names']) ?? [],
|
||||||
|
|
||||||
|
mcpConfigEnabled: mcp['enabled'] as bool? ?? false,
|
||||||
|
mcpPreset: mcp['preset']?.toString() ?? 'auto',
|
||||||
|
strictPackageImports:
|
||||||
|
conventions['strict_package_imports'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// mcp_json.dart — Builds .cursor/mcp.json from project brief (env placeholders only).
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
class McpJsonBuilder {
|
||||||
|
/// JSON string for Cursor MCP config. Never embed secrets — use `\${VAR}` in strings.
|
||||||
|
static String build(ProjectBrief brief) {
|
||||||
|
if (brief.mcpPreset == 'minimal') {
|
||||||
|
return const JsonEncoder.withIndent(' ')
|
||||||
|
.convert(<String, dynamic>{'mcpServers': <String, dynamic>{}});
|
||||||
|
}
|
||||||
|
|
||||||
|
final servers = <String, Map<String, dynamic>>{
|
||||||
|
'filesystem': {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-filesystem', '.'],
|
||||||
|
'description': 'Read/write project files under the workspace root',
|
||||||
|
},
|
||||||
|
'flutter-docs': {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-fetch'],
|
||||||
|
'env': {'BASE_URL': 'https://api.flutter.dev/flutter'},
|
||||||
|
'description': 'Flutter API documentation lookup',
|
||||||
|
},
|
||||||
|
'dart-pub': {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-fetch'],
|
||||||
|
'env': {'BASE_URL': 'https://pub.dev/api'},
|
||||||
|
'description': 'Pub.dev package metadata and versions',
|
||||||
|
},
|
||||||
|
'github': {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-github'],
|
||||||
|
'env': {'GITHUB_TOKEN': r'${GITHUB_TOKEN}'},
|
||||||
|
'description': 'GitHub issues and PR context (requires GITHUB_TOKEN)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (brief.backends.contains('firebase')) {
|
||||||
|
servers['firebase'] = {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-firebase'],
|
||||||
|
'env': {'FIREBASE_PROJECT': r'${FIREBASE_PROJECT_ID}'},
|
||||||
|
'description':
|
||||||
|
'Firebase project context (configure FIREBASE_PROJECT_ID)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brief.designSource == 'figma_mcp') {
|
||||||
|
servers['figma'] = {
|
||||||
|
'url': 'https://mcp.figma.com/mcp',
|
||||||
|
'type': 'http',
|
||||||
|
'headers': {
|
||||||
|
'Authorization': r'Bearer ${FIGMA_ACCESS_TOKEN}',
|
||||||
|
},
|
||||||
|
'description':
|
||||||
|
'Figma MCP — set FIGMA_ACCESS_TOKEN (never commit the value)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brief.backends.contains('supabase')) {
|
||||||
|
servers['supabase'] = {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@supabase/mcp-server-supabase@latest'],
|
||||||
|
'env': {
|
||||||
|
'SUPABASE_ACCESS_TOKEN': r'${SUPABASE_ACCESS_TOKEN}',
|
||||||
|
},
|
||||||
|
'description': 'Supabase MCP — token from env only',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brief.apiDocsFormat == 'openapi' && brief.apiDocsPath.isNotEmpty) {
|
||||||
|
servers['openapi-ref'] = {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-fetch'],
|
||||||
|
'env': {
|
||||||
|
'OPENAPI_SPEC_URL': r'${OPENAPI_SPEC_URL}',
|
||||||
|
},
|
||||||
|
'description':
|
||||||
|
'Optional fetch MCP — point OPENAPI_SPEC_URL at a hosted spec or file:// URL you expose locally',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return const JsonEncoder.withIndent(' ').convert({'mcpServers': servers});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,6 +59,15 @@ class ProjectBrief {
|
|||||||
final bool rolesEnabled;
|
final bool rolesEnabled;
|
||||||
final List<String> roleNames;
|
final List<String> roleNames;
|
||||||
|
|
||||||
|
/// When true, emit `.cursor/mcp.json` with safe env-placeholder servers.
|
||||||
|
final bool mcpConfigEnabled;
|
||||||
|
|
||||||
|
/// When [mcpConfigEnabled]: `minimal` (empty `mcpServers`) or `auto` (stubs from brief).
|
||||||
|
final String mcpPreset;
|
||||||
|
|
||||||
|
/// When true, generated flutter-core rule enforces package imports (no cross-feature relatives).
|
||||||
|
final bool strictPackageImports;
|
||||||
|
|
||||||
const ProjectBrief({
|
const ProjectBrief({
|
||||||
required this.projectName,
|
required this.projectName,
|
||||||
required this.packageId,
|
required this.packageId,
|
||||||
@@ -90,6 +99,9 @@ class ProjectBrief {
|
|||||||
this.themeVariants = const ['light', 'dark'],
|
this.themeVariants = const ['light', 'dark'],
|
||||||
this.rolesEnabled = false,
|
this.rolesEnabled = false,
|
||||||
this.roleNames = const [],
|
this.roleNames = const [],
|
||||||
|
this.mcpConfigEnabled = false,
|
||||||
|
this.mcpPreset = 'auto',
|
||||||
|
this.strictPackageImports = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Local snapshot for tooling (written as `cursor-gen-metadata.json` under the output dir).
|
/// Local snapshot for tooling (written as `cursor-gen-metadata.json` under the output dir).
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'mcp_json.dart';
|
||||||
import 'models.dart';
|
import 'models.dart';
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
@@ -10,22 +11,32 @@ class Renderer {
|
|||||||
required List<String> templateFiles,
|
required List<String> templateFiles,
|
||||||
required String templateSrc,
|
required String templateSrc,
|
||||||
}) async {
|
}) async {
|
||||||
final context = _buildContext(brief);
|
final baseContext = _buildContext(brief);
|
||||||
final output = <String, String>{};
|
final output = <String, String>{};
|
||||||
|
|
||||||
for (final key in templateFiles) {
|
for (final key in templateFiles) {
|
||||||
|
if (key == 'config/mcp-json') {
|
||||||
|
output['mcp.json'] = McpJsonBuilder.build(brief);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final outPath = _outputPath(key);
|
||||||
final tmplPath = _templatePath(templateSrc, key);
|
final tmplPath = _templatePath(templateSrc, key);
|
||||||
final file = File(tmplPath);
|
final file = File(tmplPath);
|
||||||
if (!file.existsSync()) {
|
if (!file.existsSync()) {
|
||||||
// Gracefully skip missing optional templates with a placeholder
|
output[outPath] = _missingTemplatePlaceholder(key);
|
||||||
output[_outputPath(key)] = _missingTemplatePlaceholder(key);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var content = await file.readAsString();
|
var content = await file.readAsString();
|
||||||
content = _substituteAll(content, context);
|
final ctx = Map<String, String>.from(baseContext);
|
||||||
_checkUnreplacedPlaceholders(
|
if (key.startsWith('rules/features/')) {
|
||||||
content, key); // Pillar 3: validate no broken {{VAR}}
|
final slug = p.basename(key);
|
||||||
output[_outputPath(key)] = content;
|
ctx['FEATURE_MODULE'] = slug;
|
||||||
|
ctx['FEATURE_MODULE_TITLE'] = _titleCase(slug);
|
||||||
|
}
|
||||||
|
content = _substituteAll(content, ctx);
|
||||||
|
_checkUnreplacedPlaceholders(content, key);
|
||||||
|
output[outPath] = content;
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -70,10 +81,36 @@ class Renderer {
|
|||||||
'ARCH_IMPORT_RULES': _archImportRules(brief.architecture),
|
'ARCH_IMPORT_RULES': _archImportRules(brief.architecture),
|
||||||
'TEST_PATTERN': _testPattern(brief.stateManagement),
|
'TEST_PATTERN': _testPattern(brief.stateManagement),
|
||||||
'LOCALES_LIST': brief.locales.join(', '),
|
'LOCALES_LIST': brief.locales.join(', '),
|
||||||
'TEMPLATE_VERSION': '1.0.1',
|
'TEMPLATE_VERSION': '1.0.5',
|
||||||
|
'IMPORT_POLICY_BLOCK': _importPolicyBlock(brief.strictPackageImports),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String _titleCase(String slug) {
|
||||||
|
if (slug.isEmpty) return slug;
|
||||||
|
return slug
|
||||||
|
.split('_')
|
||||||
|
.where((w) => w.isNotEmpty)
|
||||||
|
.map((w) => '${w[0].toUpperCase()}${w.substring(1)}')
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _importPolicyBlock(bool strictPackage) {
|
||||||
|
if (strictPackage) {
|
||||||
|
return '''
|
||||||
|
### Imports (strict — `conventions.strict_package_imports: true`)
|
||||||
|
- Use `package:<your_pubspec_name>/...` imports everywhere in `lib/` and `test/` — **no** relative `../` across feature boundaries (the brief `project.package` id is often the app bundle id; prefer the **pubspec.yaml `name`** for Dart imports)
|
||||||
|
- Barrel files (`index.dart`) at feature roots; do not wildcard re-export third-party packages
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
return '''
|
||||||
|
### Imports (default)
|
||||||
|
- Order: `dart:` → `package:` → relative
|
||||||
|
- Relative imports are allowed **within** the same feature directory; use `package:` imports for cross-feature code
|
||||||
|
- Never import another feature's internals from outside that feature
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
static String _substituteAll(String content, Map<String, String> ctx) {
|
static String _substituteAll(String content, Map<String, String> ctx) {
|
||||||
for (final entry in ctx.entries) {
|
for (final entry in ctx.entries) {
|
||||||
content = content.replaceAll('{{${entry.key}}}', entry.value);
|
content = content.replaceAll('{{${entry.key}}}', entry.value);
|
||||||
@@ -93,6 +130,9 @@ class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String _templatePath(String templateSrc, String key) {
|
static String _templatePath(String templateSrc, String key) {
|
||||||
|
if (key.startsWith('root/')) {
|
||||||
|
return p.join(templateSrc, '$key.tmpl');
|
||||||
|
}
|
||||||
if (key.startsWith('skills/')) {
|
if (key.startsWith('skills/')) {
|
||||||
final skillName = p.basename(key);
|
final skillName = p.basename(key);
|
||||||
return p.join(templateSrc, 'skills', skillName, 'SKILL.md.tmpl');
|
return p.join(templateSrc, 'skills', skillName, 'SKILL.md.tmpl');
|
||||||
@@ -104,10 +144,32 @@ class Renderer {
|
|||||||
}
|
}
|
||||||
return p.join(templateSrc, 'hooks', '${p.basename(key)}.ts.tmpl');
|
return p.join(templateSrc, 'hooks', '${p.basename(key)}.ts.tmpl');
|
||||||
}
|
}
|
||||||
|
if (key.startsWith('commands/')) {
|
||||||
|
final name = p.basename(key);
|
||||||
|
return p.join(templateSrc, 'commands', '$name.md.tmpl');
|
||||||
|
}
|
||||||
|
if (key == 'onboarding/ONBOARDING') {
|
||||||
|
return p.join(templateSrc, 'onboarding', 'ONBOARDING.md.tmpl');
|
||||||
|
}
|
||||||
|
if (key.startsWith('telemetry/')) {
|
||||||
|
final leaf = p.basename(key);
|
||||||
|
if (leaf == 'gitignore') {
|
||||||
|
return p.join(templateSrc, 'telemetry', 'gitignore.tmpl');
|
||||||
|
}
|
||||||
|
if (leaf == 'log-sh') {
|
||||||
|
return p.join(templateSrc, 'telemetry', 'log.sh.tmpl');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key.startsWith('rules/features/')) {
|
||||||
|
return p.join(templateSrc, 'rules', 'features', '_stub.mdc.tmpl');
|
||||||
|
}
|
||||||
return p.join(templateSrc, '$key.mdc.tmpl');
|
return p.join(templateSrc, '$key.mdc.tmpl');
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _outputPath(String key) {
|
static String _outputPath(String key) {
|
||||||
|
if (key.startsWith('root/')) {
|
||||||
|
return '__root__/${key.substring(5)}';
|
||||||
|
}
|
||||||
if (key.startsWith('skills/')) {
|
if (key.startsWith('skills/')) {
|
||||||
final name = p.basename(key);
|
final name = p.basename(key);
|
||||||
return 'skills/$name/SKILL.md';
|
return 'skills/$name/SKILL.md';
|
||||||
@@ -116,6 +178,22 @@ class Renderer {
|
|||||||
if (key.endsWith('hooks-json')) return 'hooks/hooks.json';
|
if (key.endsWith('hooks-json')) return 'hooks/hooks.json';
|
||||||
return 'hooks/${p.basename(key)}.ts';
|
return 'hooks/${p.basename(key)}.ts';
|
||||||
}
|
}
|
||||||
|
if (key.startsWith('commands/')) {
|
||||||
|
final name = p.basename(key);
|
||||||
|
return 'commands/$name.md';
|
||||||
|
}
|
||||||
|
if (key == 'onboarding/ONBOARDING') {
|
||||||
|
return 'ONBOARDING.md';
|
||||||
|
}
|
||||||
|
if (key == 'telemetry/gitignore') {
|
||||||
|
return 'telemetry/.gitignore';
|
||||||
|
}
|
||||||
|
if (key == 'telemetry/log-sh') {
|
||||||
|
return 'telemetry/log.sh';
|
||||||
|
}
|
||||||
|
if (key == 'config/mcp-json') {
|
||||||
|
return 'mcp.json';
|
||||||
|
}
|
||||||
return '${key.replaceAll('rules/', 'rules/')}.mdc';
|
return '${key.replaceAll('rules/', 'rules/')}.mdc';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ class Resolver {
|
|||||||
static List<String> resolve(ProjectBrief brief) {
|
static List<String> resolve(ProjectBrief brief) {
|
||||||
final files = <String>[];
|
final files = <String>[];
|
||||||
|
|
||||||
|
// ── Meta: rule authoring (first — governs other .mdc files) ─────────
|
||||||
|
files.add('rules/universal/rule-authoring');
|
||||||
|
|
||||||
// ── Universal (every project) ──────────────────────────────────────
|
// ── Universal (every project) ──────────────────────────────────────
|
||||||
files.addAll([
|
files.addAll([
|
||||||
'rules/universal/flutter-core',
|
'rules/universal/flutter-core',
|
||||||
@@ -15,6 +18,11 @@ class Resolver {
|
|||||||
'rules/universal/project-context',
|
'rules/universal/project-context',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// ── Theming tokens (when high contrast or extra variants) ───────────
|
||||||
|
if (_emitThemingRule(brief)) {
|
||||||
|
files.add('rules/theming/theming');
|
||||||
|
}
|
||||||
|
|
||||||
// ── Security (Pillar 5: always-on, not just for large/auth projects) ──
|
// ── Security (Pillar 5: always-on, not just for large/auth projects) ──
|
||||||
files.add('rules/security/security-standards');
|
files.add('rules/security/security-standards');
|
||||||
|
|
||||||
@@ -28,11 +36,19 @@ class Resolver {
|
|||||||
files.add('rules/architecture/${brief.architecture}');
|
files.add('rules/architecture/${brief.architecture}');
|
||||||
|
|
||||||
// ── Backend — one or more ─────────────────────────────────────────
|
// ── Backend — one or more ─────────────────────────────────────────
|
||||||
for (final b in brief.backends) files.add('rules/backend/$b');
|
for (final b in brief.backends) {
|
||||||
|
files.add('rules/backend/$b');
|
||||||
|
}
|
||||||
if (brief.specialFeatures.contains('realtime')) {
|
if (brief.specialFeatures.contains('realtime')) {
|
||||||
files.add('rules/backend/realtime');
|
files.add('rules/backend/realtime');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Push / deep linking ───────────────────────────────────────────
|
||||||
|
if (brief.specialFeatures.contains('push_notifications') ||
|
||||||
|
brief.specialFeatures.contains('deep_linking')) {
|
||||||
|
files.add('rules/integrations/push-deeplink');
|
||||||
|
}
|
||||||
|
|
||||||
// ── Routing — exactly one ─────────────────────────────────────────
|
// ── Routing — exactly one ─────────────────────────────────────────
|
||||||
files.add('rules/routing/${brief.routing}');
|
files.add('rules/routing/${brief.routing}');
|
||||||
|
|
||||||
@@ -67,15 +83,32 @@ class Resolver {
|
|||||||
files.add('rules/i18n/localization');
|
files.add('rules/i18n/localization');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── CI/CD + flavours ─────────────────────────────────────────────
|
||||||
|
if (brief.cicd != 'none' || brief.flavors.length > 1) {
|
||||||
|
files.add('rules/cicd/cicd');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Feature-scoped rule stubs ─────────────────────────────────────
|
||||||
|
final featureKeys = <String>{};
|
||||||
|
for (final m in brief.featureModules) {
|
||||||
|
final k = featureRuleKey(m);
|
||||||
|
if (k != null) featureKeys.add(k);
|
||||||
|
}
|
||||||
|
files.addAll(featureKeys);
|
||||||
|
|
||||||
// ── Skills ────────────────────────────────────────────────────────
|
// ── Skills ────────────────────────────────────────────────────────
|
||||||
files.addAll([
|
files.addAll([
|
||||||
'skills/scaffold-feature',
|
'skills/scaffold-feature',
|
||||||
'skills/scaffold-screen',
|
'skills/scaffold-screen',
|
||||||
'skills/generate-tests',
|
'skills/generate-tests',
|
||||||
|
'skills/build',
|
||||||
|
'skills/debug-issue',
|
||||||
|
'skills/verify-change',
|
||||||
|
'skills/explain-code',
|
||||||
]);
|
]);
|
||||||
if (brief.apiDocsFormat != 'none') files.add('skills/generate-api-client');
|
if (brief.apiDocsFormat != 'none') files.add('skills/generate-api-client');
|
||||||
if (brief.flavors.length > 1) files.add('skills/create-flavor');
|
if (brief.flavors.length > 1) files.add('skills/create-flavor');
|
||||||
if (brief.cicd.isNotEmpty) files.add('skills/deploy');
|
if (brief.cicd != 'none') files.add('skills/deploy');
|
||||||
|
|
||||||
// ── Agents ────────────────────────────────────────────────────────
|
// ── Agents ────────────────────────────────────────────────────────
|
||||||
files.addAll([
|
files.addAll([
|
||||||
@@ -93,9 +126,51 @@ class Resolver {
|
|||||||
files.add('agents/migration-agent');
|
files.add('agents/migration-agent');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Cursor workspace extras (reference architecture) ────────────────
|
||||||
|
files.addAll([
|
||||||
|
'root/.cursorignore',
|
||||||
|
'root/tool/cursor_audit.sh',
|
||||||
|
'onboarding/ONBOARDING',
|
||||||
|
'commands/build',
|
||||||
|
'commands/debug-issue',
|
||||||
|
'commands/verify-change',
|
||||||
|
'commands/explain-code',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (brief.mcpConfigEnabled) {
|
||||||
|
files.add('config/mcp-json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brief.telemetryOptIn) {
|
||||||
|
files.addAll([
|
||||||
|
'telemetry/gitignore',
|
||||||
|
'telemetry/log-sh',
|
||||||
|
'rules/telemetry/usage-logging',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
files.addAll(['root/AGENTS.md', 'root/lefthook.yaml']);
|
||||||
|
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `lib/features/<slug>/` ↔ `rules/features/<slug>.mdc`
|
||||||
|
static String? featureRuleKey(String module) {
|
||||||
|
var s = module
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replaceAll(RegExp(r'[^a-z0-9]+'), '_');
|
||||||
|
s = s.replaceAll(RegExp(r'_+'), '_');
|
||||||
|
s = s.replaceAll(RegExp(r'(^_+|_+$)'), '');
|
||||||
|
if (s.isEmpty) return null;
|
||||||
|
return 'rules/features/$s';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _emitThemingRule(ProjectBrief brief) {
|
||||||
|
return brief.themeVariants.contains('high_contrast') ||
|
||||||
|
brief.themeVariants.length > 2;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns human-readable description of why each file was included
|
/// Returns human-readable description of why each file was included
|
||||||
static Map<String, String> resolveWithReasons(ProjectBrief brief) {
|
static Map<String, String> resolveWithReasons(ProjectBrief brief) {
|
||||||
final resolved = resolve(brief);
|
final resolved = resolve(brief);
|
||||||
@@ -107,24 +182,100 @@ class Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String _reason(String key, ProjectBrief brief) {
|
static String _reason(String key, ProjectBrief brief) {
|
||||||
if (key.contains('universal')) return 'Always included';
|
if (key == 'rules/universal/rule-authoring') {
|
||||||
if (key.contains('security')) return 'Always included — Pillar 5';
|
return 'Always included — meta rules for authoring .cursor/rules';
|
||||||
|
}
|
||||||
|
if (key.contains('universal')) return 'Always included';
|
||||||
|
if (key == 'rules/theming/theming') {
|
||||||
|
return 'Theme variants include high contrast or extended set';
|
||||||
|
}
|
||||||
|
if (key.contains('security')) {
|
||||||
|
return 'Always included — Pillar 5';
|
||||||
|
}
|
||||||
if (key.contains('error-handling')) return 'Always included';
|
if (key.contains('error-handling')) return 'Always included';
|
||||||
if (key.contains('state-management')) return 'Matches stack.state_management: ${brief.stateManagement}';
|
if (key.contains('state-management')) {
|
||||||
if (key.contains('architecture')) return 'Matches stack.architecture: ${brief.architecture}';
|
return 'Matches stack.state_management: ${brief.stateManagement}';
|
||||||
if (key.contains('routing')) return 'Matches stack.routing: ${brief.routing}';
|
}
|
||||||
if (key.contains('testing-e2e')) return 'testing.depth includes e2e';
|
if (key.contains('architecture')) {
|
||||||
if (key.contains('testing')) return 'Matches state_management testing patterns';
|
return 'Matches stack.architecture: ${brief.architecture}';
|
||||||
if (key.contains('platform')) return 'Matches stack.platforms';
|
}
|
||||||
|
if (key == 'rules/integrations/push-deeplink') {
|
||||||
|
return 'features.special includes push_notifications or deep_linking';
|
||||||
|
}
|
||||||
|
if (key.contains('routing')) {
|
||||||
|
return 'Matches stack.routing: ${brief.routing}';
|
||||||
|
}
|
||||||
|
if (key.contains('testing-e2e')) {
|
||||||
|
return 'testing.depth includes e2e';
|
||||||
|
}
|
||||||
|
if (key.contains('testing')) {
|
||||||
|
return 'Matches state_management testing patterns';
|
||||||
|
}
|
||||||
|
if (key.contains('platform')) {
|
||||||
|
return 'Matches stack.platforms';
|
||||||
|
}
|
||||||
if (key.startsWith('hooks/')) {
|
if (key.startsWith('hooks/')) {
|
||||||
return 'stack.codegen non-empty — Cursor hooks for analyze, boundaries, and tests';
|
return 'stack.codegen non-empty — Cursor hooks for analyze, boundaries, and tests';
|
||||||
}
|
}
|
||||||
if (key.contains('codegen')) return 'Matches stack.codegen';
|
if (key.contains('codegen')) {
|
||||||
if (key.contains('i18n')) return 'localization.enabled: true';
|
return 'Matches stack.codegen';
|
||||||
if (key.contains('migration')) return 'state_management is GetX — migration guidance included';
|
}
|
||||||
if (key.contains('security-agent')) return 'scale: ${brief.scale} or auth is configured';
|
if (key.contains('i18n')) {
|
||||||
if (key.contains('api-client')) return 'api_docs.format is set';
|
return 'localization.enabled: true';
|
||||||
if (key.contains('realtime')) return 'features.special contains realtime';
|
}
|
||||||
|
if (key == 'rules/cicd/cicd') {
|
||||||
|
return 'environments.cicd is set or multiple flavors';
|
||||||
|
}
|
||||||
|
if (key.startsWith('rules/features/')) {
|
||||||
|
return 'Listed under features.modules in project-brief.yaml';
|
||||||
|
}
|
||||||
|
if (key.contains('migration')) {
|
||||||
|
return 'state_management is GetX — migration guidance included';
|
||||||
|
}
|
||||||
|
if (key.contains('security-agent')) {
|
||||||
|
return 'scale: ${brief.scale} or auth is configured';
|
||||||
|
}
|
||||||
|
if (key == 'skills/build') {
|
||||||
|
return 'Always included — universal TDD-first feature implementation command';
|
||||||
|
}
|
||||||
|
if (key == 'skills/debug-issue') {
|
||||||
|
return 'Always included — structured bug triage and evidence-first debugging';
|
||||||
|
}
|
||||||
|
if (key == 'skills/verify-change') {
|
||||||
|
return 'Always included — pre-PR verification checklist without full /build lifecycle';
|
||||||
|
}
|
||||||
|
if (key == 'skills/explain-code') {
|
||||||
|
return 'Always included — explain-only walkthrough of code paths and stack behavior';
|
||||||
|
}
|
||||||
|
if (key.contains('api-client')) {
|
||||||
|
return 'api_docs.format is set';
|
||||||
|
}
|
||||||
|
if (key.contains('realtime')) {
|
||||||
|
return 'features.special contains realtime';
|
||||||
|
}
|
||||||
|
if (key == 'root/.cursorignore') {
|
||||||
|
return 'Root ignore patterns for Cursor indexing';
|
||||||
|
}
|
||||||
|
if (key == 'root/tool/cursor_audit.sh') {
|
||||||
|
return 'Maintenance script for rule drift checks';
|
||||||
|
}
|
||||||
|
if (key == 'onboarding/ONBOARDING') {
|
||||||
|
return 'Team onboarding for Cursor layout and slash skills';
|
||||||
|
}
|
||||||
|
if (key.startsWith('commands/')) {
|
||||||
|
return 'Project slash command → skill mapping';
|
||||||
|
}
|
||||||
|
if (key == 'config/mcp-json') {
|
||||||
|
return 'integrations.mcp.enabled: true in project-brief.yaml';
|
||||||
|
}
|
||||||
|
if (key == 'telemetry/gitignore' ||
|
||||||
|
key == 'telemetry/log-sh' ||
|
||||||
|
key == 'rules/telemetry/usage-logging') {
|
||||||
|
return 'telemetry_opt_in: true — local usage logging helpers';
|
||||||
|
}
|
||||||
|
if (key.startsWith('root/')) {
|
||||||
|
return 'Repo-level companion files';
|
||||||
|
}
|
||||||
return 'Included';
|
return 'Included';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class Validator {
|
|||||||
};
|
};
|
||||||
static const _validApiFormats = {'openapi', 'postman', 'markdown', 'none'};
|
static const _validApiFormats = {'openapi', 'postman', 'markdown', 'none'};
|
||||||
static const _validThemeVariants = {'light', 'dark', 'high_contrast'};
|
static const _validThemeVariants = {'light', 'dark', 'high_contrast'};
|
||||||
|
static const _validMcpPresets = {'auto', 'minimal'};
|
||||||
|
|
||||||
static Future<ValidationResult> validateFile(String path) async {
|
static Future<ValidationResult> validateFile(String path) async {
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
@@ -178,6 +179,18 @@ class Validator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final integrations = yaml['integrations'] as YamlMap?;
|
||||||
|
if (integrations != null) {
|
||||||
|
final mcp = integrations['mcp'] as YamlMap?;
|
||||||
|
if (mcp != null) {
|
||||||
|
final preset = mcp['preset']?.toString();
|
||||||
|
if (preset != null && !_validMcpPresets.contains(preset)) {
|
||||||
|
warnings.add(
|
||||||
|
'integrations.mcp.preset "$preset" is not valid. Use: ${_validMcpPresets.join(", ")}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid: errors.isEmpty,
|
isValid: errors.isEmpty,
|
||||||
errors: errors,
|
errors: errors,
|
||||||
@@ -214,6 +227,10 @@ class Validator {
|
|||||||
if (brief.rolesEnabled && brief.roleNames.isEmpty) {
|
if (brief.rolesEnabled && brief.roleNames.isEmpty) {
|
||||||
warnings.add('roles_enabled is true but role_names is empty');
|
warnings.add('roles_enabled is true but role_names is empty');
|
||||||
}
|
}
|
||||||
|
if (!_validMcpPresets.contains(brief.mcpPreset)) {
|
||||||
|
warnings.add(
|
||||||
|
'integrations.mcp.preset "${brief.mcpPreset}" is not valid. Use: ${_validMcpPresets.join(", ")}');
|
||||||
|
}
|
||||||
|
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid: errors.isEmpty, errors: errors, warnings: warnings);
|
isValid: errors.isEmpty, errors: errors, warnings: warnings);
|
||||||
|
|||||||
@@ -8,17 +8,19 @@ import 'models.dart';
|
|||||||
import 'logger.dart';
|
import 'logger.dart';
|
||||||
|
|
||||||
const _lockFileName = '.cursor-gen-lock.json';
|
const _lockFileName = '.cursor-gen-lock.json';
|
||||||
const _currentVersion = '1.0.1';
|
|
||||||
|
/// Current flutter-cursor-templates bundle version (lock file, wizard, --check-updates).
|
||||||
|
const kCursorTemplatesVersion = '1.0.5';
|
||||||
|
|
||||||
class VersionManager {
|
class VersionManager {
|
||||||
/// Check if the project's locked version differs from the current template version
|
/// Check if the project's locked version differs from the current template version
|
||||||
static Future<VersionStatus> check({required ProjectBrief brief}) async {
|
static Future<VersionStatus> check({required ProjectBrief brief}) async {
|
||||||
final locked = brief.cursorTemplatesVersion ?? 'unset';
|
final locked = brief.cursorTemplatesVersion ?? 'unset';
|
||||||
final hasUpdate = locked != _currentVersion && locked != 'unset';
|
final hasUpdate = locked != kCursorTemplatesVersion && locked != 'unset';
|
||||||
return VersionStatus(
|
return VersionStatus(
|
||||||
hasUpdate: hasUpdate,
|
hasUpdate: hasUpdate,
|
||||||
currentVersion: locked,
|
currentVersion: locked,
|
||||||
latestVersion: _currentVersion,
|
latestVersion: kCursorTemplatesVersion,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ class VersionManager {
|
|||||||
}) async {
|
}) async {
|
||||||
final briefHash = await _fileHash(briefPath);
|
final briefHash = await _fileHash(briefPath);
|
||||||
final lock = {
|
final lock = {
|
||||||
'templateVersion': _currentVersion,
|
'templateVersion': kCursorTemplatesVersion,
|
||||||
'generatedAt': DateTime.now().toIso8601String(),
|
'generatedAt': DateTime.now().toIso8601String(),
|
||||||
'briefHash': briefHash,
|
'briefHash': briefHash,
|
||||||
'projectName': brief.projectName,
|
'projectName': brief.projectName,
|
||||||
@@ -72,15 +74,15 @@ class VersionManager {
|
|||||||
final lockedVersion = lock['templateVersion'] as String? ?? 'unknown';
|
final lockedVersion = lock['templateVersion'] as String? ?? 'unknown';
|
||||||
Logger.info('Template version check:');
|
Logger.info('Template version check:');
|
||||||
Logger.info(' Locked: $lockedVersion');
|
Logger.info(' Locked: $lockedVersion');
|
||||||
Logger.info(' Latest: $_currentVersion');
|
Logger.info(' Latest: $kCursorTemplatesVersion');
|
||||||
|
|
||||||
if (lockedVersion == _currentVersion) {
|
if (lockedVersion == kCursorTemplatesVersion) {
|
||||||
Logger.success(' ✔ You are on the latest template version.');
|
Logger.success(' ✔ You are on the latest template version.');
|
||||||
} else {
|
} else {
|
||||||
Logger.warn(' ⚠ Update available!');
|
Logger.warn(' ⚠ Update available!');
|
||||||
Logger.info('\nTo update:');
|
Logger.info('\nTo update:');
|
||||||
Logger.info(
|
Logger.info(
|
||||||
' 1. Update cursor_templates_version in project-brief.yaml to "$_currentVersion"');
|
' 1. Update cursor_templates_version in project-brief.yaml to "$kCursorTemplatesVersion"');
|
||||||
Logger.info(' 2. Run: cursor_gen --diff (preview changes)');
|
Logger.info(' 2. Run: cursor_gen --diff (preview changes)');
|
||||||
Logger.info(' 3. Run: cursor_gen --refresh (apply updates)');
|
Logger.info(' 3. Run: cursor_gen --refresh (apply updates)');
|
||||||
Logger.info(
|
Logger.info(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'logger.dart';
|
import 'logger.dart';
|
||||||
|
import 'version_manager.dart';
|
||||||
|
|
||||||
class Wizard {
|
class Wizard {
|
||||||
static Future<void> run({required String outputPath}) async {
|
static Future<void> run({required String outputPath}) async {
|
||||||
@@ -80,6 +81,22 @@ class Wizard {
|
|||||||
|
|
||||||
Logger.info('');
|
Logger.info('');
|
||||||
|
|
||||||
|
// Features
|
||||||
|
final modulesRaw = _ask(
|
||||||
|
'Feature modules (comma-separated, optional)', hint: '');
|
||||||
|
answers['feature_modules'] = _splitComma(modulesRaw);
|
||||||
|
answers['special_features'] = _askMultiChoice(
|
||||||
|
'Special capabilities (optional)',
|
||||||
|
[
|
||||||
|
'realtime',
|
||||||
|
'push_notifications',
|
||||||
|
'deep_linking',
|
||||||
|
'offline_first',
|
||||||
|
],
|
||||||
|
defaults: []);
|
||||||
|
|
||||||
|
Logger.info('');
|
||||||
|
|
||||||
// Environments
|
// Environments
|
||||||
final flavorsInput =
|
final flavorsInput =
|
||||||
_ask('Build flavors (comma-separated)', hint: 'dev,staging,prod');
|
_ask('Build flavors (comma-separated)', hint: 'dev,staging,prod');
|
||||||
@@ -92,10 +109,60 @@ class Wizard {
|
|||||||
answers['testing_depth'] = _askChoice(
|
answers['testing_depth'] = _askChoice(
|
||||||
'Testing depth', ['unit_widget', 'integration', 'e2e', 'full'],
|
'Testing depth', ['unit_widget', 'integration', 'e2e', 'full'],
|
||||||
defaultIdx: 0);
|
defaultIdx: 0);
|
||||||
|
final testingDepth = answers['testing_depth'] as String;
|
||||||
|
if (testingDepth == 'e2e' || testingDepth == 'full') {
|
||||||
|
answers['e2e_tool'] = _askChoice(
|
||||||
|
'E2E tool', ['patrol', 'maestro'], defaultIdx: 0);
|
||||||
|
} else {
|
||||||
|
answers['e2e_tool'] = 'patrol';
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info('');
|
||||||
|
|
||||||
|
// Design (validator-aligned sources)
|
||||||
|
answers['design_source'] = _askChoice('Design source', [
|
||||||
|
'figma_mcp',
|
||||||
|
'figma_manual',
|
||||||
|
'native_ref',
|
||||||
|
'html_ref',
|
||||||
|
'none',
|
||||||
|
], defaultIdx: 4);
|
||||||
|
final designSource = answers['design_source'] as String;
|
||||||
|
if (designSource == 'figma_mcp' || designSource == 'figma_manual') {
|
||||||
|
answers['figma_url'] =
|
||||||
|
_ask('Figma file URL', hint: 'https://www.figma.com/design/...');
|
||||||
|
} else {
|
||||||
|
answers['figma_url'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info('');
|
||||||
|
|
||||||
|
// API docs
|
||||||
|
answers['api_docs_format'] = _askChoice(
|
||||||
|
'API docs format', ['openapi', 'postman', 'markdown', 'none'],
|
||||||
|
defaultIdx: 3);
|
||||||
|
final apiFmt = answers['api_docs_format'] as String;
|
||||||
|
if (apiFmt != 'none') {
|
||||||
|
answers['api_docs_path'] =
|
||||||
|
_ask('Path to API spec (repo-relative)', hint: 'docs/api.yaml');
|
||||||
|
} else {
|
||||||
|
answers['api_docs_path'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info('');
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
final l10n = _askBool('Enable localization / i18n?', defaultYes: false);
|
final l10n = _askBool('Enable localization / i18n?', defaultYes: false);
|
||||||
answers['i18n'] = l10n;
|
answers['i18n'] = l10n;
|
||||||
|
if (l10n) {
|
||||||
|
final locRaw =
|
||||||
|
_ask('Locale codes (comma-separated)', hint: 'en');
|
||||||
|
var locales = _splitComma(locRaw);
|
||||||
|
if (locales.isEmpty) locales = ['en'];
|
||||||
|
answers['locales'] = locales;
|
||||||
|
} else {
|
||||||
|
answers['locales'] = <String>['en'];
|
||||||
|
}
|
||||||
|
|
||||||
// Telemetry opt-in (Pillar 6)
|
// Telemetry opt-in (Pillar 6)
|
||||||
final telemetry = _askBool(
|
final telemetry = _askBool(
|
||||||
@@ -183,13 +250,18 @@ class Wizard {
|
|||||||
final refLocals = List<String>.from(a['local_paths'] as List);
|
final refLocals = List<String>.from(a['local_paths'] as List);
|
||||||
final themes = List<String>.from(a['theme_variants'] as List);
|
final themes = List<String>.from(a['theme_variants'] as List);
|
||||||
final roles = List<String>.from(a['role_names'] as List);
|
final roles = List<String>.from(a['role_names'] as List);
|
||||||
|
final featureMods = List<String>.from(a['feature_modules'] as List);
|
||||||
|
final specialFeats = List<String>.from(a['special_features'] as List);
|
||||||
|
final locales = List<String>.from(a['locales'] as List);
|
||||||
|
final figmaUrl = a['figma_url'] as String;
|
||||||
|
final apiPath = a['api_docs_path'] as String;
|
||||||
return '''# project-brief.yaml — cursor_gen configuration
|
return '''# project-brief.yaml — cursor_gen configuration
|
||||||
# Generated by cursor_gen --wizard
|
# Generated by cursor_gen --wizard
|
||||||
# Run: cursor_gen to generate .cursor/
|
# Run: cursor_gen to generate .cursor/
|
||||||
# Run: cursor_gen --refresh to update after changes
|
# Run: cursor_gen --refresh to update after changes
|
||||||
|
|
||||||
# Pillar 1: Pin to template version for reproducibility
|
# Pillar 1: Pin to template version for reproducibility
|
||||||
cursor_templates_version: "1.0.1"
|
cursor_templates_version: "$kCursorTemplatesVersion"
|
||||||
|
|
||||||
project:
|
project:
|
||||||
name: "${a['name']}"
|
name: "${a['name']}"
|
||||||
@@ -216,15 +288,15 @@ environments:
|
|||||||
|
|
||||||
testing:
|
testing:
|
||||||
depth: "${a['testing_depth']}"
|
depth: "${a['testing_depth']}"
|
||||||
e2e_tool: "patrol"
|
e2e_tool: "${a['e2e_tool']}"
|
||||||
|
|
||||||
design:
|
design:
|
||||||
source: "none"
|
source: "${a['design_source']}"
|
||||||
figma_url: ""
|
figma_url: "${_yamlEsc(figmaUrl)}"
|
||||||
|
|
||||||
api_docs:
|
api_docs:
|
||||||
format: "none"
|
format: "${a['api_docs_format']}"
|
||||||
path: ""
|
path: "${_yamlEsc(apiPath)}"
|
||||||
|
|
||||||
references:
|
references:
|
||||||
repos: [${_yamlQStringList(refRepos)}]
|
repos: [${_yamlQStringList(refRepos)}]
|
||||||
@@ -236,12 +308,12 @@ app_context:
|
|||||||
role_names: [${_yamlQStringList(roles)}]
|
role_names: [${_yamlQStringList(roles)}]
|
||||||
|
|
||||||
features:
|
features:
|
||||||
modules: []
|
modules: [${_yamlQStringList(featureMods)}]
|
||||||
special: []
|
special: [${_yamlQStringList(specialFeats)}]
|
||||||
|
|
||||||
localization:
|
localization:
|
||||||
enabled: ${a['i18n']}
|
enabled: ${a['i18n']}
|
||||||
locales: ["en"]
|
locales: [${_yamlQStringList(locales)}]
|
||||||
|
|
||||||
# Pillar 6: Opt-in local telemetry (logs rule trigger frequency locally)
|
# Pillar 6: Opt-in local telemetry (logs rule trigger frequency locally)
|
||||||
telemetry_opt_in: ${a['telemetry']}
|
telemetry_opt_in: ${a['telemetry']}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
Repo-level notes for AI assistants. Authoritative stack and conventions are in `project-brief.yaml` and `.cursor/` (regenerate with `cursor_gen` from the project root).
|
||||||
|
|
||||||
|
- **Package:** `{{PACKAGE_ID}}`
|
||||||
|
- **Onboarding:** `.cursor/ONBOARDING.md` — layout, slash commands → skills, MCP opt-in, audits
|
||||||
|
- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` (see `.cursor/commands/*.md`)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# {{PROJECT_NAME}} — generated by cursor_gen; adjust commands to your repo
|
||||||
|
# Optional: run `bash tool/cursor_audit.sh` after changing features.modules or rule files.
|
||||||
|
pre-commit:
|
||||||
|
commands:
|
||||||
|
flutter-analyze:
|
||||||
|
run: flutter analyze
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# {{PROJECT_NAME}} — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for {{PROJECT_NAME}}
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **{{FLAVORS_LIST}}**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — {{E2E_TOOL}} on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **{{CICD_TOOL}}** (`{{CICD_RAW}}`)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module {{FEATURE_MODULE}} — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/{{FEATURE_MODULE}}/**", "test/**/{{FEATURE_MODULE}}/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — {{FEATURE_MODULE_TITLE}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `{{FEATURE_MODULE}}` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Localization / i18n conventions for {{PROJECT_NAME}}"
|
description: "Localization / i18n conventions for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["lib/l10n/**", "lib/**/*.dart", "test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Localization Standards — {{PROJECT_NAME}}
|
# Localization Standards — {{PROJECT_NAME}}
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: Push notifications and deep linking for {{PROJECT_NAME}}
|
||||||
|
globs: ["lib/**/*.dart", "android/**", "ios/**", "test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Push & deep linking — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Special capabilities from `project-brief.yaml`: **{{SPECIAL_FEATURES}}**. Native and server configuration must stay consistent.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- **Push:** request permissions through the platform flow; handle denial gracefully; no silent failures on token registration
|
||||||
|
- **Routing:** deep links and notification taps MUST go through **{{ROUTING}}** (no ad-hoc `Navigator` stacks for inbound links)
|
||||||
|
- **Payloads:** map FCM/APNs/Supabase payloads to domain models in the data layer — no JSON parsing scattered in widgets
|
||||||
|
- **iOS:** exercise push on a **physical device** when possible (simulator limitations)
|
||||||
|
- **Android:** declare required permissions explicitly; target API behaviour for POST_NOTIFICATIONS where applicable
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- Unit-test payload → domain mapping and routing targets
|
||||||
|
- Integration/E2E: cold start, background, and foreground tap-to-open flows when `testing.depth` allows
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
description: "Policy for optional local AI usage logs under .cursor/telemetry/"
|
||||||
|
globs: [".cursor/telemetry/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Telemetry — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
When `telemetry_opt_in: true`, this repo may record **local-only** generation or usage notes under `.cursor/telemetry/`. This is **not** production analytics.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- **Never** commit secrets, tokens, or PII into JSONL or shell history
|
||||||
|
- Prefer redacted summaries over raw prompts
|
||||||
|
- Add `.cursor/telemetry/*.jsonl` to `.gitignore` unless your team explicitly version-controls sanitized samples
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Append one JSON object per line (JSONL) with ISO timestamps and event type
|
||||||
|
- Rotate or truncate files if they grow beyond a few MB
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
description: Semantic colours and theme extensions for {{PROJECT_NAME}}
|
||||||
|
globs: ["lib/**/*.dart", "test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Theming — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Theme variants from brief: **{{THEME_SUMMARY}}**.{{HIGH_CONTRAST_NOTE}}
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Prefer **`ThemeExtension`** (or design-system equivalents) for app-specific colours and radii — avoid raw `Color(0xFF...)` in feature code except in central token definitions
|
||||||
|
- Use **`Theme.of(context).colorScheme`** / `TextTheme` for Material-aligned roles where appropriate
|
||||||
|
- **High contrast:** when supported, verify WCAG contrast for text and controls; never rely on colour alone for state (pair with icon or label){{HIGH_CONTRAST_UX_LINE}}
|
||||||
|
- Touch targets: respect platform minimums (e.g. ~48 logical pixels) for interactive widgets
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Hard-coded `Colors.*` scattered across feature widgets instead of theme tokens
|
||||||
|
- Ignoring `MediaQuery.of(context).platformBrightness` / high-contrast modes when the product claims support
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Core Flutter conventions for {{PROJECT_NAME}} — always applied"
|
description: "Core Flutter conventions for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Flutter Core Standards — {{PROJECT_NAME}}
|
# Flutter Core Standards — {{PROJECT_NAME}}
|
||||||
@@ -29,10 +30,7 @@ alwaysApply: true
|
|||||||
- Private members: `_camelCase`
|
- Private members: `_camelCase`
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
- Order: dart: → package: → relative
|
{{IMPORT_POLICY_BLOCK}}
|
||||||
- Use relative imports within a feature; absolute for cross-feature
|
|
||||||
- Never import a feature's internal files from outside that feature
|
|
||||||
|
|
||||||
## Code quality
|
## Code quality
|
||||||
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
||||||
- No `print()` in production code — use a logging package
|
- No `print()` in production code — use a logging package
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Project context for {{PROJECT_NAME}} — always applied"
|
description: "Stack summary and product context for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project Context — {{PROJECT_NAME}}
|
# Project Context — {{PROJECT_NAME}}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
description: How Cursor rules in this repository must be written and maintained.
|
||||||
|
globs: [".cursor/rules/**/*.mdc"]
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rule authoring — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Rules are version-controlled contracts for AI assistants. Poor rules waste context and silently steer every edit.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- One focused concern per file; split broad topics instead of one mega-rule
|
||||||
|
- Every rule MUST have a clear `description` in frontmatter (one sentence)
|
||||||
|
- Prefer `alwaysApply: false` with **narrow** `globs` for domain rules — reserve `alwaysApply: true` for meta and safety
|
||||||
|
- `globs` must be as specific as possible — never `["**/*"]` unless tooling requires it
|
||||||
|
- Code samples in rules MUST be valid for this project (Dart/Flutter/YAML as appropriate)
|
||||||
|
- Deprecated guidance is removed, not left commented out
|
||||||
|
- Each substantive rule includes **Context** (why), **Constraints** (must/must not), and where helpful **Patterns** / **Anti-patterns**
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Domain rules (testing, l10n, a feature) with `alwaysApply: true` — burns context
|
||||||
|
- Rules with no concrete examples when the topic is code-facing
|
||||||
|
- Stale feature rules after modules are removed — run `tool/cursor_audit.sh` periodically
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "UI/UX standards for {{PROJECT_NAME}} — always applied"
|
description: "UI/UX standards for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# UI / UX Standards — {{PROJECT_NAME}}
|
# UI / UX Standards — {{PROJECT_NAME}}
|
||||||
|
|||||||
@@ -0,0 +1,573 @@
|
|||||||
|
# Build — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
Implements any feature end-to-end: deep research → TDD → integration tests → external setup checklist → verified PR.
|
||||||
|
Stack: **{{STATE_MANAGEMENT}}** / **{{ARCHITECTURE}}** / **{{BACKEND}}** / {{PLATFORMS_LIST}}.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/build <free-text description of what to implement>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `/build implement notification module end-to-end`
|
||||||
|
- `/build add biometric auth`
|
||||||
|
- `/build integrate Stripe payments`
|
||||||
|
- `/build add deep linking for shared product pages`
|
||||||
|
- `/build implement analytics event tracking`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FEATURE_REGISTRY
|
||||||
|
|
||||||
|
The AI reads this registry to classify the request and load the right research, test scenarios, and setup steps.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
feature_registry:
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
keywords: [notification, push, fcm, apns, alert, badge, silent push,
|
||||||
|
remote notification, local notification, firebase messaging]
|
||||||
|
pub_packages: [firebase_messaging, flutter_local_notifications, firebase_core]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- parse notification payload to domain model
|
||||||
|
- FCM token generation and storage
|
||||||
|
- token refresh triggers re-registration
|
||||||
|
- notification permission denied returns graceful fallback
|
||||||
|
- notification service initialisation is idempotent
|
||||||
|
widget_test_scenarios:
|
||||||
|
- notification banner renders correct title and body
|
||||||
|
- tap on notification navigates to correct route
|
||||||
|
- badge count updates on new message
|
||||||
|
integration_test_scenarios:
|
||||||
|
- foreground notification receipt and display
|
||||||
|
- background notification receipt (app backgrounded, not killed)
|
||||||
|
- killed-state notification receipt (cold launch from notification tap)
|
||||||
|
- tap-to-open in foreground state routes correctly
|
||||||
|
- tap-to-open in background state routes correctly
|
||||||
|
- tap-to-open in killed state routes correctly
|
||||||
|
- notification payload parsing end-to-end
|
||||||
|
- deep link routing from notification data field
|
||||||
|
- multiple simultaneous notifications (ordering and dedup)
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Cloud Messaging, download google-services.json, download GoogleService-Info.plist]
|
||||||
|
ios: [Push Notifications capability, Background Modes remote notifications, APNs .p8 key upload to Firebase]
|
||||||
|
android: [POST_NOTIFICATIONS permission, RECEIVE_BOOT_COMPLETED permission, google-services plugin]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/notifications/, integration_test/notifications/]
|
||||||
|
modified: [lib/main.dart, android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, ios/Runner/AppDelegate.swift, pubspec.yaml]
|
||||||
|
|
||||||
|
auth:
|
||||||
|
keywords: [auth, authentication, login, sign in, sign out, logout,
|
||||||
|
biometric, face id, touch id, fingerprint, oauth, jwt,
|
||||||
|
session, token refresh, social login, google sign-in]
|
||||||
|
pub_packages: [firebase_auth, supabase_flutter, local_auth, flutter_secure_storage, google_sign_in]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- login success stores token securely
|
||||||
|
- login failure (wrong password) returns typed error
|
||||||
|
- login failure (network) returns typed error
|
||||||
|
- token refresh succeeds and updates stored token
|
||||||
|
- token refresh failure triggers logout
|
||||||
|
- biometric auth success grants access
|
||||||
|
- biometric auth failure falls back to password
|
||||||
|
- biometric not enrolled returns correct error state
|
||||||
|
- logout clears all stored credentials
|
||||||
|
- session persistence: token loaded on cold start
|
||||||
|
widget_test_scenarios:
|
||||||
|
- login form renders correctly
|
||||||
|
- validation errors shown inline
|
||||||
|
- loading state disables submit button
|
||||||
|
- biometric prompt shown when available
|
||||||
|
integration_test_scenarios:
|
||||||
|
- complete login flow (email and password)
|
||||||
|
- login then logout then login again
|
||||||
|
- invalid credentials shows error and stays on login
|
||||||
|
- biometric login flow on enrolled device
|
||||||
|
- biometric fallback to password
|
||||||
|
- token refresh in background while user navigates
|
||||||
|
- cold start with stored valid session skips login
|
||||||
|
- cold start with expired session redirects to login
|
||||||
|
- social auth OAuth redirect and return
|
||||||
|
- deep link into protected route redirects to login then back
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Email/Password provider, Enable Google provider, download updated config files]
|
||||||
|
ios: [NSFaceIDUsageDescription in Info.plist, LocalAuthentication.framework in Xcode]
|
||||||
|
android: [USE_BIOMETRIC permission, USE_FINGERPRINT permission, minSdkVersion 23 for biometric]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/auth/, integration_test/auth/]
|
||||||
|
modified: [lib/main.dart, lib/core/di/injection.dart, pubspec.yaml]
|
||||||
|
|
||||||
|
payments:
|
||||||
|
keywords: [payment, stripe, in-app purchase, iap, checkout, subscription,
|
||||||
|
billing, apple pay, google pay, card, transaction, refund, revenue cat]
|
||||||
|
pub_packages: [flutter_stripe, purchases_flutter, in_app_purchase]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- checkout request builds correct PaymentIntent params
|
||||||
|
- payment success updates order state
|
||||||
|
- payment failure (card declined) returns typed error
|
||||||
|
- webhook event parsed to domain model
|
||||||
|
- refund request constructs correct API call
|
||||||
|
- subscription status checked on app resume
|
||||||
|
widget_test_scenarios:
|
||||||
|
- checkout form renders with correct amount
|
||||||
|
- payment loading state shown during processing
|
||||||
|
- success confirmation screen renders
|
||||||
|
- error state with retry option
|
||||||
|
integration_test_scenarios:
|
||||||
|
- end-to-end checkout with test card (Stripe test mode)
|
||||||
|
- checkout with 3D Secure challenge
|
||||||
|
- card declined shows error then retry succeeds
|
||||||
|
- Apple Pay / Google Pay sheet appears (device capability check)
|
||||||
|
- subscription purchase and entitlement unlock
|
||||||
|
- subscription restore flow
|
||||||
|
- refund flow from order history
|
||||||
|
external_setup:
|
||||||
|
stripe: [publishable and secret keys in flavor .env files, webhook endpoint in Stripe Dashboard, Apple Pay domain registration]
|
||||||
|
ios: [In-App Purchase capability, Apple Pay capability and merchant identifier, com.apple.developer.in-app-payments entitlement]
|
||||||
|
android: [Google Pay Console setup, BILLING permission for in_app_purchase]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/payments/, integration_test/payments/]
|
||||||
|
modified: [ios/Runner/Runner.entitlements, android/app/src/main/AndroidManifest.xml, pubspec.yaml]
|
||||||
|
|
||||||
|
deep_links:
|
||||||
|
keywords: [deep link, deep linking, universal link, app link, deferred deep link,
|
||||||
|
dynamic link, branch, uri scheme, custom scheme, url scheme]
|
||||||
|
pub_packages: [go_router, app_links, uni_links]
|
||||||
|
rules_to_load: [platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- URI parsed to correct route and parameters
|
||||||
|
- unknown URI falls back to home
|
||||||
|
- authenticated-only route redirects to login when unauthenticated
|
||||||
|
- deep link preserves query parameters
|
||||||
|
widget_test_scenarios:
|
||||||
|
- navigation guard redirects unauthenticated deep link
|
||||||
|
integration_test_scenarios:
|
||||||
|
- cold start from Universal Link routes to correct screen
|
||||||
|
- cold start from custom URI scheme routes correctly
|
||||||
|
- backgrounded app receives deep link and navigates
|
||||||
|
- foreground app receives deep link and navigates
|
||||||
|
- deep link to authenticated route redirects to login then original destination
|
||||||
|
- deep link with path parameters loads correct content
|
||||||
|
- invalid or malformed deep link shows 404 screen
|
||||||
|
external_setup:
|
||||||
|
ios: [Associated Domains capability applinks:yourdomain.com, host apple-app-site-association file at /.well-known/]
|
||||||
|
android: [intent-filter with android:autoVerify=true in AndroidManifest.xml, host assetlinks.json at /.well-known/]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/core/routing/deep_link_handler.dart, integration_test/deep_links/]
|
||||||
|
modified: [lib/core/routing/router.dart, android/app/src/main/AndroidManifest.xml, ios/Runner/Runner.entitlements, ios/Runner/Info.plist]
|
||||||
|
|
||||||
|
analytics:
|
||||||
|
keywords: [analytics, tracking, event, mixpanel, amplitude, firebase analytics,
|
||||||
|
segment, posthog, screen view, funnel, cohort]
|
||||||
|
pub_packages: [firebase_analytics, mixpanel_flutter, amplitude_flutter, posthog_flutter]
|
||||||
|
rules_to_load: [security-standards.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- analytics service logs correct event name and params
|
||||||
|
- PII fields stripped before event sent
|
||||||
|
- analytics disabled in dev flavor
|
||||||
|
- screen name logged on navigation
|
||||||
|
widget_test_scenarios:
|
||||||
|
- RouteObserver triggers screen_view event
|
||||||
|
integration_test_scenarios:
|
||||||
|
- user action triggers expected event (verify via debug view)
|
||||||
|
- screen transitions log screen_view with correct names
|
||||||
|
- opt-out disables all tracking
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Analytics in Firebase Console, enable DebugView for local testing, configure conversion events]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/core/analytics/analytics_service.dart, lib/core/analytics/analytics_events.dart, integration_test/analytics/]
|
||||||
|
modified: [lib/main.dart, pubspec.yaml]
|
||||||
|
|
||||||
|
storage:
|
||||||
|
keywords: [storage, file upload, download, cloud storage, firebase storage,
|
||||||
|
supabase storage, s3, image upload, document, file picker,
|
||||||
|
offline, cache, hive, isar, objectbox, sqflite, drift]
|
||||||
|
pub_packages: [firebase_storage, supabase_flutter, hive_flutter, isar, drift, file_picker, image_picker, path_provider]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- upload returns public URL on success
|
||||||
|
- upload failure returns typed error
|
||||||
|
- local cache read before remote fetch (offline-first)
|
||||||
|
- cache invalidation on TTL expiry
|
||||||
|
- large file upload uses resumable upload
|
||||||
|
widget_test_scenarios:
|
||||||
|
- file picker button triggers picker
|
||||||
|
- upload progress indicator shown
|
||||||
|
- image preview renders after selection
|
||||||
|
integration_test_scenarios:
|
||||||
|
- end-to-end file upload and retrieval
|
||||||
|
- offline mode: local cache serves data
|
||||||
|
- background upload completes when connectivity restored
|
||||||
|
- file size limit enforced
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Firebase Storage, configure Storage security rules, set CORS policy for web if applicable]
|
||||||
|
ios: [NSPhotoLibraryUsageDescription in Info.plist, NSCameraUsageDescription in Info.plist]
|
||||||
|
android: [READ_EXTERNAL_STORAGE or READ_MEDIA_IMAGES permission]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/storage/, integration_test/storage/]
|
||||||
|
modified: [android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, pubspec.yaml]
|
||||||
|
|
||||||
|
camera_media:
|
||||||
|
keywords: [camera, photo, video, image picker, qr code, barcode scanner,
|
||||||
|
ar, augmented reality, gallery, media, record, capture]
|
||||||
|
pub_packages: [camera, image_picker, mobile_scanner, qr_flutter, image_cropper, video_player]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- QR/barcode parsed to correct domain model
|
||||||
|
- image compressed before upload
|
||||||
|
- camera permission denied returns typed error
|
||||||
|
widget_test_scenarios:
|
||||||
|
- camera preview widget renders
|
||||||
|
- capture button triggers photo take
|
||||||
|
integration_test_scenarios:
|
||||||
|
- camera opens and captures photo on real device
|
||||||
|
- gallery picker selects image and returns
|
||||||
|
- QR scan decodes valid code correctly
|
||||||
|
- permission denied shows correct error UI
|
||||||
|
external_setup:
|
||||||
|
ios: [NSCameraUsageDescription, NSPhotoLibraryUsageDescription, NSMicrophoneUsageDescription for video]
|
||||||
|
android: [CAMERA permission, READ_MEDIA_IMAGES for gallery access]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/camera/, integration_test/camera/]
|
||||||
|
modified: [android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, pubspec.yaml]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Context Loading
|
||||||
|
|
||||||
|
**When the user types `/build <request>`, do the following before writing a single line of code:**
|
||||||
|
|
||||||
|
1. Read `project-brief.yaml` from the repo root. Extract and hold in context:
|
||||||
|
- `stack.state_management` → **{{STATE_MGMT_RAW}}**
|
||||||
|
- `stack.architecture` → **{{ARCH_RAW}}**
|
||||||
|
- `stack.routing` → **{{ROUTING_RAW}}**
|
||||||
|
- `stack.backend` → **{{BACKENDS_LIST}}**
|
||||||
|
- `stack.platforms` → **{{PLATFORMS_LIST}}**
|
||||||
|
- `stack.codegen` → **{{CODEGEN_LIST}}**
|
||||||
|
- `testing.depth` → **{{TESTING_DEPTH}}**
|
||||||
|
- `testing.e2e_tool` → **{{E2E_TOOL}}**
|
||||||
|
- `features.modules` (existing features) → **{{FEATURES_LIST}}**
|
||||||
|
- `features.special` → **{{SPECIAL_FEATURES}}**
|
||||||
|
- `environments.flavors` → **{{FLAVORS_LIST}}**
|
||||||
|
- `environments.cicd` → **{{CICD_RAW}}**
|
||||||
|
|
||||||
|
2. Parse the user's free-text request. Match against `FEATURE_REGISTRY` keywords using longest-match. Multiple types are allowed if the request spans features.
|
||||||
|
|
||||||
|
3. Load `.cursor/rules/` files listed under `rules_to_load` for the matched feature type.
|
||||||
|
|
||||||
|
4. If the user's message contains a URL or GitHub repo reference, fetch and index it for pattern reference.
|
||||||
|
|
||||||
|
5. Check `features.modules` — if the feature already exists in the list, ask: "This feature is already listed in project-brief.yaml. Building additional capability on top of it? (y/n)"
|
||||||
|
|
||||||
|
6. Print the context summary table before proceeding:
|
||||||
|
|
||||||
|
```
|
||||||
|
| Field | Value |
|
||||||
|
|------------------|------------------------------|
|
||||||
|
| Feature type | [detected type] |
|
||||||
|
| State management | {{STATE_MANAGEMENT}} |
|
||||||
|
| Architecture | {{ARCHITECTURE}} |
|
||||||
|
| Backend | {{BACKEND}} |
|
||||||
|
| Platforms | {{PLATFORMS_LIST}} |
|
||||||
|
| E2E tool | {{E2E_TOOL}} |
|
||||||
|
| Rules loaded | [list from rules_to_load] |
|
||||||
|
| Existing modules | {{FEATURES_LIST}} |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Deep Research
|
||||||
|
|
||||||
|
**Before touching any file, print the full research output.**
|
||||||
|
|
||||||
|
1. From the FEATURE_REGISTRY, enumerate all `pub_packages` for the detected type. For each, determine the correct version compatible with current Flutter stable (`flutter --version`). Print the full `pubspec.yaml` additions.
|
||||||
|
|
||||||
|
2. Build the complete file manifest, adapting to `{{ARCHITECTURE}}`:
|
||||||
|
|
||||||
|
**For {{ARCHITECTURE}} ({{ARCH_RAW}}):**
|
||||||
|
- `clean`: expand each feature layer explicitly → `domain/entities/`, `domain/repositories/`, `domain/usecases/`, `data/models/`, `data/datasources/`, `data/repositories/`, `presentation/`
|
||||||
|
- `feature_first`: `[feature]/[feature]_screen.dart`, `[feature]_provider.dart` or `[feature]_bloc.dart`, `[feature]_repository.dart`, `[feature]_model.dart`, `widgets/`
|
||||||
|
- `mvc`: `[feature]/model/`, `[feature]/view/`, `[feature]/controller/`
|
||||||
|
|
||||||
|
**State management file naming ({{STATE_MANAGEMENT}} / {{STATE_MGMT_RAW}}):**
|
||||||
|
- `bloc`: `[feature]_bloc.dart`, `[feature]_event.dart`, `[feature]_state.dart`
|
||||||
|
- `riverpod`: `[feature]_provider.dart`, `[feature]_notifier.dart`
|
||||||
|
- `getx`: `[feature]_controller.dart`, `[feature]_binding.dart`
|
||||||
|
|
||||||
|
3. List all modified files from `files_to_touch.modified` — show exactly what change goes in each file.
|
||||||
|
|
||||||
|
4. List all external service configuration required per platform (from `external_setup`). Only show platforms present in `{{PLATFORMS_LIST}}`.
|
||||||
|
|
||||||
|
5. Print research results in this format:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Research Results
|
||||||
|
|
||||||
|
### Packages to add to pubspec.yaml
|
||||||
|
- [package_name]: ^[version]
|
||||||
|
|
||||||
|
### Files to create ({{ARCHITECTURE}} / {{STATE_MANAGEMENT}})
|
||||||
|
lib/features/[name]/... (full list)
|
||||||
|
|
||||||
|
### Files to modify
|
||||||
|
[file_path] — [what changes]
|
||||||
|
|
||||||
|
### External services requiring configuration
|
||||||
|
- [Service] ([platform]) — [what to do]
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not touch any file until the user has seen this output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: TDD Implementation
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:test-driven-development`**
|
||||||
|
|
||||||
|
Follow Red → Green → Refactor strictly. Show actual terminal output at each step.
|
||||||
|
|
||||||
|
### Step 3a — Red: Write all failing tests first
|
||||||
|
|
||||||
|
1. Mirror the feature directory under `test/features/[feature]/`.
|
||||||
|
2. Write unit tests for **every scenario** in `FEATURE_REGISTRY.unit_test_scenarios` for the detected type.
|
||||||
|
3. Write widget tests for **every scenario** in `FEATURE_REGISTRY.widget_test_scenarios`.
|
||||||
|
4. Use `mocktail` for all dependencies.
|
||||||
|
5. Test naming convention: `'given [precondition], when [action], then [expected outcome]'`
|
||||||
|
6. Use the state management test pattern for **{{STATE_MANAGEMENT}}**:
|
||||||
|
```
|
||||||
|
{{TEST_PATTERN}}
|
||||||
|
```
|
||||||
|
7. Run and confirm red:
|
||||||
|
```
|
||||||
|
flutter test test/features/[feature]/ --no-pub
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
If failures are unclear or non-deterministic after one iteration, use **`/debug`** with full pasted output before guessing fixes.
|
||||||
|
|
||||||
|
### Step 3b — Green: Implement
|
||||||
|
|
||||||
|
1. Create domain entities and repository interfaces.
|
||||||
|
2. Create data-layer implementations wiring to `{{BACKEND}}`.
|
||||||
|
3. Create presentation layer using **{{STATE_MANAGEMENT}}** patterns.
|
||||||
|
4. Register in DI container — follow `{{ARCH_IMPORT_RULES}}`.
|
||||||
|
5. Wire the route in `{{ROUTING}}` router.
|
||||||
|
6. If `{{CODEGEN_LIST}}` is not `none`: run `dart run build_runner build --delete-conflicting-outputs` after adding models.
|
||||||
|
7. Run tests and confirm green:
|
||||||
|
```
|
||||||
|
flutter test test/features/[feature]/ --no-pub
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
|
||||||
|
### Step 3c — Refactor
|
||||||
|
|
||||||
|
1. Review for duplication, naming, and layer boundary violations per `.cursor/rules/flutter-core.mdc`.
|
||||||
|
2. Run and confirm clean:
|
||||||
|
```
|
||||||
|
flutter analyze
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
3. Run tests once more to confirm still green.
|
||||||
|
|
||||||
|
If output stays red or errors are ambiguous after two focused attempts, stop and use **`/debug`** with the full failing command and log before changing more code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Integration Test Generation
|
||||||
|
|
||||||
|
**Generate `integration_test/[feature_type]/` before asking the user to run anything.**
|
||||||
|
|
||||||
|
### Integration test file structure
|
||||||
|
|
||||||
|
Each test file follows this template:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// integration_test/[feature]/[scenario]_test.dart
|
||||||
|
// Generated by /build — {{PROJECT_NAME}}
|
||||||
|
// Feature: [feature_type] | Scenario: [scenario_name]
|
||||||
|
// Run with: flutter test integration_test/[feature]/[scenario]_test.dart -d <device_id>
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:{{PACKAGE_ID}}/main.dart' as app;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('[FeatureType] — [Scenario Name]', () {
|
||||||
|
setUp(() async {
|
||||||
|
// Seed state / configure mocks / reset storage
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
// Cleanup
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'given [precondition], when [action], then [expected outcome]',
|
||||||
|
(tester) async {
|
||||||
|
app.main();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// test body
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**For `{{E2E_TOOL}}` (patrol):** swap `flutter_test` imports for `package:patrol` and use Patrol's `$` NativeAutomator selector syntax.
|
||||||
|
|
||||||
|
### Files to generate
|
||||||
|
|
||||||
|
Create one file per logical scenario cluster from `FEATURE_REGISTRY.integration_test_scenarios`.
|
||||||
|
Also generate `integration_test/[feature]/README.md` with the full test matrix and run commands.
|
||||||
|
|
||||||
|
### PAUSE GATE — user must run on device
|
||||||
|
|
||||||
|
After generating all files, print this table and **stop**. Wait for the user to paste device output before Phase 6.
|
||||||
|
|
||||||
|
```
|
||||||
|
## ACTION REQUIRED — Run Integration Tests on Real Device
|
||||||
|
|
||||||
|
| File | Covers | Requires Hardware |
|
||||||
|
|------|--------|------------------|
|
||||||
|
| integration_test/[feature]/[scenario]_test.dart | [what it covers] | Yes/No |
|
||||||
|
...
|
||||||
|
|
||||||
|
Run on iOS:
|
||||||
|
flutter test integration_test/[feature]/ -d <your_ios_device_id>
|
||||||
|
|
||||||
|
Run on Android:
|
||||||
|
flutter test integration_test/[feature]/ -d <your_android_device_id>
|
||||||
|
|
||||||
|
Paste the output here to continue.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: External Setup Checklist
|
||||||
|
|
||||||
|
**Print before asking the user to verify anything. Group by service/platform. Only show sections for platforms in `{{PLATFORMS_LIST}}`.**
|
||||||
|
|
||||||
|
### Format for console/service steps (numbered list):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Firebase (backend: {{BACKEND}})
|
||||||
|
- [ ] 1. Open Firebase Console → [exact menu path]
|
||||||
|
- [ ] 2. [Specific action]
|
||||||
|
- [ ] 3. Download updated config file → place at [exact path]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Format for native platform steps (table):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### iOS Setup
|
||||||
|
| Step | Where | What | How to Verify |
|
||||||
|
|------|-------|------|---------------|
|
||||||
|
| [Step name] | [Xcode location / file path] | [Exact change] | [Verification method] |
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Android Setup
|
||||||
|
- [ ] 1. Open `android/app/src/main/AndroidManifest.xml`
|
||||||
|
- Add: `<uses-permission android:name="[permission]"/>`
|
||||||
|
- [ ] 2. [Next step with exact value]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flavor scoping note (flavors: {{FLAVORS_LIST}})
|
||||||
|
|
||||||
|
Config files and API keys must be scoped per flavor. Never place production keys in `dev` flavor files. Follow the pattern established in `.cursor/rules/` for flavor-based configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Verification Gate
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:verification-before-completion`**
|
||||||
|
|
||||||
|
**Do not claim completion without pasting real output for every item below.**
|
||||||
|
|
||||||
|
```
|
||||||
|
VERIFICATION REQUIRED — paste real output for each:
|
||||||
|
|
||||||
|
[ ] flutter test test/features/[feature]/ --no-pub
|
||||||
|
Required: "All N tests passed."
|
||||||
|
|
||||||
|
[ ] flutter analyze
|
||||||
|
Required: "No issues found!"
|
||||||
|
|
||||||
|
[ ] Integration test device output (from Phase 4 pause gate)
|
||||||
|
Required: actual device test output
|
||||||
|
|
||||||
|
[ ] lefthook run pre-commit
|
||||||
|
Required: all hooks passed
|
||||||
|
```
|
||||||
|
|
||||||
|
If any check fails: invoke `superpowers:systematic-debugging` to diagnose before retrying.
|
||||||
|
For noisy errors or weak reproduction, run **`/debug`** and fill the BugReport skeleton with logs before retrying.
|
||||||
|
For small, isolated changes, **`/verify`** is enough to re-check tests and analyze without re-reading this whole skill.
|
||||||
|
Do not proceed to Phase 7 until all four checks are green.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: PR Preparation
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:finishing-a-development-branch`**
|
||||||
|
|
||||||
|
1. **Commit message** (conventional commits format):
|
||||||
|
```
|
||||||
|
feat([feature_type]): implement [feature] end-to-end
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **PR description template:**
|
||||||
|
```markdown
|
||||||
|
## What
|
||||||
|
[One sentence: what feature was implemented]
|
||||||
|
|
||||||
|
## Test coverage
|
||||||
|
- Unit/widget tests: N passing
|
||||||
|
- Integration test matrix:
|
||||||
|
| Scenario | iOS | Android |
|
||||||
|
|----------|-----|---------|
|
||||||
|
| [scenario] | ✅ | ✅ |
|
||||||
|
|
||||||
|
## External setup completed
|
||||||
|
- [ ] [Service 1 setup]
|
||||||
|
- [ ] [Service 2 setup]
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
[Any follow-up TODOs or known limitations]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **CI/CD advice for {{CICD_TOOL}} ({{CICD_RAW}}):**
|
||||||
|
Ensure workflow secrets are scoped per flavor ({{FLAVORS_LIST}}). Never expose production keys in dev environment secrets. Verify the CI workflow runs `flutter test` and `flutter analyze` before deploy steps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules applied every phase
|
||||||
|
|
||||||
|
Always active — read before writing any code:
|
||||||
|
- `.cursor/rules/flutter-core.mdc`
|
||||||
|
- `.cursor/rules/security-standards.mdc`
|
||||||
|
- `.cursor/rules/project-context.mdc`
|
||||||
|
- Feature-type-specific rules from `FEATURE_REGISTRY.rules_to_load`
|
||||||
|
|
||||||
|
Architecture import rules for **{{ARCHITECTURE}}**:
|
||||||
|
{{ARCH_IMPORT_RULES}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code generation notes
|
||||||
|
|
||||||
|
**Codegen tools configured: {{CODEGEN_LIST}}**
|
||||||
|
|
||||||
|
After adding any new model or injectable class, run:
|
||||||
|
```
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit generated files (`.g.dart`, `.freezed.dart`, `injection.config.dart`) — do not gitignore them.
|
||||||
|
|
||||||
|
**Template version:** {{TEMPLATE_VERSION}}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
name: Debug issue
|
||||||
|
description: Triage failing tests, analyze, CI, or runtime errors with an evidence-first BugReport. Use /debug and paste logs; invokes systematic-debugging before fixes. Stack {{STATE_MANAGEMENT}} / {{ARCHITECTURE}} / {{BACKEND}} / {{PLATFORMS_LIST}}.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Debug — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
Triage failures (tests, CI, runtime, or build) **without** jumping to fixes. Stay in hypothesis-and-evidence mode until root cause is stated.
|
||||||
|
|
||||||
|
**Stack context:** **{{STATE_MANAGEMENT}}** / **{{ARCHITECTURE}}** / **{{BACKEND}}** / platforms: {{PLATFORMS_LIST}}. Flavors: {{FLAVORS_LIST}}. Codegen: {{CODEGEN_LIST}}.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/debug <what failed — paste error output, command, or symptom>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/debug flutter test fails on auth_cubit_test — paste output`
|
||||||
|
- `/debug CI analyze step — paste log excerpt`
|
||||||
|
- `/debug app crashes on cold start after last change`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 — Normalize the report (BugReport)
|
||||||
|
|
||||||
|
Emit this skeleton **before** deep analysis. If the user already pasted logs, map them into the fields instead of re-asking.
|
||||||
|
|
||||||
|
| Field | Content |
|
||||||
|
|-------|---------|
|
||||||
|
| **Summary** | One line: what broke |
|
||||||
|
| **Expected** | What should happen |
|
||||||
|
| **Actual** | What happened (symptom + error text) |
|
||||||
|
| **Repro steps** | Numbered, minimal |
|
||||||
|
| **Scope / files touched** | Paths or PR slice |
|
||||||
|
| **Environment** | OS, Flutter/Dart version if known, device vs simulator, flavor |
|
||||||
|
| **Evidence** | Pasted command output, stack trace, or screenshot notes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Evidence checklist (Flutter-aware)
|
||||||
|
|
||||||
|
Gather or request **concrete** evidence. Do not guess versions or config.
|
||||||
|
|
||||||
|
1. **`flutter doctor -v`** — paste output when environment is unknown or iOS/Android toolchain errors appear.
|
||||||
|
2. **Failing command** — full invocation + **verbatim** tail of output (e.g. `flutter test …`, `dart test …`, `flutter analyze`).
|
||||||
|
3. **`flutter analyze`** — if not already in the failure log, run or ask the user to run and paste.
|
||||||
|
4. **Flavors** — this project uses: **{{FLAVORS_LIST}}**. Confirm which flavor was active if the failure is env-specific.
|
||||||
|
5. **Platforms** — **{{PLATFORMS_LIST}}**. Narrow reproduction to the platform that failed when relevant.
|
||||||
|
6. **Codegen** — tools: **{{CODEGEN_LIST}}**. When this is not `none`, remind to run `dart run build_runner build --delete-conflicting-outputs` after generated files changed, and to align with `.cursor/hooks/` / `lefthook run pre-commit` when hooks are present.
|
||||||
|
7. **Testing depth** — **{{TESTING_DEPTH}}**; E2E tool: **{{E2E_TOOL}}**. Match the failure to the right layer (unit vs widget vs integration).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Root cause (no code yet)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:systematic-debugging`**
|
||||||
|
|
||||||
|
Produce **one paragraph**: hypothesis tied to **specific lines** in the pasted evidence. Mark confidence (high / medium / low). **No code changes** in this phase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Fix (only after Phase 2)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:systematic-debugging`** again while iterating fixes.
|
||||||
|
|
||||||
|
When proposing changes:
|
||||||
|
|
||||||
|
- Respect architecture boundaries for **{{ARCHITECTURE}}**:
|
||||||
|
{{ARCH_IMPORT_RULES}}
|
||||||
|
- Always consider: `.cursor/rules/flutter-core.mdc`, `.cursor/rules/security-standards.mdc`, `.cursor/rules/project-context.mdc`, and state-management rules for **{{STATE_MANAGEMENT}}**.
|
||||||
|
|
||||||
|
After each fix attempt, re-run the **same** failing command and paste new output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACTION REQUIRED
|
||||||
|
|
||||||
|
If evidence is missing, **stop** and print:
|
||||||
|
|
||||||
|
1. Exact commands to run (copy-paste ready).
|
||||||
|
2. What to paste back (full error blocks, not summaries).
|
||||||
|
3. If the user cannot run commands: state assumptions explicitly and set confidence to **low**.
|
||||||
|
|
||||||
|
**Template version:** {{TEMPLATE_VERSION}}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
name: Explain code
|
||||||
|
description: Explain what code does without editing it. Use /explain with a path or symbol; covers {{STATE_MANAGEMENT}} state, {{BACKEND}} I/O, routing {{ROUTING}}, and failure modes. Ask when facts are not in the repo.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Explain — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
Explain **what the code is doing** — **do not** change production code unless the user explicitly asks for a fix.
|
||||||
|
|
||||||
|
**Stack:** **{{STATE_MANAGEMENT}}** / **{{ARCHITECTURE}}** / **{{ROUTING}}** / **{{BACKEND}}** / platforms: {{PLATFORMS_LIST}}. Package: `{{PACKAGE_ID}}`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/explain <file path, widget name, class, or symbol>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/explain lib/features/cart/cart_cubit.dart`
|
||||||
|
- `/explain how checkout routes after payment`
|
||||||
|
- `/explain CartPage build method`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output format (use these headings in order)
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
One short paragraph: why this code exists in the product context (**{{PROJECT_NAME}}** — {{DESCRIPTION}}).
|
||||||
|
|
||||||
|
### Public API
|
||||||
|
|
||||||
|
Surface area: public classes/methods, constructors, and what callers are expected to pass. Note codegen involvement when **{{CODEGEN_LIST}}** is not `none`.
|
||||||
|
|
||||||
|
### Call flow
|
||||||
|
|
||||||
|
Ordered steps from entry point (e.g. widget `build`, route handler, Bloc `on<Event>`, Riverpod `build`, GetX controller lifecycle) through collaborators. Tie navigation to **{{ROUTING}}** where relevant.
|
||||||
|
|
||||||
|
### State and side effects
|
||||||
|
|
||||||
|
How state is held and updated for **{{STATE_MANAGEMENT}}** ({{STATE_MGMT_RAW}}). Mention async work, listeners, and disposal. Reference **{{ARCH_IMPORT_RULES}}** if layering is unclear.
|
||||||
|
|
||||||
|
### I/O and backends
|
||||||
|
|
||||||
|
Network, local storage, or platform channels touching **{{BACKEND}}** (and **{{AUTH}}** where auth applies: {{AUTH_RAW}}). Do **not** invent API shapes not visible in the repo.
|
||||||
|
|
||||||
|
### Failure modes
|
||||||
|
|
||||||
|
What can go wrong: null paths, error states, race conditions, missing permissions on {{PLATFORMS_LIST}}, auth edge cases.
|
||||||
|
|
||||||
|
### Suggested tests
|
||||||
|
|
||||||
|
Ideas aligned with **{{TESTING_DEPTH}}** and **{{E2E_TOOL}}**; for **{{STATE_MANAGEMENT}}**, prefer patterns like:
|
||||||
|
|
||||||
|
```
|
||||||
|
{{TEST_PATTERN}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unknowns — questions for you
|
||||||
|
|
||||||
|
If behavior depends on runtime config, native projects, remote API contracts, or secrets not in tree: **stop** and list **specific** questions. Do not fabricate facts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- **Project brief:** `project-brief.yaml` — feature modules: {{FEATURES_LIST}}; special features: {{SPECIAL_FEATURES}}.
|
||||||
|
- **Scale:** {{SCALE}}; i18n locales: {{LOCALES_LIST}} (when explaining localization).
|
||||||
|
- **Design:** {{DESIGN_SOURCE}}; Figma URL: {{FIGMA_URL}}.
|
||||||
|
- **API docs format:** {{API_DOCS_FORMAT}} (path: {{API_DOCS_PATH}}).
|
||||||
|
- **Related repos (if any):**
|
||||||
|
|
||||||
|
{{GIT_REFS_BLOCK}}
|
||||||
|
|
||||||
|
- **Local paths (if any):**
|
||||||
|
|
||||||
|
{{LOCAL_PATHS_BLOCK}}
|
||||||
|
|
||||||
|
**Template version:** {{TEMPLATE_VERSION}}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
name: Verify change
|
||||||
|
description: Post-change or pre-PR verification without the full /build lifecycle. Use /verify with optional scope; respects testing depth {{TESTING_DEPTH}} and E2E {{E2E_TOOL}}; no success claims without pasted outputs.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Verify — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
Run a **focused** verification gate after a change or before a small PR — without replaying the entire `/build` document.
|
||||||
|
|
||||||
|
**Stack:** **{{STATE_MANAGEMENT}}** / **{{ARCHITECTURE}}** / **{{BACKEND}}** / {{PLATFORMS_LIST}}. **Testing depth:** {{TESTING_DEPTH}}. **E2E tool:** {{E2E_TOOL}}. **Codegen:** {{CODEGEN_LIST}}.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/verify [optional scope: paths, “small PR”, or feature name]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/verify lib/features/auth/`
|
||||||
|
- `/verify small PR before push`
|
||||||
|
- `/verify after dependency bump`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist (adapt to testing depth)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:verification-before-completion`**
|
||||||
|
|
||||||
|
**Do not claim success** until the user (or you, in a trusted environment) has pasted **real** output for each applicable item below.
|
||||||
|
|
||||||
|
### Always (typical Flutter repo)
|
||||||
|
|
||||||
|
- [ ] **Unit / widget tests** for touched code — e.g. `flutter test <path> --no-pub` or full suite as appropriate.
|
||||||
|
**Required in paste:** a line showing tests passed (or the exact failure to fix).
|
||||||
|
- [ ] **`flutter analyze`**
|
||||||
|
**Required in paste:** `No issues found!` or the analyzer errors to address.
|
||||||
|
|
||||||
|
### When `testing.depth` is `full` or `e2e` (this brief: **{{TESTING_DEPTH}}**)
|
||||||
|
|
||||||
|
- [ ] **Integration / E2E** — use **{{E2E_TOOL}}** patterns from `.cursor/rules/` (e.g. Patrol). Run on a device or emulator when scenarios require it; paste run output.
|
||||||
|
|
||||||
|
### When `testing.depth` is `unit_widget` only
|
||||||
|
|
||||||
|
- [ ] **Integration / device E2E** — *not* mandatory unless the change touches integration-only surfaces; if skipped, say so explicitly in the paste.
|
||||||
|
|
||||||
|
### Hooks and codegen
|
||||||
|
|
||||||
|
- [ ] **`lefthook run pre-commit`** — run when the repo has Lefthook configured (especially when **{{CODEGEN_LIST}}** is not `none`). Paste hook summary.
|
||||||
|
If hooks are not set up for this workspace, write *“skipped — hooks not configured”* instead of failing silently.
|
||||||
|
|
||||||
|
### CI alignment
|
||||||
|
|
||||||
|
- [ ] For **{{CICD_TOOL}}** ({{CICD_RAW}}): confirm the same commands the pipeline runs (analyze + tests) are green locally. Note flavor-scoped secrets for **{{FLAVORS_LIST}}**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACTION REQUIRED — paste block
|
||||||
|
|
||||||
|
Print this table and wait for pasted output before declaring done:
|
||||||
|
|
||||||
|
```
|
||||||
|
VERIFICATION — paste real output for each line you executed:
|
||||||
|
|
||||||
|
[ ] flutter test … → (paste: pass count or failures)
|
||||||
|
[ ] flutter analyze → (paste: no issues or errors)
|
||||||
|
[ ] integration / e2e (if depth is full/e2e or change requires it) →
|
||||||
|
[ ] lefthook run pre-commit → (paste or "skipped — not configured")
|
||||||
|
```
|
||||||
|
|
||||||
|
If anything fails: switch to **`/debug`** with the failing log before guessing.
|
||||||
|
|
||||||
|
**Template version:** {{TEMPLATE_VERSION}}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*.jsonl
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Append a JSONL usage line (local only). Requires jq when passing structured payload.
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
LOG="${ROOT}/telemetry/usage.jsonl"
|
||||||
|
mkdir -p "$(dirname "$LOG")"
|
||||||
|
echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"event\":\"${1:-note}\",\"detail\":\"${2:-}\"}" >>"$LOG"
|
||||||
@@ -3,109 +3,41 @@
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
import 'golden_briefs.dart';
|
||||||
import '../src/resolver.dart';
|
import '../src/resolver.dart';
|
||||||
import '../src/renderer.dart';
|
import '../src/renderer.dart';
|
||||||
import '../src/validator.dart';
|
import '../src/validator.dart';
|
||||||
import '../src/models.dart';
|
import '../src/models.dart';
|
||||||
|
import '../src/mcp_json.dart';
|
||||||
// ─── Test fixtures ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
final _blocCleanBrief = ProjectBrief(
|
|
||||||
projectName: 'TestApp',
|
|
||||||
packageId: 'com.test.testapp',
|
|
||||||
description: 'Test app for golden tests',
|
|
||||||
scale: 'medium',
|
|
||||||
stateManagement: 'bloc',
|
|
||||||
routing: 'gorouter',
|
|
||||||
architecture: 'clean',
|
|
||||||
backends: ['firebase'],
|
|
||||||
auth: 'firebase_auth',
|
|
||||||
platforms: ['ios', 'android'],
|
|
||||||
codegenTools: ['freezed'],
|
|
||||||
flavors: ['dev', 'prod'],
|
|
||||||
cicd: 'github_actions',
|
|
||||||
testingDepth: 'unit_widget',
|
|
||||||
e2eTool: 'patrol',
|
|
||||||
designSource: 'none',
|
|
||||||
figmaUrl: '',
|
|
||||||
apiDocsFormat: 'none',
|
|
||||||
apiDocsPath: '',
|
|
||||||
referenceRepos: [],
|
|
||||||
localPaths: [],
|
|
||||||
featureModules: ['auth', 'home', 'products'],
|
|
||||||
specialFeatures: [],
|
|
||||||
i18nEnabled: false,
|
|
||||||
locales: ['en'],
|
|
||||||
);
|
|
||||||
|
|
||||||
final _riverpodFFBrief = ProjectBrief(
|
|
||||||
projectName: 'TaskFlow',
|
|
||||||
packageId: 'com.test.taskflow',
|
|
||||||
description: 'Task management app',
|
|
||||||
scale: 'small',
|
|
||||||
stateManagement: 'riverpod',
|
|
||||||
routing: 'gorouter',
|
|
||||||
architecture: 'feature_first',
|
|
||||||
backends: ['supabase'],
|
|
||||||
auth: 'supabase_auth',
|
|
||||||
platforms: ['ios', 'android', 'web'],
|
|
||||||
codegenTools: ['freezed', 'json_serializable'],
|
|
||||||
flavors: ['dev', 'prod'],
|
|
||||||
cicd: 'github_actions',
|
|
||||||
testingDepth: 'unit_widget',
|
|
||||||
e2eTool: 'patrol',
|
|
||||||
designSource: 'none',
|
|
||||||
figmaUrl: '',
|
|
||||||
apiDocsFormat: 'none',
|
|
||||||
apiDocsPath: '',
|
|
||||||
referenceRepos: [],
|
|
||||||
localPaths: [],
|
|
||||||
featureModules: ['auth', 'tasks', 'profile'],
|
|
||||||
specialFeatures: [],
|
|
||||||
i18nEnabled: true,
|
|
||||||
locales: ['en', 'fr'],
|
|
||||||
);
|
|
||||||
|
|
||||||
final _getxMvcBrief = ProjectBrief(
|
|
||||||
projectName: 'LegacyApp',
|
|
||||||
packageId: 'com.test.legacy',
|
|
||||||
description: 'Legacy GetX app',
|
|
||||||
scale: 'medium',
|
|
||||||
stateManagement: 'getx',
|
|
||||||
routing: 'getx_nav',
|
|
||||||
architecture: 'mvc',
|
|
||||||
backends: ['rest'],
|
|
||||||
auth: 'jwt_rest',
|
|
||||||
platforms: ['ios', 'android'],
|
|
||||||
codegenTools: [],
|
|
||||||
flavors: ['dev', 'prod'],
|
|
||||||
cicd: 'codemagic',
|
|
||||||
testingDepth: 'unit_widget',
|
|
||||||
e2eTool: 'patrol',
|
|
||||||
designSource: 'none',
|
|
||||||
figmaUrl: '',
|
|
||||||
apiDocsFormat: 'openapi',
|
|
||||||
apiDocsPath: 'docs/api.yaml',
|
|
||||||
referenceRepos: [],
|
|
||||||
localPaths: [],
|
|
||||||
featureModules: ['auth', 'dashboard'],
|
|
||||||
specialFeatures: [],
|
|
||||||
i18nEnabled: false,
|
|
||||||
locales: ['en'],
|
|
||||||
);
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// ─── Resolver tests ─────────────────────────────────────────────────────────
|
// ─── Resolver tests ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
group('Resolver', () {
|
group('Resolver', () {
|
||||||
test('BLoC + Clean resolves correct files', () {
|
test('BLoC + Clean resolves correct files', () {
|
||||||
final files = Resolver.resolve(_blocCleanBrief);
|
final files = Resolver.resolve(kBlocCleanFirebaseBrief);
|
||||||
|
|
||||||
// Universal — always present
|
// Universal — always present
|
||||||
|
expect(files.first, equals('rules/universal/rule-authoring'));
|
||||||
expect(files, contains('rules/universal/flutter-core'));
|
expect(files, contains('rules/universal/flutter-core'));
|
||||||
expect(files, contains('rules/universal/ui-ux-standards'));
|
expect(files, contains('rules/universal/ui-ux-standards'));
|
||||||
expect(files, contains('rules/universal/project-context'));
|
expect(files, contains('rules/universal/project-context'));
|
||||||
|
|
||||||
|
// CI/CD rule when cicd set or multiple flavors
|
||||||
|
expect(files, contains('rules/cicd/cicd'));
|
||||||
|
|
||||||
|
// Feature stubs from brief
|
||||||
|
expect(files, contains('rules/features/auth'));
|
||||||
|
expect(files, contains('rules/features/home'));
|
||||||
|
expect(files, contains('rules/features/products'));
|
||||||
|
|
||||||
|
// Cursor workspace extras
|
||||||
|
expect(files, contains('root/.cursorignore'));
|
||||||
|
expect(files, contains('root/tool/cursor_audit.sh'));
|
||||||
|
expect(files, contains('onboarding/ONBOARDING'));
|
||||||
|
expect(files, contains('commands/build'));
|
||||||
|
expect(files, contains('commands/debug-issue'));
|
||||||
|
|
||||||
// Security — always present (Pillar 5)
|
// Security — always present (Pillar 5)
|
||||||
expect(files, contains('rules/security/security-standards'));
|
expect(files, contains('rules/security/security-standards'));
|
||||||
|
|
||||||
@@ -141,10 +73,16 @@ void main() {
|
|||||||
expect(files, containsNot('rules/state-management/getx'));
|
expect(files, containsNot('rules/state-management/getx'));
|
||||||
expect(files, containsNot('rules/architecture/feature_first'));
|
expect(files, containsNot('rules/architecture/feature_first'));
|
||||||
expect(files, containsNot('rules/routing/getx_nav'));
|
expect(files, containsNot('rules/routing/getx_nav'));
|
||||||
|
|
||||||
|
expect(files, contains('commands/verify-change'));
|
||||||
|
expect(files, contains('commands/explain-code'));
|
||||||
|
expect(files, containsNot('config/mcp-json'));
|
||||||
|
expect(files, contains('root/AGENTS.md'));
|
||||||
|
expect(files, contains('root/lefthook.yaml'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Riverpod + Feature-First + Web resolves web platform template', () {
|
test('Riverpod + Feature-First + Web resolves web platform template', () {
|
||||||
final files = Resolver.resolve(_riverpodFFBrief);
|
final files = Resolver.resolve(kRiverpodFfSupabaseBrief);
|
||||||
expect(files, contains('rules/platform/platform-web'));
|
expect(files, contains('rules/platform/platform-web'));
|
||||||
expect(files, contains('rules/state-management/riverpod'));
|
expect(files, contains('rules/state-management/riverpod'));
|
||||||
expect(files, contains('rules/architecture/feature_first'));
|
expect(files, contains('rules/architecture/feature_first'));
|
||||||
@@ -154,8 +92,177 @@ void main() {
|
|||||||
expect(files, containsNot('agents/migration-agent'));
|
expect(files, containsNot('agents/migration-agent'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('featureRuleKey sanitizes module names', () {
|
||||||
|
expect(Resolver.featureRuleKey('My Cart'), 'rules/features/my_cart');
|
||||||
|
expect(Resolver.featureRuleKey('auth'), 'rules/features/auth');
|
||||||
|
expect(Resolver.featureRuleKey(' '), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('MCP template key when integrations.mcp.enabled', () {
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'McpApp',
|
||||||
|
packageId: 'com.test.mcp',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['firebase'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
mcpConfigEnabled: true,
|
||||||
|
);
|
||||||
|
expect(Resolver.resolve(brief), contains('config/mcp-json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Push/deeplink rule when special features request it', () {
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'PushApp',
|
||||||
|
packageId: 'com.test.push',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['rest'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: ['push_notifications'],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
);
|
||||||
|
expect(Resolver.resolve(brief), contains('rules/integrations/push-deeplink'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Theming rule when high contrast requested', () {
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'A11yApp',
|
||||||
|
packageId: 'com.test.a11y',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['rest'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
themeVariants: const ['light', 'dark', 'high_contrast'],
|
||||||
|
);
|
||||||
|
expect(Resolver.resolve(brief), contains('rules/theming/theming'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Telemetry helpers when telemetry_opt_in', () {
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'TelApp',
|
||||||
|
packageId: 'com.test.tel',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['rest'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
telemetryOptIn: true,
|
||||||
|
);
|
||||||
|
final files = Resolver.resolve(brief);
|
||||||
|
expect(files, contains('telemetry/gitignore'));
|
||||||
|
expect(files, contains('telemetry/log-sh'));
|
||||||
|
expect(files, contains('rules/telemetry/usage-logging'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('McpJsonBuilder minimal preset emits empty mcpServers', () {
|
||||||
|
const brief = ProjectBrief(
|
||||||
|
projectName: 'M',
|
||||||
|
packageId: 'com.m.m',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['rest'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
mcpConfigEnabled: true,
|
||||||
|
mcpPreset: 'minimal',
|
||||||
|
);
|
||||||
|
expect(McpJsonBuilder.build(brief), contains('"mcpServers": {}'));
|
||||||
|
});
|
||||||
|
|
||||||
test('GetX + MVC includes migration-agent', () {
|
test('GetX + MVC includes migration-agent', () {
|
||||||
final files = Resolver.resolve(_getxMvcBrief);
|
final files = Resolver.resolve(kGetxMvcRestBrief);
|
||||||
expect(files, contains('agents/migration-agent'));
|
expect(files, contains('agents/migration-agent'));
|
||||||
expect(files, contains('rules/state-management/getx'));
|
expect(files, contains('rules/state-management/getx'));
|
||||||
expect(files, contains('rules/architecture/mvc'));
|
expect(files, contains('rules/architecture/mvc'));
|
||||||
@@ -168,7 +275,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Codegen stack includes Cursor hooks templates', () {
|
test('Codegen stack includes Cursor hooks templates', () {
|
||||||
final files = Resolver.resolve(_blocCleanBrief);
|
final files = Resolver.resolve(kBlocCleanFirebaseBrief);
|
||||||
expect(files, contains('hooks/hooks-json'));
|
expect(files, contains('hooks/hooks-json'));
|
||||||
expect(files, contains('hooks/arch-guard'));
|
expect(files, contains('hooks/arch-guard'));
|
||||||
});
|
});
|
||||||
@@ -238,11 +345,26 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('No duplicate files in resolved list', () {
|
test('No duplicate files in resolved list', () {
|
||||||
final files = Resolver.resolve(_blocCleanBrief);
|
final files = Resolver.resolve(kBlocCleanFirebaseBrief);
|
||||||
final unique = files.toSet();
|
final unique = files.toSet();
|
||||||
expect(files.length, equals(unique.length),
|
expect(files.length, equals(unique.length),
|
||||||
reason: 'Duplicate template files detected');
|
reason: 'Duplicate template files detected');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Universal slash skills always in resolved list', () {
|
||||||
|
const universalSkills = [
|
||||||
|
'skills/build',
|
||||||
|
'skills/debug-issue',
|
||||||
|
'skills/verify-change',
|
||||||
|
'skills/explain-code',
|
||||||
|
];
|
||||||
|
for (final skill in universalSkills) {
|
||||||
|
expect(Resolver.resolve(kBlocCleanFirebaseBrief), contains(skill));
|
||||||
|
expect(Resolver.resolve(kRiverpodFfSupabaseBrief), contains(skill));
|
||||||
|
expect(Resolver.resolve(kGetxMvcRestBrief), contains(skill));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Renderer / placeholder tests ───────────────────────────────────────────
|
// ─── Renderer / placeholder tests ───────────────────────────────────────────
|
||||||
@@ -274,8 +396,8 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final rendered = await Renderer.render(
|
final rendered = await Renderer.render(
|
||||||
brief: _blocCleanBrief,
|
brief: kBlocCleanFirebaseBrief,
|
||||||
templateFiles: Resolver.resolve(_blocCleanBrief),
|
templateFiles: Resolver.resolve(kBlocCleanFirebaseBrief),
|
||||||
templateSrc: templateDir,
|
templateSrc: templateDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -300,7 +422,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final rendered = await Renderer.render(
|
final rendered = await Renderer.render(
|
||||||
brief: _blocCleanBrief,
|
brief: kBlocCleanFirebaseBrief,
|
||||||
templateFiles: ['hooks/arch-guard'],
|
templateFiles: ['hooks/arch-guard'],
|
||||||
templateSrc: templateDir,
|
templateSrc: templateDir,
|
||||||
);
|
);
|
||||||
@@ -312,13 +434,58 @@ void main() {
|
|||||||
'Default template dir should resolve real arch-guard.ts.tmpl');
|
'Default template dir should resolve real arch-guard.ts.tmpl');
|
||||||
expect(content, contains('arch-guard'));
|
expect(content, contains('arch-guard'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('mcp.json renders from config/mcp-json key', () async {
|
||||||
|
final templateDir = _templateDir();
|
||||||
|
if (!Directory(templateDir).existsSync()) {
|
||||||
|
markTestSkipped('Template directory not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'McpApp',
|
||||||
|
packageId: 'com.test.mcp',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: const ['firebase'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: const ['ios'],
|
||||||
|
codegenTools: const [],
|
||||||
|
flavors: const ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: const [],
|
||||||
|
localPaths: const [],
|
||||||
|
featureModules: const [],
|
||||||
|
specialFeatures: const [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: const ['en'],
|
||||||
|
mcpConfigEnabled: true,
|
||||||
|
);
|
||||||
|
final rendered = await Renderer.render(
|
||||||
|
brief: brief,
|
||||||
|
templateFiles: const ['config/mcp-json'],
|
||||||
|
templateSrc: templateDir,
|
||||||
|
);
|
||||||
|
final json = rendered['mcp.json']!;
|
||||||
|
expect(json, contains('mcpServers'));
|
||||||
|
expect(json, contains('filesystem'));
|
||||||
|
expect(json, contains('firebase'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Validator tests ─────────────────────────────────────────────────────────
|
// ─── Validator tests ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
group('Validator', () {
|
group('Validator', () {
|
||||||
test('Valid brief passes validation', () async {
|
test('Valid brief passes validation', () async {
|
||||||
final result = await Validator.validate(_blocCleanBrief);
|
final result = await Validator.validate(kBlocCleanFirebaseBrief);
|
||||||
expect(result.isValid, isTrue);
|
expect(result.isValid, isTrue);
|
||||||
expect(result.errors, isEmpty);
|
expect(result.errors, isEmpty);
|
||||||
});
|
});
|
||||||
@@ -401,8 +568,8 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final rendered = await Renderer.render(
|
final rendered = await Renderer.render(
|
||||||
brief: _blocCleanBrief,
|
brief: kBlocCleanFirebaseBrief,
|
||||||
templateFiles: Resolver.resolve(_blocCleanBrief),
|
templateFiles: Resolver.resolve(kBlocCleanFirebaseBrief),
|
||||||
templateSrc: templateDir,
|
templateSrc: templateDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -430,11 +597,11 @@ void main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final rendered = await Renderer.render(
|
final rendered = await Renderer.render(
|
||||||
brief: _riverpodFFBrief,
|
brief: kRiverpodFfSupabaseBrief,
|
||||||
templateFiles: Resolver.resolve(_riverpodFFBrief),
|
templateFiles: Resolver.resolve(kRiverpodFfSupabaseBrief),
|
||||||
templateSrc: templateDir,
|
templateSrc: templateDir,
|
||||||
);
|
);
|
||||||
_compareGoldens('test/golden/riverpod-ff-supabase', rendered);
|
await _compareGoldens('test/golden/riverpod-ff-supabase', rendered);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GetX + MVC renders match golden files', () async {
|
test('GetX + MVC renders match golden files', () async {
|
||||||
@@ -444,11 +611,11 @@ void main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final rendered = await Renderer.render(
|
final rendered = await Renderer.render(
|
||||||
brief: _getxMvcBrief,
|
brief: kGetxMvcRestBrief,
|
||||||
templateFiles: Resolver.resolve(_getxMvcBrief),
|
templateFiles: Resolver.resolve(kGetxMvcRestBrief),
|
||||||
templateSrc: templateDir,
|
templateSrc: templateDir,
|
||||||
);
|
);
|
||||||
_compareGoldens('test/golden/getx-mvc-rest', rendered);
|
await _compareGoldens('test/golden/getx-mvc-rest', rendered);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — TestApp
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# TestApp
|
||||||
|
|
||||||
|
Repo-level notes for AI assistants. Authoritative stack and conventions are in `project-brief.yaml` and `.cursor/` (regenerate with `cursor_gen` from the project root).
|
||||||
|
|
||||||
|
- **Package:** `com.test.testapp`
|
||||||
|
- **Onboarding:** `.cursor/ONBOARDING.md` — layout, slash commands → skills, MCP opt-in, audits
|
||||||
|
- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` (see `.cursor/commands/*.md`)
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
# TestApp — generated by cursor_gen; adjust commands to your repo
|
||||||
|
# Optional: run `bash tool/cursor_audit.sh` after changing features.modules or rule files.
|
||||||
|
pre-commit:
|
||||||
|
commands:
|
||||||
|
flutter-analyze:
|
||||||
|
run: flutter analyze
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# TestApp — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for TestApp
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — TestApp
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **dev, prod**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — patrol on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **GitHub Actions** (`github_actions`)
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module auth — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/auth/**", "test/**/auth/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Auth
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `auth` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module home — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/home/**", "test/**/home/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Home
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `home` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module products — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/products/**", "test/**/products/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Products
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `products` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+6
-5
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Core Flutter conventions for TestApp — always applied"
|
description: "Core Flutter conventions for TestApp"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Flutter Core Standards — TestApp
|
# Flutter Core Standards — TestApp
|
||||||
@@ -29,9 +30,9 @@ alwaysApply: true
|
|||||||
- Private members: `_camelCase`
|
- Private members: `_camelCase`
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
- Order: dart: → package: → relative
|
### Imports (strict — `conventions.strict_package_imports: true`)
|
||||||
- Use relative imports within a feature; absolute for cross-feature
|
- Use `package:<your_pubspec_name>/...` imports everywhere in `lib/` and `test/` — **no** relative `../` across feature boundaries (the brief `project.package` id is often the app bundle id; prefer the **pubspec.yaml `name`** for Dart imports)
|
||||||
- Never import a feature's internal files from outside that feature
|
- Barrel files (`index.dart`) at feature roots; do not wildcard re-export third-party packages
|
||||||
|
|
||||||
## Code quality
|
## Code quality
|
||||||
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Project context for TestApp — always applied"
|
description: "Stack summary and product context for TestApp"
|
||||||
alwaysApply: true
|
globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project Context — TestApp
|
# Project Context — TestApp
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
description: How Cursor rules in this repository must be written and maintained.
|
||||||
|
globs: [".cursor/rules/**/*.mdc"]
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rule authoring — TestApp
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Rules are version-controlled contracts for AI assistants. Poor rules waste context and silently steer every edit.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- One focused concern per file; split broad topics instead of one mega-rule
|
||||||
|
- Every rule MUST have a clear `description` in frontmatter (one sentence)
|
||||||
|
- Prefer `alwaysApply: false` with **narrow** `globs` for domain rules — reserve `alwaysApply: true` for meta and safety
|
||||||
|
- `globs` must be as specific as possible — never `["**/*"]` unless tooling requires it
|
||||||
|
- Code samples in rules MUST be valid for this project (Dart/Flutter/YAML as appropriate)
|
||||||
|
- Deprecated guidance is removed, not left commented out
|
||||||
|
- Each substantive rule includes **Context** (why), **Constraints** (must/must not), and where helpful **Patterns** / **Anti-patterns**
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Domain rules (testing, l10n, a feature) with `alwaysApply: true` — burns context
|
||||||
|
- Rules with no concrete examples when the topic is code-facing
|
||||||
|
- Stale feature rules after modules are removed — run `tool/cursor_audit.sh` periodically
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "UI/UX standards for TestApp — always applied"
|
description: "UI/UX standards for TestApp"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# UI / UX Standards — TestApp
|
# UI / UX Standards — TestApp
|
||||||
|
|||||||
+579
@@ -0,0 +1,579 @@
|
|||||||
|
# Build — TestApp
|
||||||
|
|
||||||
|
Implements any feature end-to-end: deep research → TDD → integration tests → external setup checklist → verified PR.
|
||||||
|
Stack: **BLoC / Cubit** / **Clean Architecture** / **Firebase** / ios, android.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/build <free-text description of what to implement>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `/build implement notification module end-to-end`
|
||||||
|
- `/build add biometric auth`
|
||||||
|
- `/build integrate Stripe payments`
|
||||||
|
- `/build add deep linking for shared product pages`
|
||||||
|
- `/build implement analytics event tracking`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FEATURE_REGISTRY
|
||||||
|
|
||||||
|
The AI reads this registry to classify the request and load the right research, test scenarios, and setup steps.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
feature_registry:
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
keywords: [notification, push, fcm, apns, alert, badge, silent push,
|
||||||
|
remote notification, local notification, firebase messaging]
|
||||||
|
pub_packages: [firebase_messaging, flutter_local_notifications, firebase_core]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- parse notification payload to domain model
|
||||||
|
- FCM token generation and storage
|
||||||
|
- token refresh triggers re-registration
|
||||||
|
- notification permission denied returns graceful fallback
|
||||||
|
- notification service initialisation is idempotent
|
||||||
|
widget_test_scenarios:
|
||||||
|
- notification banner renders correct title and body
|
||||||
|
- tap on notification navigates to correct route
|
||||||
|
- badge count updates on new message
|
||||||
|
integration_test_scenarios:
|
||||||
|
- foreground notification receipt and display
|
||||||
|
- background notification receipt (app backgrounded, not killed)
|
||||||
|
- killed-state notification receipt (cold launch from notification tap)
|
||||||
|
- tap-to-open in foreground state routes correctly
|
||||||
|
- tap-to-open in background state routes correctly
|
||||||
|
- tap-to-open in killed state routes correctly
|
||||||
|
- notification payload parsing end-to-end
|
||||||
|
- deep link routing from notification data field
|
||||||
|
- multiple simultaneous notifications (ordering and dedup)
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Cloud Messaging, download google-services.json, download GoogleService-Info.plist]
|
||||||
|
ios: [Push Notifications capability, Background Modes remote notifications, APNs .p8 key upload to Firebase]
|
||||||
|
android: [POST_NOTIFICATIONS permission, RECEIVE_BOOT_COMPLETED permission, google-services plugin]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/notifications/, integration_test/notifications/]
|
||||||
|
modified: [lib/main.dart, android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, ios/Runner/AppDelegate.swift, pubspec.yaml]
|
||||||
|
|
||||||
|
auth:
|
||||||
|
keywords: [auth, authentication, login, sign in, sign out, logout,
|
||||||
|
biometric, face id, touch id, fingerprint, oauth, jwt,
|
||||||
|
session, token refresh, social login, google sign-in]
|
||||||
|
pub_packages: [firebase_auth, supabase_flutter, local_auth, flutter_secure_storage, google_sign_in]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- login success stores token securely
|
||||||
|
- login failure (wrong password) returns typed error
|
||||||
|
- login failure (network) returns typed error
|
||||||
|
- token refresh succeeds and updates stored token
|
||||||
|
- token refresh failure triggers logout
|
||||||
|
- biometric auth success grants access
|
||||||
|
- biometric auth failure falls back to password
|
||||||
|
- biometric not enrolled returns correct error state
|
||||||
|
- logout clears all stored credentials
|
||||||
|
- session persistence: token loaded on cold start
|
||||||
|
widget_test_scenarios:
|
||||||
|
- login form renders correctly
|
||||||
|
- validation errors shown inline
|
||||||
|
- loading state disables submit button
|
||||||
|
- biometric prompt shown when available
|
||||||
|
integration_test_scenarios:
|
||||||
|
- complete login flow (email and password)
|
||||||
|
- login then logout then login again
|
||||||
|
- invalid credentials shows error and stays on login
|
||||||
|
- biometric login flow on enrolled device
|
||||||
|
- biometric fallback to password
|
||||||
|
- token refresh in background while user navigates
|
||||||
|
- cold start with stored valid session skips login
|
||||||
|
- cold start with expired session redirects to login
|
||||||
|
- social auth OAuth redirect and return
|
||||||
|
- deep link into protected route redirects to login then back
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Email/Password provider, Enable Google provider, download updated config files]
|
||||||
|
ios: [NSFaceIDUsageDescription in Info.plist, LocalAuthentication.framework in Xcode]
|
||||||
|
android: [USE_BIOMETRIC permission, USE_FINGERPRINT permission, minSdkVersion 23 for biometric]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/auth/, integration_test/auth/]
|
||||||
|
modified: [lib/main.dart, lib/core/di/injection.dart, pubspec.yaml]
|
||||||
|
|
||||||
|
payments:
|
||||||
|
keywords: [payment, stripe, in-app purchase, iap, checkout, subscription,
|
||||||
|
billing, apple pay, google pay, card, transaction, refund, revenue cat]
|
||||||
|
pub_packages: [flutter_stripe, purchases_flutter, in_app_purchase]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- checkout request builds correct PaymentIntent params
|
||||||
|
- payment success updates order state
|
||||||
|
- payment failure (card declined) returns typed error
|
||||||
|
- webhook event parsed to domain model
|
||||||
|
- refund request constructs correct API call
|
||||||
|
- subscription status checked on app resume
|
||||||
|
widget_test_scenarios:
|
||||||
|
- checkout form renders with correct amount
|
||||||
|
- payment loading state shown during processing
|
||||||
|
- success confirmation screen renders
|
||||||
|
- error state with retry option
|
||||||
|
integration_test_scenarios:
|
||||||
|
- end-to-end checkout with test card (Stripe test mode)
|
||||||
|
- checkout with 3D Secure challenge
|
||||||
|
- card declined shows error then retry succeeds
|
||||||
|
- Apple Pay / Google Pay sheet appears (device capability check)
|
||||||
|
- subscription purchase and entitlement unlock
|
||||||
|
- subscription restore flow
|
||||||
|
- refund flow from order history
|
||||||
|
external_setup:
|
||||||
|
stripe: [publishable and secret keys in flavor .env files, webhook endpoint in Stripe Dashboard, Apple Pay domain registration]
|
||||||
|
ios: [In-App Purchase capability, Apple Pay capability and merchant identifier, com.apple.developer.in-app-payments entitlement]
|
||||||
|
android: [Google Pay Console setup, BILLING permission for in_app_purchase]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/payments/, integration_test/payments/]
|
||||||
|
modified: [ios/Runner/Runner.entitlements, android/app/src/main/AndroidManifest.xml, pubspec.yaml]
|
||||||
|
|
||||||
|
deep_links:
|
||||||
|
keywords: [deep link, deep linking, universal link, app link, deferred deep link,
|
||||||
|
dynamic link, branch, uri scheme, custom scheme, url scheme]
|
||||||
|
pub_packages: [go_router, app_links, uni_links]
|
||||||
|
rules_to_load: [platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- URI parsed to correct route and parameters
|
||||||
|
- unknown URI falls back to home
|
||||||
|
- authenticated-only route redirects to login when unauthenticated
|
||||||
|
- deep link preserves query parameters
|
||||||
|
widget_test_scenarios:
|
||||||
|
- navigation guard redirects unauthenticated deep link
|
||||||
|
integration_test_scenarios:
|
||||||
|
- cold start from Universal Link routes to correct screen
|
||||||
|
- cold start from custom URI scheme routes correctly
|
||||||
|
- backgrounded app receives deep link and navigates
|
||||||
|
- foreground app receives deep link and navigates
|
||||||
|
- deep link to authenticated route redirects to login then original destination
|
||||||
|
- deep link with path parameters loads correct content
|
||||||
|
- invalid or malformed deep link shows 404 screen
|
||||||
|
external_setup:
|
||||||
|
ios: [Associated Domains capability applinks:yourdomain.com, host apple-app-site-association file at /.well-known/]
|
||||||
|
android: [intent-filter with android:autoVerify=true in AndroidManifest.xml, host assetlinks.json at /.well-known/]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/core/routing/deep_link_handler.dart, integration_test/deep_links/]
|
||||||
|
modified: [lib/core/routing/router.dart, android/app/src/main/AndroidManifest.xml, ios/Runner/Runner.entitlements, ios/Runner/Info.plist]
|
||||||
|
|
||||||
|
analytics:
|
||||||
|
keywords: [analytics, tracking, event, mixpanel, amplitude, firebase analytics,
|
||||||
|
segment, posthog, screen view, funnel, cohort]
|
||||||
|
pub_packages: [firebase_analytics, mixpanel_flutter, amplitude_flutter, posthog_flutter]
|
||||||
|
rules_to_load: [security-standards.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- analytics service logs correct event name and params
|
||||||
|
- PII fields stripped before event sent
|
||||||
|
- analytics disabled in dev flavor
|
||||||
|
- screen name logged on navigation
|
||||||
|
widget_test_scenarios:
|
||||||
|
- RouteObserver triggers screen_view event
|
||||||
|
integration_test_scenarios:
|
||||||
|
- user action triggers expected event (verify via debug view)
|
||||||
|
- screen transitions log screen_view with correct names
|
||||||
|
- opt-out disables all tracking
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Analytics in Firebase Console, enable DebugView for local testing, configure conversion events]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/core/analytics/analytics_service.dart, lib/core/analytics/analytics_events.dart, integration_test/analytics/]
|
||||||
|
modified: [lib/main.dart, pubspec.yaml]
|
||||||
|
|
||||||
|
storage:
|
||||||
|
keywords: [storage, file upload, download, cloud storage, firebase storage,
|
||||||
|
supabase storage, s3, image upload, document, file picker,
|
||||||
|
offline, cache, hive, isar, objectbox, sqflite, drift]
|
||||||
|
pub_packages: [firebase_storage, supabase_flutter, hive_flutter, isar, drift, file_picker, image_picker, path_provider]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- upload returns public URL on success
|
||||||
|
- upload failure returns typed error
|
||||||
|
- local cache read before remote fetch (offline-first)
|
||||||
|
- cache invalidation on TTL expiry
|
||||||
|
- large file upload uses resumable upload
|
||||||
|
widget_test_scenarios:
|
||||||
|
- file picker button triggers picker
|
||||||
|
- upload progress indicator shown
|
||||||
|
- image preview renders after selection
|
||||||
|
integration_test_scenarios:
|
||||||
|
- end-to-end file upload and retrieval
|
||||||
|
- offline mode: local cache serves data
|
||||||
|
- background upload completes when connectivity restored
|
||||||
|
- file size limit enforced
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Firebase Storage, configure Storage security rules, set CORS policy for web if applicable]
|
||||||
|
ios: [NSPhotoLibraryUsageDescription in Info.plist, NSCameraUsageDescription in Info.plist]
|
||||||
|
android: [READ_EXTERNAL_STORAGE or READ_MEDIA_IMAGES permission]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/storage/, integration_test/storage/]
|
||||||
|
modified: [android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, pubspec.yaml]
|
||||||
|
|
||||||
|
camera_media:
|
||||||
|
keywords: [camera, photo, video, image picker, qr code, barcode scanner,
|
||||||
|
ar, augmented reality, gallery, media, record, capture]
|
||||||
|
pub_packages: [camera, image_picker, mobile_scanner, qr_flutter, image_cropper, video_player]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- QR/barcode parsed to correct domain model
|
||||||
|
- image compressed before upload
|
||||||
|
- camera permission denied returns typed error
|
||||||
|
widget_test_scenarios:
|
||||||
|
- camera preview widget renders
|
||||||
|
- capture button triggers photo take
|
||||||
|
integration_test_scenarios:
|
||||||
|
- camera opens and captures photo on real device
|
||||||
|
- gallery picker selects image and returns
|
||||||
|
- QR scan decodes valid code correctly
|
||||||
|
- permission denied shows correct error UI
|
||||||
|
external_setup:
|
||||||
|
ios: [NSCameraUsageDescription, NSPhotoLibraryUsageDescription, NSMicrophoneUsageDescription for video]
|
||||||
|
android: [CAMERA permission, READ_MEDIA_IMAGES for gallery access]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/camera/, integration_test/camera/]
|
||||||
|
modified: [android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, pubspec.yaml]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Context Loading
|
||||||
|
|
||||||
|
**When the user types `/build <request>`, do the following before writing a single line of code:**
|
||||||
|
|
||||||
|
1. Read `project-brief.yaml` from the repo root. Extract and hold in context:
|
||||||
|
- `stack.state_management` → **bloc**
|
||||||
|
- `stack.architecture` → **clean**
|
||||||
|
- `stack.routing` → **gorouter**
|
||||||
|
- `stack.backend` → **firebase**
|
||||||
|
- `stack.platforms` → **ios, android**
|
||||||
|
- `stack.codegen` → **freezed**
|
||||||
|
- `testing.depth` → **unit_widget**
|
||||||
|
- `testing.e2e_tool` → **patrol**
|
||||||
|
- `features.modules` (existing features) → **auth, home, products**
|
||||||
|
- `features.special` → ****
|
||||||
|
- `environments.flavors` → **dev, prod**
|
||||||
|
- `environments.cicd` → **github_actions**
|
||||||
|
|
||||||
|
2. Parse the user's free-text request. Match against `FEATURE_REGISTRY` keywords using longest-match. Multiple types are allowed if the request spans features.
|
||||||
|
|
||||||
|
3. Load `.cursor/rules/` files listed under `rules_to_load` for the matched feature type.
|
||||||
|
|
||||||
|
4. If the user's message contains a URL or GitHub repo reference, fetch and index it for pattern reference.
|
||||||
|
|
||||||
|
5. Check `features.modules` — if the feature already exists in the list, ask: "This feature is already listed in project-brief.yaml. Building additional capability on top of it? (y/n)"
|
||||||
|
|
||||||
|
6. Print the context summary table before proceeding:
|
||||||
|
|
||||||
|
```
|
||||||
|
| Field | Value |
|
||||||
|
|------------------|------------------------------|
|
||||||
|
| Feature type | [detected type] |
|
||||||
|
| State management | BLoC / Cubit |
|
||||||
|
| Architecture | Clean Architecture |
|
||||||
|
| Backend | Firebase |
|
||||||
|
| Platforms | ios, android |
|
||||||
|
| E2E tool | patrol |
|
||||||
|
| Rules loaded | [list from rules_to_load] |
|
||||||
|
| Existing modules | auth, home, products |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Deep Research
|
||||||
|
|
||||||
|
**Before touching any file, print the full research output.**
|
||||||
|
|
||||||
|
1. From the FEATURE_REGISTRY, enumerate all `pub_packages` for the detected type. For each, determine the correct version compatible with current Flutter stable (`flutter --version`). Print the full `pubspec.yaml` additions.
|
||||||
|
|
||||||
|
2. Build the complete file manifest, adapting to `Clean Architecture`:
|
||||||
|
|
||||||
|
**For Clean Architecture (clean):**
|
||||||
|
- `clean`: expand each feature layer explicitly → `domain/entities/`, `domain/repositories/`, `domain/usecases/`, `data/models/`, `data/datasources/`, `data/repositories/`, `presentation/`
|
||||||
|
- `feature_first`: `[feature]/[feature]_screen.dart`, `[feature]_provider.dart` or `[feature]_bloc.dart`, `[feature]_repository.dart`, `[feature]_model.dart`, `widgets/`
|
||||||
|
- `mvc`: `[feature]/model/`, `[feature]/view/`, `[feature]/controller/`
|
||||||
|
|
||||||
|
**State management file naming (BLoC / Cubit / bloc):**
|
||||||
|
- `bloc`: `[feature]_bloc.dart`, `[feature]_event.dart`, `[feature]_state.dart`
|
||||||
|
- `riverpod`: `[feature]_provider.dart`, `[feature]_notifier.dart`
|
||||||
|
- `getx`: `[feature]_controller.dart`, `[feature]_binding.dart`
|
||||||
|
|
||||||
|
3. List all modified files from `files_to_touch.modified` — show exactly what change goes in each file.
|
||||||
|
|
||||||
|
4. List all external service configuration required per platform (from `external_setup`). Only show platforms present in `ios, android`.
|
||||||
|
|
||||||
|
5. Print research results in this format:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Research Results
|
||||||
|
|
||||||
|
### Packages to add to pubspec.yaml
|
||||||
|
- [package_name]: ^[version]
|
||||||
|
|
||||||
|
### Files to create (Clean Architecture / BLoC / Cubit)
|
||||||
|
lib/features/[name]/... (full list)
|
||||||
|
|
||||||
|
### Files to modify
|
||||||
|
[file_path] — [what changes]
|
||||||
|
|
||||||
|
### External services requiring configuration
|
||||||
|
- [Service] ([platform]) — [what to do]
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not touch any file until the user has seen this output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: TDD Implementation
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:test-driven-development`**
|
||||||
|
|
||||||
|
Follow Red → Green → Refactor strictly. Show actual terminal output at each step.
|
||||||
|
|
||||||
|
### Step 3a — Red: Write all failing tests first
|
||||||
|
|
||||||
|
1. Mirror the feature directory under `test/features/[feature]/`.
|
||||||
|
2. Write unit tests for **every scenario** in `FEATURE_REGISTRY.unit_test_scenarios` for the detected type.
|
||||||
|
3. Write widget tests for **every scenario** in `FEATURE_REGISTRY.widget_test_scenarios`.
|
||||||
|
4. Use `mocktail` for all dependencies.
|
||||||
|
5. Test naming convention: `'given [precondition], when [action], then [expected outcome]'`
|
||||||
|
6. Use the state management test pattern for **BLoC / Cubit**:
|
||||||
|
```
|
||||||
|
blocTest<MyCubit, MyState>(description, build: () => MyCubit(), act: (c) => c.method(), expect: () => [MyState()])
|
||||||
|
```
|
||||||
|
7. Run and confirm red:
|
||||||
|
```
|
||||||
|
flutter test test/features/[feature]/ --no-pub
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
If failures are unclear or non-deterministic after one iteration, use **`/debug`** with full pasted output before guessing fixes.
|
||||||
|
|
||||||
|
### Step 3b — Green: Implement
|
||||||
|
|
||||||
|
1. Create domain entities and repository interfaces.
|
||||||
|
2. Create data-layer implementations wiring to `Firebase`.
|
||||||
|
3. Create presentation layer using **BLoC / Cubit** patterns.
|
||||||
|
4. Register in DI container — follow `- presentation/ MUST NOT import from data/
|
||||||
|
- domain/ MUST NOT import from data/ or presentation/
|
||||||
|
- data/ CAN import from domain/ (implements interfaces)
|
||||||
|
- Use dependency injection to invert data → domain dependency`.
|
||||||
|
5. Wire the route in `GoRouter` router.
|
||||||
|
6. If `freezed` is not `none`: run `dart run build_runner build --delete-conflicting-outputs` after adding models.
|
||||||
|
7. Run tests and confirm green:
|
||||||
|
```
|
||||||
|
flutter test test/features/[feature]/ --no-pub
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
|
||||||
|
### Step 3c — Refactor
|
||||||
|
|
||||||
|
1. Review for duplication, naming, and layer boundary violations per `.cursor/rules/flutter-core.mdc`.
|
||||||
|
2. Run and confirm clean:
|
||||||
|
```
|
||||||
|
flutter analyze
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
3. Run tests once more to confirm still green.
|
||||||
|
|
||||||
|
If output stays red or errors are ambiguous after two focused attempts, stop and use **`/debug`** with the full failing command and log before changing more code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Integration Test Generation
|
||||||
|
|
||||||
|
**Generate `integration_test/[feature_type]/` before asking the user to run anything.**
|
||||||
|
|
||||||
|
### Integration test file structure
|
||||||
|
|
||||||
|
Each test file follows this template:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// integration_test/[feature]/[scenario]_test.dart
|
||||||
|
// Generated by /build — TestApp
|
||||||
|
// Feature: [feature_type] | Scenario: [scenario_name]
|
||||||
|
// Run with: flutter test integration_test/[feature]/[scenario]_test.dart -d <device_id>
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:com.test.testapp/main.dart' as app;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('[FeatureType] — [Scenario Name]', () {
|
||||||
|
setUp(() async {
|
||||||
|
// Seed state / configure mocks / reset storage
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
// Cleanup
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'given [precondition], when [action], then [expected outcome]',
|
||||||
|
(tester) async {
|
||||||
|
app.main();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// test body
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**For `patrol` (patrol):** swap `flutter_test` imports for `package:patrol` and use Patrol's `$` NativeAutomator selector syntax.
|
||||||
|
|
||||||
|
### Files to generate
|
||||||
|
|
||||||
|
Create one file per logical scenario cluster from `FEATURE_REGISTRY.integration_test_scenarios`.
|
||||||
|
Also generate `integration_test/[feature]/README.md` with the full test matrix and run commands.
|
||||||
|
|
||||||
|
### PAUSE GATE — user must run on device
|
||||||
|
|
||||||
|
After generating all files, print this table and **stop**. Wait for the user to paste device output before Phase 6.
|
||||||
|
|
||||||
|
```
|
||||||
|
## ACTION REQUIRED — Run Integration Tests on Real Device
|
||||||
|
|
||||||
|
| File | Covers | Requires Hardware |
|
||||||
|
|------|--------|------------------|
|
||||||
|
| integration_test/[feature]/[scenario]_test.dart | [what it covers] | Yes/No |
|
||||||
|
...
|
||||||
|
|
||||||
|
Run on iOS:
|
||||||
|
flutter test integration_test/[feature]/ -d <your_ios_device_id>
|
||||||
|
|
||||||
|
Run on Android:
|
||||||
|
flutter test integration_test/[feature]/ -d <your_android_device_id>
|
||||||
|
|
||||||
|
Paste the output here to continue.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: External Setup Checklist
|
||||||
|
|
||||||
|
**Print before asking the user to verify anything. Group by service/platform. Only show sections for platforms in `ios, android`.**
|
||||||
|
|
||||||
|
### Format for console/service steps (numbered list):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Firebase (backend: Firebase)
|
||||||
|
- [ ] 1. Open Firebase Console → [exact menu path]
|
||||||
|
- [ ] 2. [Specific action]
|
||||||
|
- [ ] 3. Download updated config file → place at [exact path]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Format for native platform steps (table):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### iOS Setup
|
||||||
|
| Step | Where | What | How to Verify |
|
||||||
|
|------|-------|------|---------------|
|
||||||
|
| [Step name] | [Xcode location / file path] | [Exact change] | [Verification method] |
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Android Setup
|
||||||
|
- [ ] 1. Open `android/app/src/main/AndroidManifest.xml`
|
||||||
|
- Add: `<uses-permission android:name="[permission]"/>`
|
||||||
|
- [ ] 2. [Next step with exact value]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flavor scoping note (flavors: dev, prod)
|
||||||
|
|
||||||
|
Config files and API keys must be scoped per flavor. Never place production keys in `dev` flavor files. Follow the pattern established in `.cursor/rules/` for flavor-based configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Verification Gate
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:verification-before-completion`**
|
||||||
|
|
||||||
|
**Do not claim completion without pasting real output for every item below.**
|
||||||
|
|
||||||
|
```
|
||||||
|
VERIFICATION REQUIRED — paste real output for each:
|
||||||
|
|
||||||
|
[ ] flutter test test/features/[feature]/ --no-pub
|
||||||
|
Required: "All N tests passed."
|
||||||
|
|
||||||
|
[ ] flutter analyze
|
||||||
|
Required: "No issues found!"
|
||||||
|
|
||||||
|
[ ] Integration test device output (from Phase 4 pause gate)
|
||||||
|
Required: actual device test output
|
||||||
|
|
||||||
|
[ ] lefthook run pre-commit
|
||||||
|
Required: all hooks passed
|
||||||
|
```
|
||||||
|
|
||||||
|
If any check fails: invoke `superpowers:systematic-debugging` to diagnose before retrying.
|
||||||
|
For noisy errors or weak reproduction, run **`/debug`** and fill the BugReport skeleton with logs before retrying.
|
||||||
|
For small, isolated changes, **`/verify`** is enough to re-check tests and analyze without re-reading this whole skill.
|
||||||
|
Do not proceed to Phase 7 until all four checks are green.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: PR Preparation
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:finishing-a-development-branch`**
|
||||||
|
|
||||||
|
1. **Commit message** (conventional commits format):
|
||||||
|
```
|
||||||
|
feat([feature_type]): implement [feature] end-to-end
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **PR description template:**
|
||||||
|
```markdown
|
||||||
|
## What
|
||||||
|
[One sentence: what feature was implemented]
|
||||||
|
|
||||||
|
## Test coverage
|
||||||
|
- Unit/widget tests: N passing
|
||||||
|
- Integration test matrix:
|
||||||
|
| Scenario | iOS | Android |
|
||||||
|
|----------|-----|---------|
|
||||||
|
| [scenario] | ✅ | ✅ |
|
||||||
|
|
||||||
|
## External setup completed
|
||||||
|
- [ ] [Service 1 setup]
|
||||||
|
- [ ] [Service 2 setup]
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
[Any follow-up TODOs or known limitations]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **CI/CD advice for GitHub Actions (github_actions):**
|
||||||
|
Ensure workflow secrets are scoped per flavor (dev, prod). Never expose production keys in dev environment secrets. Verify the CI workflow runs `flutter test` and `flutter analyze` before deploy steps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules applied every phase
|
||||||
|
|
||||||
|
Always active — read before writing any code:
|
||||||
|
- `.cursor/rules/flutter-core.mdc`
|
||||||
|
- `.cursor/rules/security-standards.mdc`
|
||||||
|
- `.cursor/rules/project-context.mdc`
|
||||||
|
- Feature-type-specific rules from `FEATURE_REGISTRY.rules_to_load`
|
||||||
|
|
||||||
|
Architecture import rules for **Clean Architecture**:
|
||||||
|
- presentation/ MUST NOT import from data/
|
||||||
|
- domain/ MUST NOT import from data/ or presentation/
|
||||||
|
- data/ CAN import from domain/ (implements interfaces)
|
||||||
|
- Use dependency injection to invert data → domain dependency
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code generation notes
|
||||||
|
|
||||||
|
**Codegen tools configured: freezed**
|
||||||
|
|
||||||
|
After adding any new model or injectable class, run:
|
||||||
|
```
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit generated files (`.g.dart`, `.freezed.dart`, `injection.config.dart`) — do not gitignore them.
|
||||||
|
|
||||||
|
**Template version:** 1.0.5
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
name: Debug issue
|
||||||
|
description: Triage failing tests, analyze, CI, or runtime errors with an evidence-first BugReport. Use /debug and paste logs; invokes systematic-debugging before fixes. Stack BLoC / Cubit / Clean Architecture / Firebase / ios, android.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Debug — TestApp
|
||||||
|
|
||||||
|
Triage failures (tests, CI, runtime, or build) **without** jumping to fixes. Stay in hypothesis-and-evidence mode until root cause is stated.
|
||||||
|
|
||||||
|
**Stack context:** **BLoC / Cubit** / **Clean Architecture** / **Firebase** / platforms: ios, android. Flavors: dev, prod. Codegen: freezed.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/debug <what failed — paste error output, command, or symptom>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/debug flutter test fails on auth_cubit_test — paste output`
|
||||||
|
- `/debug CI analyze step — paste log excerpt`
|
||||||
|
- `/debug app crashes on cold start after last change`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 — Normalize the report (BugReport)
|
||||||
|
|
||||||
|
Emit this skeleton **before** deep analysis. If the user already pasted logs, map them into the fields instead of re-asking.
|
||||||
|
|
||||||
|
| Field | Content |
|
||||||
|
|-------|---------|
|
||||||
|
| **Summary** | One line: what broke |
|
||||||
|
| **Expected** | What should happen |
|
||||||
|
| **Actual** | What happened (symptom + error text) |
|
||||||
|
| **Repro steps** | Numbered, minimal |
|
||||||
|
| **Scope / files touched** | Paths or PR slice |
|
||||||
|
| **Environment** | OS, Flutter/Dart version if known, device vs simulator, flavor |
|
||||||
|
| **Evidence** | Pasted command output, stack trace, or screenshot notes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Evidence checklist (Flutter-aware)
|
||||||
|
|
||||||
|
Gather or request **concrete** evidence. Do not guess versions or config.
|
||||||
|
|
||||||
|
1. **`flutter doctor -v`** — paste output when environment is unknown or iOS/Android toolchain errors appear.
|
||||||
|
2. **Failing command** — full invocation + **verbatim** tail of output (e.g. `flutter test …`, `dart test …`, `flutter analyze`).
|
||||||
|
3. **`flutter analyze`** — if not already in the failure log, run or ask the user to run and paste.
|
||||||
|
4. **Flavors** — this project uses: **dev, prod**. Confirm which flavor was active if the failure is env-specific.
|
||||||
|
5. **Platforms** — **ios, android**. Narrow reproduction to the platform that failed when relevant.
|
||||||
|
6. **Codegen** — tools: **freezed**. When this is not `none`, remind to run `dart run build_runner build --delete-conflicting-outputs` after generated files changed, and to align with `.cursor/hooks/` / `lefthook run pre-commit` when hooks are present.
|
||||||
|
7. **Testing depth** — **unit_widget**; E2E tool: **patrol**. Match the failure to the right layer (unit vs widget vs integration).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Root cause (no code yet)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:systematic-debugging`**
|
||||||
|
|
||||||
|
Produce **one paragraph**: hypothesis tied to **specific lines** in the pasted evidence. Mark confidence (high / medium / low). **No code changes** in this phase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Fix (only after Phase 2)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:systematic-debugging`** again while iterating fixes.
|
||||||
|
|
||||||
|
When proposing changes:
|
||||||
|
|
||||||
|
- Respect architecture boundaries for **Clean Architecture**:
|
||||||
|
- presentation/ MUST NOT import from data/
|
||||||
|
- domain/ MUST NOT import from data/ or presentation/
|
||||||
|
- data/ CAN import from domain/ (implements interfaces)
|
||||||
|
- Use dependency injection to invert data → domain dependency
|
||||||
|
- Always consider: `.cursor/rules/flutter-core.mdc`, `.cursor/rules/security-standards.mdc`, `.cursor/rules/project-context.mdc`, and state-management rules for **BLoC / Cubit**.
|
||||||
|
|
||||||
|
After each fix attempt, re-run the **same** failing command and paste new output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACTION REQUIRED
|
||||||
|
|
||||||
|
If evidence is missing, **stop** and print:
|
||||||
|
|
||||||
|
1. Exact commands to run (copy-paste ready).
|
||||||
|
2. What to paste back (full error blocks, not summaries).
|
||||||
|
3. If the user cannot run commands: state assumptions explicitly and set confidence to **low**.
|
||||||
|
|
||||||
|
**Template version:** 1.0.5
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
name: Explain code
|
||||||
|
description: Explain what code does without editing it. Use /explain with a path or symbol; covers BLoC / Cubit state, Firebase I/O, routing GoRouter, and failure modes. Ask when facts are not in the repo.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Explain — TestApp
|
||||||
|
|
||||||
|
Explain **what the code is doing** — **do not** change production code unless the user explicitly asks for a fix.
|
||||||
|
|
||||||
|
**Stack:** **BLoC / Cubit** / **Clean Architecture** / **GoRouter** / **Firebase** / platforms: ios, android. Package: `com.test.testapp`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/explain <file path, widget name, class, or symbol>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/explain lib/features/cart/cart_cubit.dart`
|
||||||
|
- `/explain how checkout routes after payment`
|
||||||
|
- `/explain CartPage build method`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output format (use these headings in order)
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
One short paragraph: why this code exists in the product context (**TestApp** — Test app for golden tests).
|
||||||
|
|
||||||
|
### Public API
|
||||||
|
|
||||||
|
Surface area: public classes/methods, constructors, and what callers are expected to pass. Note codegen involvement when **freezed** is not `none`.
|
||||||
|
|
||||||
|
### Call flow
|
||||||
|
|
||||||
|
Ordered steps from entry point (e.g. widget `build`, route handler, Bloc `on<Event>`, Riverpod `build`, GetX controller lifecycle) through collaborators. Tie navigation to **GoRouter** where relevant.
|
||||||
|
|
||||||
|
### State and side effects
|
||||||
|
|
||||||
|
How state is held and updated for **BLoC / Cubit** (bloc). Mention async work, listeners, and disposal. Reference **- presentation/ MUST NOT import from data/
|
||||||
|
- domain/ MUST NOT import from data/ or presentation/
|
||||||
|
- data/ CAN import from domain/ (implements interfaces)
|
||||||
|
- Use dependency injection to invert data → domain dependency** if layering is unclear.
|
||||||
|
|
||||||
|
### I/O and backends
|
||||||
|
|
||||||
|
Network, local storage, or platform channels touching **Firebase** (and **Firebase Auth** where auth applies: firebase_auth). Do **not** invent API shapes not visible in the repo.
|
||||||
|
|
||||||
|
### Failure modes
|
||||||
|
|
||||||
|
What can go wrong: null paths, error states, race conditions, missing permissions on ios, android, auth edge cases.
|
||||||
|
|
||||||
|
### Suggested tests
|
||||||
|
|
||||||
|
Ideas aligned with **unit_widget** and **patrol**; for **BLoC / Cubit**, prefer patterns like:
|
||||||
|
|
||||||
|
```
|
||||||
|
blocTest<MyCubit, MyState>(description, build: () => MyCubit(), act: (c) => c.method(), expect: () => [MyState()])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unknowns — questions for you
|
||||||
|
|
||||||
|
If behavior depends on runtime config, native projects, remote API contracts, or secrets not in tree: **stop** and list **specific** questions. Do not fabricate facts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- **Project brief:** `project-brief.yaml` — feature modules: auth, home, products; special features: .
|
||||||
|
- **Scale:** medium; i18n locales: en (when explaining localization).
|
||||||
|
- **Design:** none; Figma URL: .
|
||||||
|
- **API docs format:** none (path: ).
|
||||||
|
- **Related repos (if any):**
|
||||||
|
|
||||||
|
_No Git repository URLs listed._ Add entries under `references.repos` in project-brief.yaml when other repos are part of the product context.
|
||||||
|
|
||||||
|
- **Local paths (if any):**
|
||||||
|
|
||||||
|
_No local paths listed._ Add monorepo packages or sibling folders under `references.local_paths` in project-brief.yaml when relevant.
|
||||||
|
|
||||||
|
**Template version:** 1.0.5
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
name: Verify change
|
||||||
|
description: Post-change or pre-PR verification without the full /build lifecycle. Use /verify with optional scope; respects testing depth unit_widget and E2E patrol; no success claims without pasted outputs.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Verify — TestApp
|
||||||
|
|
||||||
|
Run a **focused** verification gate after a change or before a small PR — without replaying the entire `/build` document.
|
||||||
|
|
||||||
|
**Stack:** **BLoC / Cubit** / **Clean Architecture** / **Firebase** / ios, android. **Testing depth:** unit_widget. **E2E tool:** patrol. **Codegen:** freezed.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/verify [optional scope: paths, “small PR”, or feature name]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/verify lib/features/auth/`
|
||||||
|
- `/verify small PR before push`
|
||||||
|
- `/verify after dependency bump`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist (adapt to testing depth)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:verification-before-completion`**
|
||||||
|
|
||||||
|
**Do not claim success** until the user (or you, in a trusted environment) has pasted **real** output for each applicable item below.
|
||||||
|
|
||||||
|
### Always (typical Flutter repo)
|
||||||
|
|
||||||
|
- [ ] **Unit / widget tests** for touched code — e.g. `flutter test <path> --no-pub` or full suite as appropriate.
|
||||||
|
**Required in paste:** a line showing tests passed (or the exact failure to fix).
|
||||||
|
- [ ] **`flutter analyze`**
|
||||||
|
**Required in paste:** `No issues found!` or the analyzer errors to address.
|
||||||
|
|
||||||
|
### When `testing.depth` is `full` or `e2e` (this brief: **unit_widget**)
|
||||||
|
|
||||||
|
- [ ] **Integration / E2E** — use **patrol** patterns from `.cursor/rules/` (e.g. Patrol). Run on a device or emulator when scenarios require it; paste run output.
|
||||||
|
|
||||||
|
### When `testing.depth` is `unit_widget` only
|
||||||
|
|
||||||
|
- [ ] **Integration / device E2E** — *not* mandatory unless the change touches integration-only surfaces; if skipped, say so explicitly in the paste.
|
||||||
|
|
||||||
|
### Hooks and codegen
|
||||||
|
|
||||||
|
- [ ] **`lefthook run pre-commit`** — run when the repo has Lefthook configured (especially when **freezed** is not `none`). Paste hook summary.
|
||||||
|
If hooks are not set up for this workspace, write *“skipped — hooks not configured”* instead of failing silently.
|
||||||
|
|
||||||
|
### CI alignment
|
||||||
|
|
||||||
|
- [ ] For **GitHub Actions** (github_actions): confirm the same commands the pipeline runs (analyze + tests) are green locally. Note flavor-scoped secrets for **dev, prod**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACTION REQUIRED — paste block
|
||||||
|
|
||||||
|
Print this table and wait for pasted output before declaring done:
|
||||||
|
|
||||||
|
```
|
||||||
|
VERIFICATION — paste real output for each line you executed:
|
||||||
|
|
||||||
|
[ ] flutter test … → (paste: pass count or failures)
|
||||||
|
[ ] flutter analyze → (paste: no issues or errors)
|
||||||
|
[ ] integration / e2e (if depth is full/e2e or change requires it) →
|
||||||
|
[ ] lefthook run pre-commit → (paste or "skipped — not configured")
|
||||||
|
```
|
||||||
|
|
||||||
|
If anything fails: switch to **`/debug`** with the failing log before guessing.
|
||||||
|
|
||||||
|
**Template version:** 1.0.5
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — LegacyApp
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# LegacyApp
|
||||||
|
|
||||||
|
Repo-level notes for AI assistants. Authoritative stack and conventions are in `project-brief.yaml` and `.cursor/` (regenerate with `cursor_gen` from the project root).
|
||||||
|
|
||||||
|
- **Package:** `com.test.legacy`
|
||||||
|
- **Onboarding:** `.cursor/ONBOARDING.md` — layout, slash commands → skills, MCP opt-in, audits
|
||||||
|
- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` (see `.cursor/commands/*.md`)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# LegacyApp — generated by cursor_gen; adjust commands to your repo
|
||||||
|
# Optional: run `bash tool/cursor_audit.sh` after changing features.modules or rule files.
|
||||||
|
pre-commit:
|
||||||
|
commands:
|
||||||
|
flutter-analyze:
|
||||||
|
run: flutter analyze
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# LegacyApp — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"filesystem": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-filesystem",
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"description": "Read/write project files under the workspace root"
|
||||||
|
},
|
||||||
|
"flutter-docs": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-fetch"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"BASE_URL": "https://api.flutter.dev/flutter"
|
||||||
|
},
|
||||||
|
"description": "Flutter API documentation lookup"
|
||||||
|
},
|
||||||
|
"dart-pub": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-fetch"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"BASE_URL": "https://pub.dev/api"
|
||||||
|
},
|
||||||
|
"description": "Pub.dev package metadata and versions"
|
||||||
|
},
|
||||||
|
"github": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-github"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
|
||||||
|
},
|
||||||
|
"description": "GitHub issues and PR context (requires GITHUB_TOKEN)"
|
||||||
|
},
|
||||||
|
"openapi-ref": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-fetch"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"OPENAPI_SPEC_URL": "${OPENAPI_SPEC_URL}"
|
||||||
|
},
|
||||||
|
"description": "Optional fetch MCP — point OPENAPI_SPEC_URL at a hosted spec or file:// URL you expose locally"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for LegacyApp
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — LegacyApp
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **dev, prod**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — patrol on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **Codemagic** (`codemagic`)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module auth — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/auth/**", "test/**/auth/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Auth
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `auth` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module dashboard — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/dashboard/**", "test/**/dashboard/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Dashboard
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `dashboard` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+7
-5
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Core Flutter conventions for LegacyApp — always applied"
|
description: "Core Flutter conventions for LegacyApp"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Flutter Core Standards — LegacyApp
|
# Flutter Core Standards — LegacyApp
|
||||||
@@ -29,9 +30,10 @@ alwaysApply: true
|
|||||||
- Private members: `_camelCase`
|
- Private members: `_camelCase`
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
- Order: dart: → package: → relative
|
### Imports (default)
|
||||||
- Use relative imports within a feature; absolute for cross-feature
|
- Order: `dart:` → `package:` → relative
|
||||||
- Never import a feature's internal files from outside that feature
|
- Relative imports are allowed **within** the same feature directory; use `package:` imports for cross-feature code
|
||||||
|
- Never import another feature's internals from outside that feature
|
||||||
|
|
||||||
## Code quality
|
## Code quality
|
||||||
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Project context for LegacyApp — always applied"
|
description: "Stack summary and product context for LegacyApp"
|
||||||
alwaysApply: true
|
globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project Context — LegacyApp
|
# Project Context — LegacyApp
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
description: How Cursor rules in this repository must be written and maintained.
|
||||||
|
globs: [".cursor/rules/**/*.mdc"]
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rule authoring — LegacyApp
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Rules are version-controlled contracts for AI assistants. Poor rules waste context and silently steer every edit.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- One focused concern per file; split broad topics instead of one mega-rule
|
||||||
|
- Every rule MUST have a clear `description` in frontmatter (one sentence)
|
||||||
|
- Prefer `alwaysApply: false` with **narrow** `globs` for domain rules — reserve `alwaysApply: true` for meta and safety
|
||||||
|
- `globs` must be as specific as possible — never `["**/*"]` unless tooling requires it
|
||||||
|
- Code samples in rules MUST be valid for this project (Dart/Flutter/YAML as appropriate)
|
||||||
|
- Deprecated guidance is removed, not left commented out
|
||||||
|
- Each substantive rule includes **Context** (why), **Constraints** (must/must not), and where helpful **Patterns** / **Anti-patterns**
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Domain rules (testing, l10n, a feature) with `alwaysApply: true` — burns context
|
||||||
|
- Rules with no concrete examples when the topic is code-facing
|
||||||
|
- Stale feature rules after modules are removed — run `tool/cursor_audit.sh` periodically
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "UI/UX standards for LegacyApp — always applied"
|
description: "UI/UX standards for LegacyApp"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# UI / UX Standards — LegacyApp
|
# UI / UX Standards — LegacyApp
|
||||||
|
|||||||
@@ -0,0 +1,577 @@
|
|||||||
|
# Build — LegacyApp
|
||||||
|
|
||||||
|
Implements any feature end-to-end: deep research → TDD → integration tests → external setup checklist → verified PR.
|
||||||
|
Stack: **GetX** / **MVC** / **REST API** / ios, android.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/build <free-text description of what to implement>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `/build implement notification module end-to-end`
|
||||||
|
- `/build add biometric auth`
|
||||||
|
- `/build integrate Stripe payments`
|
||||||
|
- `/build add deep linking for shared product pages`
|
||||||
|
- `/build implement analytics event tracking`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FEATURE_REGISTRY
|
||||||
|
|
||||||
|
The AI reads this registry to classify the request and load the right research, test scenarios, and setup steps.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
feature_registry:
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
keywords: [notification, push, fcm, apns, alert, badge, silent push,
|
||||||
|
remote notification, local notification, firebase messaging]
|
||||||
|
pub_packages: [firebase_messaging, flutter_local_notifications, firebase_core]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- parse notification payload to domain model
|
||||||
|
- FCM token generation and storage
|
||||||
|
- token refresh triggers re-registration
|
||||||
|
- notification permission denied returns graceful fallback
|
||||||
|
- notification service initialisation is idempotent
|
||||||
|
widget_test_scenarios:
|
||||||
|
- notification banner renders correct title and body
|
||||||
|
- tap on notification navigates to correct route
|
||||||
|
- badge count updates on new message
|
||||||
|
integration_test_scenarios:
|
||||||
|
- foreground notification receipt and display
|
||||||
|
- background notification receipt (app backgrounded, not killed)
|
||||||
|
- killed-state notification receipt (cold launch from notification tap)
|
||||||
|
- tap-to-open in foreground state routes correctly
|
||||||
|
- tap-to-open in background state routes correctly
|
||||||
|
- tap-to-open in killed state routes correctly
|
||||||
|
- notification payload parsing end-to-end
|
||||||
|
- deep link routing from notification data field
|
||||||
|
- multiple simultaneous notifications (ordering and dedup)
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Cloud Messaging, download google-services.json, download GoogleService-Info.plist]
|
||||||
|
ios: [Push Notifications capability, Background Modes remote notifications, APNs .p8 key upload to Firebase]
|
||||||
|
android: [POST_NOTIFICATIONS permission, RECEIVE_BOOT_COMPLETED permission, google-services plugin]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/notifications/, integration_test/notifications/]
|
||||||
|
modified: [lib/main.dart, android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, ios/Runner/AppDelegate.swift, pubspec.yaml]
|
||||||
|
|
||||||
|
auth:
|
||||||
|
keywords: [auth, authentication, login, sign in, sign out, logout,
|
||||||
|
biometric, face id, touch id, fingerprint, oauth, jwt,
|
||||||
|
session, token refresh, social login, google sign-in]
|
||||||
|
pub_packages: [firebase_auth, supabase_flutter, local_auth, flutter_secure_storage, google_sign_in]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- login success stores token securely
|
||||||
|
- login failure (wrong password) returns typed error
|
||||||
|
- login failure (network) returns typed error
|
||||||
|
- token refresh succeeds and updates stored token
|
||||||
|
- token refresh failure triggers logout
|
||||||
|
- biometric auth success grants access
|
||||||
|
- biometric auth failure falls back to password
|
||||||
|
- biometric not enrolled returns correct error state
|
||||||
|
- logout clears all stored credentials
|
||||||
|
- session persistence: token loaded on cold start
|
||||||
|
widget_test_scenarios:
|
||||||
|
- login form renders correctly
|
||||||
|
- validation errors shown inline
|
||||||
|
- loading state disables submit button
|
||||||
|
- biometric prompt shown when available
|
||||||
|
integration_test_scenarios:
|
||||||
|
- complete login flow (email and password)
|
||||||
|
- login then logout then login again
|
||||||
|
- invalid credentials shows error and stays on login
|
||||||
|
- biometric login flow on enrolled device
|
||||||
|
- biometric fallback to password
|
||||||
|
- token refresh in background while user navigates
|
||||||
|
- cold start with stored valid session skips login
|
||||||
|
- cold start with expired session redirects to login
|
||||||
|
- social auth OAuth redirect and return
|
||||||
|
- deep link into protected route redirects to login then back
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Email/Password provider, Enable Google provider, download updated config files]
|
||||||
|
ios: [NSFaceIDUsageDescription in Info.plist, LocalAuthentication.framework in Xcode]
|
||||||
|
android: [USE_BIOMETRIC permission, USE_FINGERPRINT permission, minSdkVersion 23 for biometric]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/auth/, integration_test/auth/]
|
||||||
|
modified: [lib/main.dart, lib/core/di/injection.dart, pubspec.yaml]
|
||||||
|
|
||||||
|
payments:
|
||||||
|
keywords: [payment, stripe, in-app purchase, iap, checkout, subscription,
|
||||||
|
billing, apple pay, google pay, card, transaction, refund, revenue cat]
|
||||||
|
pub_packages: [flutter_stripe, purchases_flutter, in_app_purchase]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- checkout request builds correct PaymentIntent params
|
||||||
|
- payment success updates order state
|
||||||
|
- payment failure (card declined) returns typed error
|
||||||
|
- webhook event parsed to domain model
|
||||||
|
- refund request constructs correct API call
|
||||||
|
- subscription status checked on app resume
|
||||||
|
widget_test_scenarios:
|
||||||
|
- checkout form renders with correct amount
|
||||||
|
- payment loading state shown during processing
|
||||||
|
- success confirmation screen renders
|
||||||
|
- error state with retry option
|
||||||
|
integration_test_scenarios:
|
||||||
|
- end-to-end checkout with test card (Stripe test mode)
|
||||||
|
- checkout with 3D Secure challenge
|
||||||
|
- card declined shows error then retry succeeds
|
||||||
|
- Apple Pay / Google Pay sheet appears (device capability check)
|
||||||
|
- subscription purchase and entitlement unlock
|
||||||
|
- subscription restore flow
|
||||||
|
- refund flow from order history
|
||||||
|
external_setup:
|
||||||
|
stripe: [publishable and secret keys in flavor .env files, webhook endpoint in Stripe Dashboard, Apple Pay domain registration]
|
||||||
|
ios: [In-App Purchase capability, Apple Pay capability and merchant identifier, com.apple.developer.in-app-payments entitlement]
|
||||||
|
android: [Google Pay Console setup, BILLING permission for in_app_purchase]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/payments/, integration_test/payments/]
|
||||||
|
modified: [ios/Runner/Runner.entitlements, android/app/src/main/AndroidManifest.xml, pubspec.yaml]
|
||||||
|
|
||||||
|
deep_links:
|
||||||
|
keywords: [deep link, deep linking, universal link, app link, deferred deep link,
|
||||||
|
dynamic link, branch, uri scheme, custom scheme, url scheme]
|
||||||
|
pub_packages: [go_router, app_links, uni_links]
|
||||||
|
rules_to_load: [platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- URI parsed to correct route and parameters
|
||||||
|
- unknown URI falls back to home
|
||||||
|
- authenticated-only route redirects to login when unauthenticated
|
||||||
|
- deep link preserves query parameters
|
||||||
|
widget_test_scenarios:
|
||||||
|
- navigation guard redirects unauthenticated deep link
|
||||||
|
integration_test_scenarios:
|
||||||
|
- cold start from Universal Link routes to correct screen
|
||||||
|
- cold start from custom URI scheme routes correctly
|
||||||
|
- backgrounded app receives deep link and navigates
|
||||||
|
- foreground app receives deep link and navigates
|
||||||
|
- deep link to authenticated route redirects to login then original destination
|
||||||
|
- deep link with path parameters loads correct content
|
||||||
|
- invalid or malformed deep link shows 404 screen
|
||||||
|
external_setup:
|
||||||
|
ios: [Associated Domains capability applinks:yourdomain.com, host apple-app-site-association file at /.well-known/]
|
||||||
|
android: [intent-filter with android:autoVerify=true in AndroidManifest.xml, host assetlinks.json at /.well-known/]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/core/routing/deep_link_handler.dart, integration_test/deep_links/]
|
||||||
|
modified: [lib/core/routing/router.dart, android/app/src/main/AndroidManifest.xml, ios/Runner/Runner.entitlements, ios/Runner/Info.plist]
|
||||||
|
|
||||||
|
analytics:
|
||||||
|
keywords: [analytics, tracking, event, mixpanel, amplitude, firebase analytics,
|
||||||
|
segment, posthog, screen view, funnel, cohort]
|
||||||
|
pub_packages: [firebase_analytics, mixpanel_flutter, amplitude_flutter, posthog_flutter]
|
||||||
|
rules_to_load: [security-standards.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- analytics service logs correct event name and params
|
||||||
|
- PII fields stripped before event sent
|
||||||
|
- analytics disabled in dev flavor
|
||||||
|
- screen name logged on navigation
|
||||||
|
widget_test_scenarios:
|
||||||
|
- RouteObserver triggers screen_view event
|
||||||
|
integration_test_scenarios:
|
||||||
|
- user action triggers expected event (verify via debug view)
|
||||||
|
- screen transitions log screen_view with correct names
|
||||||
|
- opt-out disables all tracking
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Analytics in Firebase Console, enable DebugView for local testing, configure conversion events]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/core/analytics/analytics_service.dart, lib/core/analytics/analytics_events.dart, integration_test/analytics/]
|
||||||
|
modified: [lib/main.dart, pubspec.yaml]
|
||||||
|
|
||||||
|
storage:
|
||||||
|
keywords: [storage, file upload, download, cloud storage, firebase storage,
|
||||||
|
supabase storage, s3, image upload, document, file picker,
|
||||||
|
offline, cache, hive, isar, objectbox, sqflite, drift]
|
||||||
|
pub_packages: [firebase_storage, supabase_flutter, hive_flutter, isar, drift, file_picker, image_picker, path_provider]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- upload returns public URL on success
|
||||||
|
- upload failure returns typed error
|
||||||
|
- local cache read before remote fetch (offline-first)
|
||||||
|
- cache invalidation on TTL expiry
|
||||||
|
- large file upload uses resumable upload
|
||||||
|
widget_test_scenarios:
|
||||||
|
- file picker button triggers picker
|
||||||
|
- upload progress indicator shown
|
||||||
|
- image preview renders after selection
|
||||||
|
integration_test_scenarios:
|
||||||
|
- end-to-end file upload and retrieval
|
||||||
|
- offline mode: local cache serves data
|
||||||
|
- background upload completes when connectivity restored
|
||||||
|
- file size limit enforced
|
||||||
|
external_setup:
|
||||||
|
firebase: [Enable Firebase Storage, configure Storage security rules, set CORS policy for web if applicable]
|
||||||
|
ios: [NSPhotoLibraryUsageDescription in Info.plist, NSCameraUsageDescription in Info.plist]
|
||||||
|
android: [READ_EXTERNAL_STORAGE or READ_MEDIA_IMAGES permission]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/storage/, integration_test/storage/]
|
||||||
|
modified: [android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, pubspec.yaml]
|
||||||
|
|
||||||
|
camera_media:
|
||||||
|
keywords: [camera, photo, video, image picker, qr code, barcode scanner,
|
||||||
|
ar, augmented reality, gallery, media, record, capture]
|
||||||
|
pub_packages: [camera, image_picker, mobile_scanner, qr_flutter, image_cropper, video_player]
|
||||||
|
rules_to_load: [security-standards.mdc, platform-ios.mdc, platform-android.mdc]
|
||||||
|
unit_test_scenarios:
|
||||||
|
- QR/barcode parsed to correct domain model
|
||||||
|
- image compressed before upload
|
||||||
|
- camera permission denied returns typed error
|
||||||
|
widget_test_scenarios:
|
||||||
|
- camera preview widget renders
|
||||||
|
- capture button triggers photo take
|
||||||
|
integration_test_scenarios:
|
||||||
|
- camera opens and captures photo on real device
|
||||||
|
- gallery picker selects image and returns
|
||||||
|
- QR scan decodes valid code correctly
|
||||||
|
- permission denied shows correct error UI
|
||||||
|
external_setup:
|
||||||
|
ios: [NSCameraUsageDescription, NSPhotoLibraryUsageDescription, NSMicrophoneUsageDescription for video]
|
||||||
|
android: [CAMERA permission, READ_MEDIA_IMAGES for gallery access]
|
||||||
|
files_to_touch:
|
||||||
|
new: [lib/features/camera/, integration_test/camera/]
|
||||||
|
modified: [android/app/src/main/AndroidManifest.xml, ios/Runner/Info.plist, pubspec.yaml]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Context Loading
|
||||||
|
|
||||||
|
**When the user types `/build <request>`, do the following before writing a single line of code:**
|
||||||
|
|
||||||
|
1. Read `project-brief.yaml` from the repo root. Extract and hold in context:
|
||||||
|
- `stack.state_management` → **getx**
|
||||||
|
- `stack.architecture` → **mvc**
|
||||||
|
- `stack.routing` → **getx_nav**
|
||||||
|
- `stack.backend` → **rest**
|
||||||
|
- `stack.platforms` → **ios, android**
|
||||||
|
- `stack.codegen` → **none**
|
||||||
|
- `testing.depth` → **unit_widget**
|
||||||
|
- `testing.e2e_tool` → **patrol**
|
||||||
|
- `features.modules` (existing features) → **auth, dashboard**
|
||||||
|
- `features.special` → ****
|
||||||
|
- `environments.flavors` → **dev, prod**
|
||||||
|
- `environments.cicd` → **codemagic**
|
||||||
|
|
||||||
|
2. Parse the user's free-text request. Match against `FEATURE_REGISTRY` keywords using longest-match. Multiple types are allowed if the request spans features.
|
||||||
|
|
||||||
|
3. Load `.cursor/rules/` files listed under `rules_to_load` for the matched feature type.
|
||||||
|
|
||||||
|
4. If the user's message contains a URL or GitHub repo reference, fetch and index it for pattern reference.
|
||||||
|
|
||||||
|
5. Check `features.modules` — if the feature already exists in the list, ask: "This feature is already listed in project-brief.yaml. Building additional capability on top of it? (y/n)"
|
||||||
|
|
||||||
|
6. Print the context summary table before proceeding:
|
||||||
|
|
||||||
|
```
|
||||||
|
| Field | Value |
|
||||||
|
|------------------|------------------------------|
|
||||||
|
| Feature type | [detected type] |
|
||||||
|
| State management | GetX |
|
||||||
|
| Architecture | MVC |
|
||||||
|
| Backend | REST API |
|
||||||
|
| Platforms | ios, android |
|
||||||
|
| E2E tool | patrol |
|
||||||
|
| Rules loaded | [list from rules_to_load] |
|
||||||
|
| Existing modules | auth, dashboard |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Deep Research
|
||||||
|
|
||||||
|
**Before touching any file, print the full research output.**
|
||||||
|
|
||||||
|
1. From the FEATURE_REGISTRY, enumerate all `pub_packages` for the detected type. For each, determine the correct version compatible with current Flutter stable (`flutter --version`). Print the full `pubspec.yaml` additions.
|
||||||
|
|
||||||
|
2. Build the complete file manifest, adapting to `MVC`:
|
||||||
|
|
||||||
|
**For MVC (mvc):**
|
||||||
|
- `clean`: expand each feature layer explicitly → `domain/entities/`, `domain/repositories/`, `domain/usecases/`, `data/models/`, `data/datasources/`, `data/repositories/`, `presentation/`
|
||||||
|
- `feature_first`: `[feature]/[feature]_screen.dart`, `[feature]_provider.dart` or `[feature]_bloc.dart`, `[feature]_repository.dart`, `[feature]_model.dart`, `widgets/`
|
||||||
|
- `mvc`: `[feature]/model/`, `[feature]/view/`, `[feature]/controller/`
|
||||||
|
|
||||||
|
**State management file naming (GetX / getx):**
|
||||||
|
- `bloc`: `[feature]_bloc.dart`, `[feature]_event.dart`, `[feature]_state.dart`
|
||||||
|
- `riverpod`: `[feature]_provider.dart`, `[feature]_notifier.dart`
|
||||||
|
- `getx`: `[feature]_controller.dart`, `[feature]_binding.dart`
|
||||||
|
|
||||||
|
3. List all modified files from `files_to_touch.modified` — show exactly what change goes in each file.
|
||||||
|
|
||||||
|
4. List all external service configuration required per platform (from `external_setup`). Only show platforms present in `ios, android`.
|
||||||
|
|
||||||
|
5. Print research results in this format:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Research Results
|
||||||
|
|
||||||
|
### Packages to add to pubspec.yaml
|
||||||
|
- [package_name]: ^[version]
|
||||||
|
|
||||||
|
### Files to create (MVC / GetX)
|
||||||
|
lib/features/[name]/... (full list)
|
||||||
|
|
||||||
|
### Files to modify
|
||||||
|
[file_path] — [what changes]
|
||||||
|
|
||||||
|
### External services requiring configuration
|
||||||
|
- [Service] ([platform]) — [what to do]
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not touch any file until the user has seen this output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: TDD Implementation
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:test-driven-development`**
|
||||||
|
|
||||||
|
Follow Red → Green → Refactor strictly. Show actual terminal output at each step.
|
||||||
|
|
||||||
|
### Step 3a — Red: Write all failing tests first
|
||||||
|
|
||||||
|
1. Mirror the feature directory under `test/features/[feature]/`.
|
||||||
|
2. Write unit tests for **every scenario** in `FEATURE_REGISTRY.unit_test_scenarios` for the detected type.
|
||||||
|
3. Write widget tests for **every scenario** in `FEATURE_REGISTRY.widget_test_scenarios`.
|
||||||
|
4. Use `mocktail` for all dependencies.
|
||||||
|
5. Test naming convention: `'given [precondition], when [action], then [expected outcome]'`
|
||||||
|
6. Use the state management test pattern for **GetX**:
|
||||||
|
```
|
||||||
|
Get.put(MyController()); final ctrl = Get.find<MyController>(); expect(ctrl.value, expected);
|
||||||
|
```
|
||||||
|
7. Run and confirm red:
|
||||||
|
```
|
||||||
|
flutter test test/features/[feature]/ --no-pub
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
If failures are unclear or non-deterministic after one iteration, use **`/debug`** with full pasted output before guessing fixes.
|
||||||
|
|
||||||
|
### Step 3b — Green: Implement
|
||||||
|
|
||||||
|
1. Create domain entities and repository interfaces.
|
||||||
|
2. Create data-layer implementations wiring to `REST API`.
|
||||||
|
3. Create presentation layer using **GetX** patterns.
|
||||||
|
4. Register in DI container — follow `- View (Widget) MUST NOT contain business logic
|
||||||
|
- Controller MUST NOT import Flutter widgets directly
|
||||||
|
- Model MUST be plain Dart, no framework dependencies`.
|
||||||
|
5. Wire the route in `GetX Navigation` router.
|
||||||
|
6. If `none` is not `none`: run `dart run build_runner build --delete-conflicting-outputs` after adding models.
|
||||||
|
7. Run tests and confirm green:
|
||||||
|
```
|
||||||
|
flutter test test/features/[feature]/ --no-pub
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
|
||||||
|
### Step 3c — Refactor
|
||||||
|
|
||||||
|
1. Review for duplication, naming, and layer boundary violations per `.cursor/rules/flutter-core.mdc`.
|
||||||
|
2. Run and confirm clean:
|
||||||
|
```
|
||||||
|
flutter analyze
|
||||||
|
```
|
||||||
|
**Paste the actual output here before proceeding.**
|
||||||
|
3. Run tests once more to confirm still green.
|
||||||
|
|
||||||
|
If output stays red or errors are ambiguous after two focused attempts, stop and use **`/debug`** with the full failing command and log before changing more code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Integration Test Generation
|
||||||
|
|
||||||
|
**Generate `integration_test/[feature_type]/` before asking the user to run anything.**
|
||||||
|
|
||||||
|
### Integration test file structure
|
||||||
|
|
||||||
|
Each test file follows this template:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// integration_test/[feature]/[scenario]_test.dart
|
||||||
|
// Generated by /build — LegacyApp
|
||||||
|
// Feature: [feature_type] | Scenario: [scenario_name]
|
||||||
|
// Run with: flutter test integration_test/[feature]/[scenario]_test.dart -d <device_id>
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:com.test.legacy/main.dart' as app;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('[FeatureType] — [Scenario Name]', () {
|
||||||
|
setUp(() async {
|
||||||
|
// Seed state / configure mocks / reset storage
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
// Cleanup
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'given [precondition], when [action], then [expected outcome]',
|
||||||
|
(tester) async {
|
||||||
|
app.main();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// test body
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**For `patrol` (patrol):** swap `flutter_test` imports for `package:patrol` and use Patrol's `$` NativeAutomator selector syntax.
|
||||||
|
|
||||||
|
### Files to generate
|
||||||
|
|
||||||
|
Create one file per logical scenario cluster from `FEATURE_REGISTRY.integration_test_scenarios`.
|
||||||
|
Also generate `integration_test/[feature]/README.md` with the full test matrix and run commands.
|
||||||
|
|
||||||
|
### PAUSE GATE — user must run on device
|
||||||
|
|
||||||
|
After generating all files, print this table and **stop**. Wait for the user to paste device output before Phase 6.
|
||||||
|
|
||||||
|
```
|
||||||
|
## ACTION REQUIRED — Run Integration Tests on Real Device
|
||||||
|
|
||||||
|
| File | Covers | Requires Hardware |
|
||||||
|
|------|--------|------------------|
|
||||||
|
| integration_test/[feature]/[scenario]_test.dart | [what it covers] | Yes/No |
|
||||||
|
...
|
||||||
|
|
||||||
|
Run on iOS:
|
||||||
|
flutter test integration_test/[feature]/ -d <your_ios_device_id>
|
||||||
|
|
||||||
|
Run on Android:
|
||||||
|
flutter test integration_test/[feature]/ -d <your_android_device_id>
|
||||||
|
|
||||||
|
Paste the output here to continue.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: External Setup Checklist
|
||||||
|
|
||||||
|
**Print before asking the user to verify anything. Group by service/platform. Only show sections for platforms in `ios, android`.**
|
||||||
|
|
||||||
|
### Format for console/service steps (numbered list):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Firebase (backend: REST API)
|
||||||
|
- [ ] 1. Open Firebase Console → [exact menu path]
|
||||||
|
- [ ] 2. [Specific action]
|
||||||
|
- [ ] 3. Download updated config file → place at [exact path]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Format for native platform steps (table):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### iOS Setup
|
||||||
|
| Step | Where | What | How to Verify |
|
||||||
|
|------|-------|------|---------------|
|
||||||
|
| [Step name] | [Xcode location / file path] | [Exact change] | [Verification method] |
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Android Setup
|
||||||
|
- [ ] 1. Open `android/app/src/main/AndroidManifest.xml`
|
||||||
|
- Add: `<uses-permission android:name="[permission]"/>`
|
||||||
|
- [ ] 2. [Next step with exact value]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flavor scoping note (flavors: dev, prod)
|
||||||
|
|
||||||
|
Config files and API keys must be scoped per flavor. Never place production keys in `dev` flavor files. Follow the pattern established in `.cursor/rules/` for flavor-based configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Verification Gate
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:verification-before-completion`**
|
||||||
|
|
||||||
|
**Do not claim completion without pasting real output for every item below.**
|
||||||
|
|
||||||
|
```
|
||||||
|
VERIFICATION REQUIRED — paste real output for each:
|
||||||
|
|
||||||
|
[ ] flutter test test/features/[feature]/ --no-pub
|
||||||
|
Required: "All N tests passed."
|
||||||
|
|
||||||
|
[ ] flutter analyze
|
||||||
|
Required: "No issues found!"
|
||||||
|
|
||||||
|
[ ] Integration test device output (from Phase 4 pause gate)
|
||||||
|
Required: actual device test output
|
||||||
|
|
||||||
|
[ ] lefthook run pre-commit
|
||||||
|
Required: all hooks passed
|
||||||
|
```
|
||||||
|
|
||||||
|
If any check fails: invoke `superpowers:systematic-debugging` to diagnose before retrying.
|
||||||
|
For noisy errors or weak reproduction, run **`/debug`** and fill the BugReport skeleton with logs before retrying.
|
||||||
|
For small, isolated changes, **`/verify`** is enough to re-check tests and analyze without re-reading this whole skill.
|
||||||
|
Do not proceed to Phase 7 until all four checks are green.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: PR Preparation
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:finishing-a-development-branch`**
|
||||||
|
|
||||||
|
1. **Commit message** (conventional commits format):
|
||||||
|
```
|
||||||
|
feat([feature_type]): implement [feature] end-to-end
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **PR description template:**
|
||||||
|
```markdown
|
||||||
|
## What
|
||||||
|
[One sentence: what feature was implemented]
|
||||||
|
|
||||||
|
## Test coverage
|
||||||
|
- Unit/widget tests: N passing
|
||||||
|
- Integration test matrix:
|
||||||
|
| Scenario | iOS | Android |
|
||||||
|
|----------|-----|---------|
|
||||||
|
| [scenario] | ✅ | ✅ |
|
||||||
|
|
||||||
|
## External setup completed
|
||||||
|
- [ ] [Service 1 setup]
|
||||||
|
- [ ] [Service 2 setup]
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
[Any follow-up TODOs or known limitations]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **CI/CD advice for Codemagic (codemagic):**
|
||||||
|
Ensure workflow secrets are scoped per flavor (dev, prod). Never expose production keys in dev environment secrets. Verify the CI workflow runs `flutter test` and `flutter analyze` before deploy steps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules applied every phase
|
||||||
|
|
||||||
|
Always active — read before writing any code:
|
||||||
|
- `.cursor/rules/flutter-core.mdc`
|
||||||
|
- `.cursor/rules/security-standards.mdc`
|
||||||
|
- `.cursor/rules/project-context.mdc`
|
||||||
|
- Feature-type-specific rules from `FEATURE_REGISTRY.rules_to_load`
|
||||||
|
|
||||||
|
Architecture import rules for **MVC**:
|
||||||
|
- View (Widget) MUST NOT contain business logic
|
||||||
|
- Controller MUST NOT import Flutter widgets directly
|
||||||
|
- Model MUST be plain Dart, no framework dependencies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code generation notes
|
||||||
|
|
||||||
|
**Codegen tools configured: none**
|
||||||
|
|
||||||
|
After adding any new model or injectable class, run:
|
||||||
|
```
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit generated files (`.g.dart`, `.freezed.dart`, `injection.config.dart`) — do not gitignore them.
|
||||||
|
|
||||||
|
**Template version:** 1.0.5
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
name: Debug issue
|
||||||
|
description: Triage failing tests, analyze, CI, or runtime errors with an evidence-first BugReport. Use /debug and paste logs; invokes systematic-debugging before fixes. Stack GetX / MVC / REST API / ios, android.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Debug — LegacyApp
|
||||||
|
|
||||||
|
Triage failures (tests, CI, runtime, or build) **without** jumping to fixes. Stay in hypothesis-and-evidence mode until root cause is stated.
|
||||||
|
|
||||||
|
**Stack context:** **GetX** / **MVC** / **REST API** / platforms: ios, android. Flavors: dev, prod. Codegen: none.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/debug <what failed — paste error output, command, or symptom>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/debug flutter test fails on auth_cubit_test — paste output`
|
||||||
|
- `/debug CI analyze step — paste log excerpt`
|
||||||
|
- `/debug app crashes on cold start after last change`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 — Normalize the report (BugReport)
|
||||||
|
|
||||||
|
Emit this skeleton **before** deep analysis. If the user already pasted logs, map them into the fields instead of re-asking.
|
||||||
|
|
||||||
|
| Field | Content |
|
||||||
|
|-------|---------|
|
||||||
|
| **Summary** | One line: what broke |
|
||||||
|
| **Expected** | What should happen |
|
||||||
|
| **Actual** | What happened (symptom + error text) |
|
||||||
|
| **Repro steps** | Numbered, minimal |
|
||||||
|
| **Scope / files touched** | Paths or PR slice |
|
||||||
|
| **Environment** | OS, Flutter/Dart version if known, device vs simulator, flavor |
|
||||||
|
| **Evidence** | Pasted command output, stack trace, or screenshot notes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Evidence checklist (Flutter-aware)
|
||||||
|
|
||||||
|
Gather or request **concrete** evidence. Do not guess versions or config.
|
||||||
|
|
||||||
|
1. **`flutter doctor -v`** — paste output when environment is unknown or iOS/Android toolchain errors appear.
|
||||||
|
2. **Failing command** — full invocation + **verbatim** tail of output (e.g. `flutter test …`, `dart test …`, `flutter analyze`).
|
||||||
|
3. **`flutter analyze`** — if not already in the failure log, run or ask the user to run and paste.
|
||||||
|
4. **Flavors** — this project uses: **dev, prod**. Confirm which flavor was active if the failure is env-specific.
|
||||||
|
5. **Platforms** — **ios, android**. Narrow reproduction to the platform that failed when relevant.
|
||||||
|
6. **Codegen** — tools: **none**. When this is not `none`, remind to run `dart run build_runner build --delete-conflicting-outputs` after generated files changed, and to align with `.cursor/hooks/` / `lefthook run pre-commit` when hooks are present.
|
||||||
|
7. **Testing depth** — **unit_widget**; E2E tool: **patrol**. Match the failure to the right layer (unit vs widget vs integration).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Root cause (no code yet)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:systematic-debugging`**
|
||||||
|
|
||||||
|
Produce **one paragraph**: hypothesis tied to **specific lines** in the pasted evidence. Mark confidence (high / medium / low). **No code changes** in this phase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Fix (only after Phase 2)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:systematic-debugging`** again while iterating fixes.
|
||||||
|
|
||||||
|
When proposing changes:
|
||||||
|
|
||||||
|
- Respect architecture boundaries for **MVC**:
|
||||||
|
- View (Widget) MUST NOT contain business logic
|
||||||
|
- Controller MUST NOT import Flutter widgets directly
|
||||||
|
- Model MUST be plain Dart, no framework dependencies
|
||||||
|
- Always consider: `.cursor/rules/flutter-core.mdc`, `.cursor/rules/security-standards.mdc`, `.cursor/rules/project-context.mdc`, and state-management rules for **GetX**.
|
||||||
|
|
||||||
|
After each fix attempt, re-run the **same** failing command and paste new output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACTION REQUIRED
|
||||||
|
|
||||||
|
If evidence is missing, **stop** and print:
|
||||||
|
|
||||||
|
1. Exact commands to run (copy-paste ready).
|
||||||
|
2. What to paste back (full error blocks, not summaries).
|
||||||
|
3. If the user cannot run commands: state assumptions explicitly and set confidence to **low**.
|
||||||
|
|
||||||
|
**Template version:** 1.0.5
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
name: Explain code
|
||||||
|
description: Explain what code does without editing it. Use /explain with a path or symbol; covers GetX state, REST API I/O, routing GetX Navigation, and failure modes. Ask when facts are not in the repo.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Explain — LegacyApp
|
||||||
|
|
||||||
|
Explain **what the code is doing** — **do not** change production code unless the user explicitly asks for a fix.
|
||||||
|
|
||||||
|
**Stack:** **GetX** / **MVC** / **GetX Navigation** / **REST API** / platforms: ios, android. Package: `com.test.legacy`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/explain <file path, widget name, class, or symbol>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/explain lib/features/cart/cart_cubit.dart`
|
||||||
|
- `/explain how checkout routes after payment`
|
||||||
|
- `/explain CartPage build method`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output format (use these headings in order)
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
One short paragraph: why this code exists in the product context (**LegacyApp** — Legacy GetX app).
|
||||||
|
|
||||||
|
### Public API
|
||||||
|
|
||||||
|
Surface area: public classes/methods, constructors, and what callers are expected to pass. Note codegen involvement when **none** is not `none`.
|
||||||
|
|
||||||
|
### Call flow
|
||||||
|
|
||||||
|
Ordered steps from entry point (e.g. widget `build`, route handler, Bloc `on<Event>`, Riverpod `build`, GetX controller lifecycle) through collaborators. Tie navigation to **GetX Navigation** where relevant.
|
||||||
|
|
||||||
|
### State and side effects
|
||||||
|
|
||||||
|
How state is held and updated for **GetX** (getx). Mention async work, listeners, and disposal. Reference **- View (Widget) MUST NOT contain business logic
|
||||||
|
- Controller MUST NOT import Flutter widgets directly
|
||||||
|
- Model MUST be plain Dart, no framework dependencies** if layering is unclear.
|
||||||
|
|
||||||
|
### I/O and backends
|
||||||
|
|
||||||
|
Network, local storage, or platform channels touching **REST API** (and **JWT / REST Auth** where auth applies: jwt_rest). Do **not** invent API shapes not visible in the repo.
|
||||||
|
|
||||||
|
### Failure modes
|
||||||
|
|
||||||
|
What can go wrong: null paths, error states, race conditions, missing permissions on ios, android, auth edge cases.
|
||||||
|
|
||||||
|
### Suggested tests
|
||||||
|
|
||||||
|
Ideas aligned with **unit_widget** and **patrol**; for **GetX**, prefer patterns like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Get.put(MyController()); final ctrl = Get.find<MyController>(); expect(ctrl.value, expected);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unknowns — questions for you
|
||||||
|
|
||||||
|
If behavior depends on runtime config, native projects, remote API contracts, or secrets not in tree: **stop** and list **specific** questions. Do not fabricate facts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- **Project brief:** `project-brief.yaml` — feature modules: auth, dashboard; special features: .
|
||||||
|
- **Scale:** medium; i18n locales: en (when explaining localization).
|
||||||
|
- **Design:** none; Figma URL: .
|
||||||
|
- **API docs format:** openapi (path: docs/api.yaml).
|
||||||
|
- **Related repos (if any):**
|
||||||
|
|
||||||
|
_No Git repository URLs listed._ Add entries under `references.repos` in project-brief.yaml when other repos are part of the product context.
|
||||||
|
|
||||||
|
- **Local paths (if any):**
|
||||||
|
|
||||||
|
_No local paths listed._ Add monorepo packages or sibling folders under `references.local_paths` in project-brief.yaml when relevant.
|
||||||
|
|
||||||
|
**Template version:** 1.0.5
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
name: Verify change
|
||||||
|
description: Post-change or pre-PR verification without the full /build lifecycle. Use /verify with optional scope; respects testing depth unit_widget and E2E patrol; no success claims without pasted outputs.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Verify — LegacyApp
|
||||||
|
|
||||||
|
Run a **focused** verification gate after a change or before a small PR — without replaying the entire `/build` document.
|
||||||
|
|
||||||
|
**Stack:** **GetX** / **MVC** / **REST API** / ios, android. **Testing depth:** unit_widget. **E2E tool:** patrol. **Codegen:** none.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/verify [optional scope: paths, “small PR”, or feature name]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `/verify lib/features/auth/`
|
||||||
|
- `/verify small PR before push`
|
||||||
|
- `/verify after dependency bump`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist (adapt to testing depth)
|
||||||
|
|
||||||
|
> **Invoke skill: `superpowers:verification-before-completion`**
|
||||||
|
|
||||||
|
**Do not claim success** until the user (or you, in a trusted environment) has pasted **real** output for each applicable item below.
|
||||||
|
|
||||||
|
### Always (typical Flutter repo)
|
||||||
|
|
||||||
|
- [ ] **Unit / widget tests** for touched code — e.g. `flutter test <path> --no-pub` or full suite as appropriate.
|
||||||
|
**Required in paste:** a line showing tests passed (or the exact failure to fix).
|
||||||
|
- [ ] **`flutter analyze`**
|
||||||
|
**Required in paste:** `No issues found!` or the analyzer errors to address.
|
||||||
|
|
||||||
|
### When `testing.depth` is `full` or `e2e` (this brief: **unit_widget**)
|
||||||
|
|
||||||
|
- [ ] **Integration / E2E** — use **patrol** patterns from `.cursor/rules/` (e.g. Patrol). Run on a device or emulator when scenarios require it; paste run output.
|
||||||
|
|
||||||
|
### When `testing.depth` is `unit_widget` only
|
||||||
|
|
||||||
|
- [ ] **Integration / device E2E** — *not* mandatory unless the change touches integration-only surfaces; if skipped, say so explicitly in the paste.
|
||||||
|
|
||||||
|
### Hooks and codegen
|
||||||
|
|
||||||
|
- [ ] **`lefthook run pre-commit`** — run when the repo has Lefthook configured (especially when **none** is not `none`). Paste hook summary.
|
||||||
|
If hooks are not set up for this workspace, write *“skipped — hooks not configured”* instead of failing silently.
|
||||||
|
|
||||||
|
### CI alignment
|
||||||
|
|
||||||
|
- [ ] For **Codemagic** (codemagic): confirm the same commands the pipeline runs (analyze + tests) are green locally. Note flavor-scoped secrets for **dev, prod**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACTION REQUIRED — paste block
|
||||||
|
|
||||||
|
Print this table and wait for pasted output before declaring done:
|
||||||
|
|
||||||
|
```
|
||||||
|
VERIFICATION — paste real output for each line you executed:
|
||||||
|
|
||||||
|
[ ] flutter test … → (paste: pass count or failures)
|
||||||
|
[ ] flutter analyze → (paste: no issues or errors)
|
||||||
|
[ ] integration / e2e (if depth is full/e2e or change requires it) →
|
||||||
|
[ ] lefthook run pre-commit → (paste or "skipped — not configured")
|
||||||
|
```
|
||||||
|
|
||||||
|
If anything fails: switch to **`/debug`** with the failing log before guessing.
|
||||||
|
|
||||||
|
**Template version:** 1.0.5
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — TaskFlow
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# TaskFlow
|
||||||
|
|
||||||
|
Repo-level notes for AI assistants. Authoritative stack and conventions are in `project-brief.yaml` and `.cursor/` (regenerate with `cursor_gen` from the project root).
|
||||||
|
|
||||||
|
- **Package:** `com.test.taskflow`
|
||||||
|
- **Onboarding:** `.cursor/ONBOARDING.md` — layout, slash commands → skills, MCP opt-in, audits
|
||||||
|
- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` (see `.cursor/commands/*.md`)
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
# TaskFlow — generated by cursor_gen; adjust commands to your repo
|
||||||
|
# Optional: run `bash tool/cursor_audit.sh` after changing features.modules or rule files.
|
||||||
|
pre-commit:
|
||||||
|
commands:
|
||||||
|
flutter-analyze:
|
||||||
|
run: flutter analyze
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# TaskFlow — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for TaskFlow
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — TaskFlow
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **dev, prod**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — patrol on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **GitHub Actions** (`github_actions`)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user