diff --git a/example-project/project-brief.yaml b/example-project/project-brief.yaml index ab0dc9c..a222d88 100644 --- a/example-project/project-brief.yaml +++ b/example-project/project-brief.yaml @@ -116,6 +116,17 @@ localization: enabled: true 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/ # ----------------------------------------------------------------------------- diff --git a/flutter-cursor-templates/generator/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjM= b/flutter-cursor-templates/generator/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjM= index 0421383..f57c918 100644 Binary files a/flutter-cursor-templates/generator/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjM= and b/flutter-cursor-templates/generator/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjM= differ diff --git a/flutter-cursor-templates/generator/brief-schema.json b/flutter-cursor-templates/generator/brief-schema.json index 0a04c26..7de1cff 100644 --- a/flutter-cursor-templates/generator/brief-schema.json +++ b/flutter-cursor-templates/generator/brief-schema.json @@ -162,6 +162,38 @@ "type": "boolean", "description": "Pillar 6: Opt-in local telemetry for rule trigger analytics", "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" + } + } } } } diff --git a/flutter-cursor-templates/generator/src/brief_loader.dart b/flutter-cursor-templates/generator/src/brief_loader.dart index 4e734b7..aad5178 100644 --- a/flutter-cursor-templates/generator/src/brief_loader.dart +++ b/flutter-cursor-templates/generator/src/brief_loader.dart @@ -22,6 +22,9 @@ class BriefLoader { final appCtx = yaml['app_context'] as YamlMap? ?? YamlMap(); final features = yaml['features'] 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) final backendRaw = stack['backend']?.toString() ?? 'rest'; @@ -75,6 +78,11 @@ class BriefLoader { }(), rolesEnabled: appCtx['roles_enabled'] as bool? ?? false, roleNames: _toStringList(appCtx['role_names']) ?? [], + + mcpConfigEnabled: mcp['enabled'] as bool? ?? false, + mcpPreset: mcp['preset']?.toString() ?? 'auto', + strictPackageImports: + conventions['strict_package_imports'] as bool? ?? false, ); } diff --git a/flutter-cursor-templates/generator/src/mcp_json.dart b/flutter-cursor-templates/generator/src/mcp_json.dart new file mode 100644 index 0000000..b706361 --- /dev/null +++ b/flutter-cursor-templates/generator/src/mcp_json.dart @@ -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({'mcpServers': {}}); + } + + final servers = >{ + '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}); + } +} diff --git a/flutter-cursor-templates/generator/src/models.dart b/flutter-cursor-templates/generator/src/models.dart index d92773d..8474f2d 100644 --- a/flutter-cursor-templates/generator/src/models.dart +++ b/flutter-cursor-templates/generator/src/models.dart @@ -59,6 +59,15 @@ class ProjectBrief { final bool rolesEnabled; final List 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({ required this.projectName, required this.packageId, @@ -90,6 +99,9 @@ class ProjectBrief { this.themeVariants = const ['light', 'dark'], this.rolesEnabled = false, 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). diff --git a/flutter-cursor-templates/generator/src/renderer.dart b/flutter-cursor-templates/generator/src/renderer.dart index 179208a..52ff2c6 100644 --- a/flutter-cursor-templates/generator/src/renderer.dart +++ b/flutter-cursor-templates/generator/src/renderer.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:path/path.dart' as p; +import 'mcp_json.dart'; import 'models.dart'; class Renderer { @@ -10,22 +11,32 @@ class Renderer { required List templateFiles, required String templateSrc, }) async { - final context = _buildContext(brief); + final baseContext = _buildContext(brief); final output = {}; 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 file = File(tmplPath); if (!file.existsSync()) { - // Gracefully skip missing optional templates with a placeholder - output[_outputPath(key)] = _missingTemplatePlaceholder(key); + output[outPath] = _missingTemplatePlaceholder(key); continue; } var content = await file.readAsString(); - content = _substituteAll(content, context); - _checkUnreplacedPlaceholders( - content, key); // Pillar 3: validate no broken {{VAR}} - output[_outputPath(key)] = content; + final ctx = Map.from(baseContext); + if (key.startsWith('rules/features/')) { + final slug = p.basename(key); + ctx['FEATURE_MODULE'] = slug; + ctx['FEATURE_MODULE_TITLE'] = _titleCase(slug); + } + content = _substituteAll(content, ctx); + _checkUnreplacedPlaceholders(content, key); + output[outPath] = content; } return output; } @@ -71,9 +82,35 @@ class Renderer { 'TEST_PATTERN': _testPattern(brief.stateManagement), 'LOCALES_LIST': brief.locales.join(', '), 'TEMPLATE_VERSION': '1.0.4', + '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:/...` 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 ctx) { for (final entry in ctx.entries) { content = content.replaceAll('{{${entry.key}}}', entry.value); @@ -107,6 +144,25 @@ class Renderer { } 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'); } @@ -122,6 +178,22 @@ class Renderer { if (key.endsWith('hooks-json')) return 'hooks/hooks.json'; 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'; } diff --git a/flutter-cursor-templates/generator/src/resolver.dart b/flutter-cursor-templates/generator/src/resolver.dart index 03e4b20..35d29c6 100644 --- a/flutter-cursor-templates/generator/src/resolver.dart +++ b/flutter-cursor-templates/generator/src/resolver.dart @@ -8,6 +8,9 @@ class Resolver { static List resolve(ProjectBrief brief) { final files = []; + // ── Meta: rule authoring (first — governs other .mdc files) ───────── + files.add('rules/universal/rule-authoring'); + // ── Universal (every project) ────────────────────────────────────── files.addAll([ 'rules/universal/flutter-core', @@ -15,6 +18,11 @@ class Resolver { '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) ── files.add('rules/security/security-standards'); @@ -28,11 +36,19 @@ class Resolver { files.add('rules/architecture/${brief.architecture}'); // ── 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')) { 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 ───────────────────────────────────────── files.add('rules/routing/${brief.routing}'); @@ -67,6 +83,19 @@ class Resolver { 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 = {}; + for (final m in brief.featureModules) { + final k = featureRuleKey(m); + if (k != null) featureKeys.add(k); + } + files.addAll(featureKeys); + // ── Skills ──────────────────────────────────────────────────────── files.addAll([ 'skills/scaffold-feature', @@ -78,8 +107,8 @@ class Resolver { 'skills/explain-code', ]); if (brief.apiDocsFormat != 'none') files.add('skills/generate-api-client'); - if (brief.flavors.length > 1) files.add('skills/create-flavor'); - if (brief.cicd.isNotEmpty) files.add('skills/deploy'); + if (brief.flavors.length > 1) files.add('skills/create-flavor'); + if (brief.cicd != 'none') files.add('skills/deploy'); // ── Agents ──────────────────────────────────────────────────────── files.addAll([ @@ -97,11 +126,51 @@ class Resolver { 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; } + /// `lib/features//` ↔ `rules/features/.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 static Map resolveWithReasons(ProjectBrief brief) { final resolved = resolve(brief); @@ -113,29 +182,100 @@ class Resolver { } static String _reason(String key, ProjectBrief brief) { - if (key.contains('universal')) return 'Always included'; - if (key.contains('security')) return 'Always included — Pillar 5'; + if (key == 'rules/universal/rule-authoring') { + 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('state-management')) return 'Matches stack.state_management: ${brief.stateManagement}'; - if (key.contains('architecture')) return 'Matches stack.architecture: ${brief.architecture}'; - 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.contains('state-management')) { + return 'Matches stack.state_management: ${brief.stateManagement}'; + } + if (key.contains('architecture')) { + return 'Matches stack.architecture: ${brief.architecture}'; + } + 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/')) { return 'stack.codegen non-empty — Cursor hooks for analyze, boundaries, and tests'; } - if (key.contains('codegen')) return 'Matches stack.codegen'; - if (key.contains('i18n')) return 'localization.enabled: true'; - 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.startsWith('root/')) return 'Repo-level companion files'; + if (key.contains('codegen')) { + return 'Matches stack.codegen'; + } + if (key.contains('i18n')) { + return 'localization.enabled: true'; + } + 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'; } } diff --git a/flutter-cursor-templates/generator/src/validator.dart b/flutter-cursor-templates/generator/src/validator.dart index 209a9fb..cff24ec 100644 --- a/flutter-cursor-templates/generator/src/validator.dart +++ b/flutter-cursor-templates/generator/src/validator.dart @@ -52,6 +52,7 @@ class Validator { }; static const _validApiFormats = {'openapi', 'postman', 'markdown', 'none'}; static const _validThemeVariants = {'light', 'dark', 'high_contrast'}; + static const _validMcpPresets = {'auto', 'minimal'}; static Future validateFile(String path) async { 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( isValid: errors.isEmpty, errors: errors, @@ -214,6 +227,10 @@ class Validator { if (brief.rolesEnabled && brief.roleNames.isEmpty) { 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( isValid: errors.isEmpty, errors: errors, warnings: warnings); diff --git a/flutter-cursor-templates/generator/templates/commands/build.md.tmpl b/flutter-cursor-templates/generator/templates/commands/build.md.tmpl new file mode 100644 index 0000000..4dc2925 --- /dev/null +++ b/flutter-cursor-templates/generator/templates/commands/build.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/generator/templates/commands/debug-issue.md.tmpl b/flutter-cursor-templates/generator/templates/commands/debug-issue.md.tmpl new file mode 100644 index 0000000..21a4fbb --- /dev/null +++ b/flutter-cursor-templates/generator/templates/commands/debug-issue.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/generator/templates/commands/explain-code.md.tmpl b/flutter-cursor-templates/generator/templates/commands/explain-code.md.tmpl new file mode 100644 index 0000000..40b10cc --- /dev/null +++ b/flutter-cursor-templates/generator/templates/commands/explain-code.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/generator/templates/commands/verify-change.md.tmpl b/flutter-cursor-templates/generator/templates/commands/verify-change.md.tmpl new file mode 100644 index 0000000..a2e8bb5 --- /dev/null +++ b/flutter-cursor-templates/generator/templates/commands/verify-change.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/generator/templates/onboarding/ONBOARDING.md.tmpl b/flutter-cursor-templates/generator/templates/onboarding/ONBOARDING.md.tmpl new file mode 100644 index 0000000..04bb2ba --- /dev/null +++ b/flutter-cursor-templates/generator/templates/onboarding/ONBOARDING.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/generator/templates/root/.cursorignore.tmpl b/flutter-cursor-templates/generator/templates/root/.cursorignore.tmpl new file mode 100644 index 0000000..c4e056e --- /dev/null +++ b/flutter-cursor-templates/generator/templates/root/.cursorignore.tmpl @@ -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 diff --git a/flutter-cursor-templates/generator/templates/root/AGENTS.md.tmpl b/flutter-cursor-templates/generator/templates/root/AGENTS.md.tmpl index f903927..3384b6f 100644 --- a/flutter-cursor-templates/generator/templates/root/AGENTS.md.tmpl +++ b/flutter-cursor-templates/generator/templates/root/AGENTS.md.tmpl @@ -3,4 +3,5 @@ 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}}` -- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` +- **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`) diff --git a/flutter-cursor-templates/generator/templates/root/lefthook.yaml.tmpl b/flutter-cursor-templates/generator/templates/root/lefthook.yaml.tmpl index 2709629..84da03d 100644 --- a/flutter-cursor-templates/generator/templates/root/lefthook.yaml.tmpl +++ b/flutter-cursor-templates/generator/templates/root/lefthook.yaml.tmpl @@ -1,4 +1,5 @@ # {{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: diff --git a/flutter-cursor-templates/generator/templates/root/tool/cursor_audit.sh.tmpl b/flutter-cursor-templates/generator/templates/root/tool/cursor_audit.sh.tmpl new file mode 100644 index 0000000..0095e12 --- /dev/null +++ b/flutter-cursor-templates/generator/templates/root/tool/cursor_audit.sh.tmpl @@ -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" diff --git a/flutter-cursor-templates/generator/templates/rules/cicd/cicd.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/cicd/cicd.mdc.tmpl new file mode 100644 index 0000000..fc7fd7a --- /dev/null +++ b/flutter-cursor-templates/generator/templates/rules/cicd/cicd.mdc.tmpl @@ -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_.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}}`) diff --git a/flutter-cursor-templates/generator/templates/rules/features/_stub.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/features/_stub.mdc.tmpl new file mode 100644 index 0000000..a6961f1 --- /dev/null +++ b/flutter-cursor-templates/generator/templates/rules/features/_stub.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/generator/templates/rules/i18n/localization.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/i18n/localization.mdc.tmpl index dff5105..d646f1a 100644 --- a/flutter-cursor-templates/generator/templates/rules/i18n/localization.mdc.tmpl +++ b/flutter-cursor-templates/generator/templates/rules/i18n/localization.mdc.tmpl @@ -1,6 +1,7 @@ --- description: "Localization / i18n conventions for {{PROJECT_NAME}}" -alwaysApply: true +globs: ["lib/l10n/**", "lib/**/*.dart", "test/**/*.dart"] +alwaysApply: false --- # Localization Standards — {{PROJECT_NAME}} diff --git a/flutter-cursor-templates/generator/templates/rules/integrations/push-deeplink.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/integrations/push-deeplink.mdc.tmpl new file mode 100644 index 0000000..aa8f82a --- /dev/null +++ b/flutter-cursor-templates/generator/templates/rules/integrations/push-deeplink.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/generator/templates/rules/telemetry/usage-logging.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/telemetry/usage-logging.mdc.tmpl new file mode 100644 index 0000000..547ffd0 --- /dev/null +++ b/flutter-cursor-templates/generator/templates/rules/telemetry/usage-logging.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/generator/templates/rules/theming/theming.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/theming/theming.mdc.tmpl new file mode 100644 index 0000000..971983c --- /dev/null +++ b/flutter-cursor-templates/generator/templates/rules/theming/theming.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/generator/templates/rules/universal/flutter-core.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/universal/flutter-core.mdc.tmpl index bcafe6a..647e438 100644 --- a/flutter-cursor-templates/generator/templates/rules/universal/flutter-core.mdc.tmpl +++ b/flutter-cursor-templates/generator/templates/rules/universal/flutter-core.mdc.tmpl @@ -1,6 +1,7 @@ --- -description: "Core Flutter conventions for {{PROJECT_NAME}} — always applied" -alwaysApply: true +description: "Core Flutter conventions for {{PROJECT_NAME}}" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # Flutter Core Standards — {{PROJECT_NAME}} @@ -29,10 +30,7 @@ alwaysApply: true - Private members: `_camelCase` ## Imports -- Order: dart: → package: → relative -- Use relative imports within a feature; absolute for cross-feature -- Never import a feature's internal files from outside that feature - +{{IMPORT_POLICY_BLOCK}} ## Code quality - Max function length: 40 lines. Extract widgets and helpers aggressively - No `print()` in production code — use a logging package diff --git a/flutter-cursor-templates/generator/templates/rules/universal/project-context.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/universal/project-context.mdc.tmpl index 7d2f89c..5901ad4 100644 --- a/flutter-cursor-templates/generator/templates/rules/universal/project-context.mdc.tmpl +++ b/flutter-cursor-templates/generator/templates/rules/universal/project-context.mdc.tmpl @@ -1,6 +1,7 @@ --- -description: "Project context for {{PROJECT_NAME}} — always applied" -alwaysApply: true +description: "Stack summary and product context for {{PROJECT_NAME}}" +globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"] +alwaysApply: false --- # Project Context — {{PROJECT_NAME}} diff --git a/flutter-cursor-templates/generator/templates/rules/universal/rule-authoring.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/universal/rule-authoring.mdc.tmpl new file mode 100644 index 0000000..1b1e520 --- /dev/null +++ b/flutter-cursor-templates/generator/templates/rules/universal/rule-authoring.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/generator/templates/rules/universal/ui-ux-standards.mdc.tmpl b/flutter-cursor-templates/generator/templates/rules/universal/ui-ux-standards.mdc.tmpl index bfd07bb..2cde1db 100644 --- a/flutter-cursor-templates/generator/templates/rules/universal/ui-ux-standards.mdc.tmpl +++ b/flutter-cursor-templates/generator/templates/rules/universal/ui-ux-standards.mdc.tmpl @@ -1,6 +1,7 @@ --- -description: "UI/UX standards for {{PROJECT_NAME}} — always applied" -alwaysApply: true +description: "UI/UX standards for {{PROJECT_NAME}}" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # UI / UX Standards — {{PROJECT_NAME}} diff --git a/flutter-cursor-templates/generator/templates/telemetry/gitignore.tmpl b/flutter-cursor-templates/generator/templates/telemetry/gitignore.tmpl new file mode 100644 index 0000000..6e3f01a --- /dev/null +++ b/flutter-cursor-templates/generator/templates/telemetry/gitignore.tmpl @@ -0,0 +1,2 @@ +*.jsonl +*.log diff --git a/flutter-cursor-templates/generator/templates/telemetry/log.sh.tmpl b/flutter-cursor-templates/generator/templates/telemetry/log.sh.tmpl new file mode 100644 index 0000000..1910fc2 --- /dev/null +++ b/flutter-cursor-templates/generator/templates/telemetry/log.sh.tmpl @@ -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" diff --git a/flutter-cursor-templates/generator/test/generator_test.dart b/flutter-cursor-templates/generator/test/generator_test.dart index a71025b..61d381a 100644 --- a/flutter-cursor-templates/generator/test/generator_test.dart +++ b/flutter-cursor-templates/generator/test/generator_test.dart @@ -8,6 +8,7 @@ import '../src/resolver.dart'; import '../src/renderer.dart'; import '../src/validator.dart'; import '../src/models.dart'; +import '../src/mcp_json.dart'; void main() { // ─── Resolver tests ───────────────────────────────────────────────────────── @@ -17,10 +18,26 @@ void main() { final files = Resolver.resolve(kBlocCleanFirebaseBrief); // Universal — always present + expect(files.first, equals('rules/universal/rule-authoring')); expect(files, contains('rules/universal/flutter-core')); expect(files, contains('rules/universal/ui-ux-standards')); 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) expect(files, contains('rules/security/security-standards')); @@ -57,6 +74,9 @@ void main() { expect(files, containsNot('rules/architecture/feature_first')); 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')); }); @@ -72,6 +92,175 @@ void main() { 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', () { final files = Resolver.resolve(kGetxMvcRestBrief); expect(files, contains('agents/migration-agent')); @@ -245,6 +434,51 @@ void main() { 'Default template dir should resolve real arch-guard.ts.tmpl'); 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 ───────────────────────────────────────────────────────── diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/ONBOARDING.md b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/ONBOARDING.md new file mode 100644 index 0000000..0eaa929 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/ONBOARDING.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/.cursorignore b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/.cursorignore new file mode 100644 index 0000000..c4e056e --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/.cursorignore @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/AGENTS.md b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/AGENTS.md index 0da27df..10122d3 100644 --- a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/AGENTS.md +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/AGENTS.md @@ -3,4 +3,5 @@ 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` -- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` +- **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`) diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/lefthook.yaml b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/lefthook.yaml index d781613..2bbd562 100644 --- a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/lefthook.yaml +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/lefthook.yaml @@ -1,4 +1,5 @@ # 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: diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/tool/cursor_audit.sh b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/tool/cursor_audit.sh new file mode 100644 index 0000000..293a877 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/__root__/tool/cursor_audit.sh @@ -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" diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/build.md b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/build.md new file mode 100644 index 0000000..4dc2925 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/build.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/debug-issue.md b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/debug-issue.md new file mode 100644 index 0000000..21a4fbb --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/debug-issue.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/explain-code.md b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/explain-code.md new file mode 100644 index 0000000..40b10cc --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/explain-code.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/verify-change.md b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/verify-change.md new file mode 100644 index 0000000..a2e8bb5 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/commands/verify-change.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/cicd/cicd.mdc b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/cicd/cicd.mdc new file mode 100644 index 0000000..1798fd4 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/cicd/cicd.mdc @@ -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_.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`) diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/auth.mdc b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/auth.mdc new file mode 100644 index 0000000..090ec1c --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/auth.mdc @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/home.mdc b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/home.mdc new file mode 100644 index 0000000..662dc84 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/home.mdc @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/products.mdc b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/products.mdc new file mode 100644 index 0000000..679a565 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/features/products.mdc @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/flutter-core.mdc b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/flutter-core.mdc index 332717b..6870a55 100644 --- a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/flutter-core.mdc +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/flutter-core.mdc @@ -1,6 +1,7 @@ --- -description: "Core Flutter conventions for TestApp — always applied" -alwaysApply: true +description: "Core Flutter conventions for TestApp" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # Flutter Core Standards — TestApp @@ -29,9 +30,9 @@ alwaysApply: true - Private members: `_camelCase` ## Imports -- Order: dart: → package: → relative -- Use relative imports within a feature; absolute for cross-feature -- Never import a feature's internal files from outside that feature +### Imports (strict — `conventions.strict_package_imports: true`) +- Use `package:/...` 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 ## Code quality - Max function length: 40 lines. Extract widgets and helpers aggressively diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/project-context.mdc b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/project-context.mdc index 97ea332..b2f6f95 100644 --- a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/project-context.mdc +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/project-context.mdc @@ -1,6 +1,7 @@ --- -description: "Project context for TestApp — always applied" -alwaysApply: true +description: "Stack summary and product context for TestApp" +globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"] +alwaysApply: false --- # Project Context — TestApp diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/rule-authoring.mdc b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/rule-authoring.mdc new file mode 100644 index 0000000..1b2fc41 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/rule-authoring.mdc @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/ui-ux-standards.mdc b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/ui-ux-standards.mdc index 3929020..5c1894a 100644 --- a/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/ui-ux-standards.mdc +++ b/flutter-cursor-templates/generator/test/golden/bloc-clean-firebase/rules/universal/ui-ux-standards.mdc @@ -1,6 +1,7 @@ --- -description: "UI/UX standards for TestApp — always applied" -alwaysApply: true +description: "UI/UX standards for TestApp" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # UI / UX Standards — TestApp diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/ONBOARDING.md b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/ONBOARDING.md new file mode 100644 index 0000000..eb257f6 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/ONBOARDING.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/.cursorignore b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/.cursorignore new file mode 100644 index 0000000..c4e056e --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/.cursorignore @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/AGENTS.md b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/AGENTS.md index 2637a99..c0e0f6d 100644 --- a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/AGENTS.md +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/AGENTS.md @@ -3,4 +3,5 @@ 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` -- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` +- **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`) diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/lefthook.yaml b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/lefthook.yaml index 892cb74..81733e3 100644 --- a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/lefthook.yaml +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/lefthook.yaml @@ -1,4 +1,5 @@ # 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: diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/tool/cursor_audit.sh b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/tool/cursor_audit.sh new file mode 100644 index 0000000..7bd1c59 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/__root__/tool/cursor_audit.sh @@ -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" diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/build.md b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/build.md new file mode 100644 index 0000000..4dc2925 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/build.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/debug-issue.md b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/debug-issue.md new file mode 100644 index 0000000..21a4fbb --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/debug-issue.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/explain-code.md b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/explain-code.md new file mode 100644 index 0000000..40b10cc --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/explain-code.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/verify-change.md b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/verify-change.md new file mode 100644 index 0000000..a2e8bb5 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/commands/verify-change.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/mcp.json b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/mcp.json new file mode 100644 index 0000000..96c63b5 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/mcp.json @@ -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" + } + } +} \ No newline at end of file diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/cicd/cicd.mdc b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/cicd/cicd.mdc new file mode 100644 index 0000000..487dbbe --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/cicd/cicd.mdc @@ -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_.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`) diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/features/auth.mdc b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/features/auth.mdc new file mode 100644 index 0000000..090ec1c --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/features/auth.mdc @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/features/dashboard.mdc b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/features/dashboard.mdc new file mode 100644 index 0000000..8bef3c1 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/features/dashboard.mdc @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/flutter-core.mdc b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/flutter-core.mdc index d54135b..966d627 100644 --- a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/flutter-core.mdc +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/flutter-core.mdc @@ -1,6 +1,7 @@ --- -description: "Core Flutter conventions for LegacyApp — always applied" -alwaysApply: true +description: "Core Flutter conventions for LegacyApp" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # Flutter Core Standards — LegacyApp @@ -29,9 +30,10 @@ alwaysApply: true - Private members: `_camelCase` ## Imports -- Order: dart: → package: → relative -- Use relative imports within a feature; absolute for cross-feature -- Never import a feature's internal files from outside that feature +### 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 ## Code quality - Max function length: 40 lines. Extract widgets and helpers aggressively diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/project-context.mdc b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/project-context.mdc index 3b188bd..7405f5e 100644 --- a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/project-context.mdc +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/project-context.mdc @@ -1,6 +1,7 @@ --- -description: "Project context for LegacyApp — always applied" -alwaysApply: true +description: "Stack summary and product context for LegacyApp" +globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"] +alwaysApply: false --- # Project Context — LegacyApp diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/rule-authoring.mdc b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/rule-authoring.mdc new file mode 100644 index 0000000..3b488ad --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/rule-authoring.mdc @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/ui-ux-standards.mdc b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/ui-ux-standards.mdc index cc118d2..f8bdafb 100644 --- a/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/ui-ux-standards.mdc +++ b/flutter-cursor-templates/generator/test/golden/getx-mvc-rest/rules/universal/ui-ux-standards.mdc @@ -1,6 +1,7 @@ --- -description: "UI/UX standards for LegacyApp — always applied" -alwaysApply: true +description: "UI/UX standards for LegacyApp" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # UI / UX Standards — LegacyApp diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/ONBOARDING.md b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/ONBOARDING.md new file mode 100644 index 0000000..ca33cb6 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/ONBOARDING.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/.cursorignore b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/.cursorignore new file mode 100644 index 0000000..c4e056e --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/.cursorignore @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/AGENTS.md b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/AGENTS.md index 471a0cb..1e7bbb3 100644 --- a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/AGENTS.md +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/AGENTS.md @@ -3,4 +3,5 @@ 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` -- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` +- **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`) diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/lefthook.yaml b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/lefthook.yaml index 73d2045..baced12 100644 --- a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/lefthook.yaml +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/lefthook.yaml @@ -1,4 +1,5 @@ # 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: diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/tool/cursor_audit.sh b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/tool/cursor_audit.sh new file mode 100644 index 0000000..1742bf3 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/__root__/tool/cursor_audit.sh @@ -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" diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/build.md b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/build.md new file mode 100644 index 0000000..4dc2925 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/build.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/debug-issue.md b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/debug-issue.md new file mode 100644 index 0000000..21a4fbb --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/debug-issue.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/explain-code.md b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/explain-code.md new file mode 100644 index 0000000..40b10cc --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/explain-code.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/verify-change.md b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/verify-change.md new file mode 100644 index 0000000..a2e8bb5 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/commands/verify-change.md @@ -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. diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/cicd/cicd.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/cicd/cicd.mdc new file mode 100644 index 0000000..23939ef --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/cicd/cicd.mdc @@ -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_.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`) diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/auth.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/auth.mdc new file mode 100644 index 0000000..090ec1c --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/auth.mdc @@ -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 diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/profile.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/profile.mdc new file mode 100644 index 0000000..378cd74 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/profile.mdc @@ -0,0 +1,21 @@ +--- +description: "Feature module profile — contracts, boundaries, and ownership (fill after design)" +globs: ["lib/**/profile/**", "test/**/profile/**"] +alwaysApply: false +--- + +# Feature — Profile + +## Context +This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `profile` 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 diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/tasks.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/tasks.mdc new file mode 100644 index 0000000..cfcc07f --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/features/tasks.mdc @@ -0,0 +1,21 @@ +--- +description: "Feature module tasks — contracts, boundaries, and ownership (fill after design)" +globs: ["lib/**/tasks/**", "test/**/tasks/**"] +alwaysApply: false +--- + +# Feature — Tasks + +## Context +This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `tasks` 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 diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/i18n/localization.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/i18n/localization.mdc index 32c795b..f38b437 100644 --- a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/i18n/localization.mdc +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/i18n/localization.mdc @@ -1,6 +1,7 @@ --- description: "Localization / i18n conventions for TaskFlow" -alwaysApply: true +globs: ["lib/l10n/**", "lib/**/*.dart", "test/**/*.dart"] +alwaysApply: false --- # Localization Standards — TaskFlow diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/theming/theming.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/theming/theming.mdc new file mode 100644 index 0000000..3d5d22c --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/theming/theming.mdc @@ -0,0 +1,23 @@ +--- +description: Semantic colours and theme extensions for TaskFlow +globs: ["lib/**/*.dart", "test/**/*.dart"] +alwaysApply: false +--- + +# Theming — TaskFlow + +## Context +Theme variants from brief: **light, dark, high contrast**. +- **High contrast:** validate contrast, borders, and focus in the high-contrast theme alongside light/dark (WCAG). + + +## 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 theme:** validate loading, empty, and error states; never rely on color alone for meaning (use icons/text/semantics). +- 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 diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/flutter-core.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/flutter-core.mdc index 46b3ba2..08e96ce 100644 --- a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/flutter-core.mdc +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/flutter-core.mdc @@ -1,6 +1,7 @@ --- -description: "Core Flutter conventions for TaskFlow — always applied" -alwaysApply: true +description: "Core Flutter conventions for TaskFlow" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # Flutter Core Standards — TaskFlow @@ -29,9 +30,10 @@ alwaysApply: true - Private members: `_camelCase` ## Imports -- Order: dart: → package: → relative -- Use relative imports within a feature; absolute for cross-feature -- Never import a feature's internal files from outside that feature +### 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 ## Code quality - Max function length: 40 lines. Extract widgets and helpers aggressively diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/project-context.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/project-context.mdc index d818d32..71b4e4f 100644 --- a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/project-context.mdc +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/project-context.mdc @@ -1,6 +1,7 @@ --- -description: "Project context for TaskFlow — always applied" -alwaysApply: true +description: "Stack summary and product context for TaskFlow" +globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"] +alwaysApply: false --- # Project Context — TaskFlow @@ -42,10 +43,12 @@ _No Git repository URLs listed._ Add entries under `references.repos` in project _No local paths listed._ Add monorepo packages or sibling folders under `references.local_paths` in project-brief.yaml when relevant. ## Product UX / themes & roles -- **Theme variants:** light, dark +- **Theme variants:** light, dark, high contrast - **Roles:** Not enabled (`app_context.roles_enabled: false`). +- **High contrast:** validate contrast, borders, and focus in the high-contrast theme alongside light/dark (WCAG). + ## Reviews — which rule owns what - **Theme, colors, typography, spacing/radius tokens** → `ui-ux-standards.mdc` (widgets read `Theme.of(context)` only) - **User-visible copy & locales** → `localization.mdc` (ARB / `AppLocalizations`; no UI string literals) diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/rule-authoring.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/rule-authoring.mdc new file mode 100644 index 0000000..7c23180 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/rule-authoring.mdc @@ -0,0 +1,24 @@ +--- +description: How Cursor rules in this repository must be written and maintained. +globs: [".cursor/rules/**/*.mdc"] +alwaysApply: true +--- + +# Rule authoring — TaskFlow + +## 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 diff --git a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/ui-ux-standards.mdc b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/ui-ux-standards.mdc index 0929436..0951f97 100644 --- a/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/ui-ux-standards.mdc +++ b/flutter-cursor-templates/generator/test/golden/riverpod-ff-supabase/rules/universal/ui-ux-standards.mdc @@ -1,6 +1,7 @@ --- -description: "UI/UX standards for TaskFlow — always applied" -alwaysApply: true +description: "UI/UX standards for TaskFlow" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # UI / UX Standards — TaskFlow @@ -46,3 +47,4 @@ alwaysApply: true - Minimum contrast ratio: 4.5:1 (WCAG AA) - Test with TalkBack / VoiceOver before each release +- **High contrast theme:** validate loading, empty, and error states; never rely on color alone for meaning (use icons/text/semantics). diff --git a/flutter-cursor-templates/generator/test/golden_briefs.dart b/flutter-cursor-templates/generator/test/golden_briefs.dart new file mode 100644 index 0000000..5953173 --- /dev/null +++ b/flutter-cursor-templates/generator/test/golden_briefs.dart @@ -0,0 +1,91 @@ +// Shared ProjectBrief fixtures for golden tests and tool/refresh_goldens.dart + +import '../src/models.dart'; + +final kBlocCleanFirebaseBrief = 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'], + strictPackageImports: true, +); + +final kRiverpodFfSupabaseBrief = 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'], + themeVariants: const ['light', 'dark', 'high_contrast'], +); + +final kGetxMvcRestBrief = 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'], + mcpConfigEnabled: true, + mcpPreset: 'auto', +); diff --git a/flutter-cursor-templates/generator/tool/cursor_audit.sh b/flutter-cursor-templates/generator/tool/cursor_audit.sh new file mode 100755 index 0000000..c5e4257 --- /dev/null +++ b/flutter-cursor-templates/generator/tool/cursor_audit.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# cursor_gen — run from any Flutter repo: bash path/to/generator/tool/cursor_audit.sh [ROOT] +set -euo pipefail + +ROOT="$(cd "${1:-.}" && 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" diff --git a/flutter-cursor-templates/generator/tool/refresh_goldens.dart b/flutter-cursor-templates/generator/tool/refresh_goldens.dart new file mode 100644 index 0000000..caefe81 --- /dev/null +++ b/flutter-cursor-templates/generator/tool/refresh_goldens.dart @@ -0,0 +1,47 @@ +// Regenerates test/golden/* after intentional template changes. +// Run from generator/: dart run tool/refresh_goldens.dart + +import 'dart:io'; + +import 'package:path/path.dart' as p; + +import '../src/models.dart'; +import '../src/renderer.dart'; +import '../src/resolver.dart'; +import '../test/golden_briefs.dart'; + +Future main() async { + final templateSrc = _templateDir(); + if (!Directory(templateSrc).existsSync()) { + stderr.writeln('Template dir not found: $templateSrc'); + exitCode = 1; + return; + } + + final cases = { + 'bloc-clean-firebase': kBlocCleanFirebaseBrief, + 'riverpod-ff-supabase': kRiverpodFfSupabaseBrief, + 'getx-mvc-rest': kGetxMvcRestBrief, + }; + + for (final e in cases.entries) { + final profile = e.key; + final brief = e.value; + final rendered = await Renderer.render( + brief: brief, + templateFiles: Resolver.resolve(brief), + templateSrc: templateSrc, + ); + for (final out in rendered.entries) { + final file = File('test/golden/$profile/${out.key}'); + await file.parent.create(recursive: true); + await file.writeAsString(out.value); + stdout.writeln('Wrote ${file.path}'); + } + } +} + +String _templateDir() { + final scriptDir = File(Platform.script.toFilePath()).parent; + return p.normalize(p.join(scriptDir.path, '..', '..', 'templates')); +} diff --git a/flutter-cursor-templates/templates/commands/build.md.tmpl b/flutter-cursor-templates/templates/commands/build.md.tmpl new file mode 100644 index 0000000..4dc2925 --- /dev/null +++ b/flutter-cursor-templates/templates/commands/build.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/templates/commands/debug-issue.md.tmpl b/flutter-cursor-templates/templates/commands/debug-issue.md.tmpl new file mode 100644 index 0000000..21a4fbb --- /dev/null +++ b/flutter-cursor-templates/templates/commands/debug-issue.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/templates/commands/explain-code.md.tmpl b/flutter-cursor-templates/templates/commands/explain-code.md.tmpl new file mode 100644 index 0000000..40b10cc --- /dev/null +++ b/flutter-cursor-templates/templates/commands/explain-code.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/templates/commands/verify-change.md.tmpl b/flutter-cursor-templates/templates/commands/verify-change.md.tmpl new file mode 100644 index 0000000..a2e8bb5 --- /dev/null +++ b/flutter-cursor-templates/templates/commands/verify-change.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/templates/onboarding/ONBOARDING.md.tmpl b/flutter-cursor-templates/templates/onboarding/ONBOARDING.md.tmpl new file mode 100644 index 0000000..04bb2ba --- /dev/null +++ b/flutter-cursor-templates/templates/onboarding/ONBOARDING.md.tmpl @@ -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. diff --git a/flutter-cursor-templates/templates/root/.cursorignore.tmpl b/flutter-cursor-templates/templates/root/.cursorignore.tmpl new file mode 100644 index 0000000..c4e056e --- /dev/null +++ b/flutter-cursor-templates/templates/root/.cursorignore.tmpl @@ -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 diff --git a/flutter-cursor-templates/templates/root/AGENTS.md.tmpl b/flutter-cursor-templates/templates/root/AGENTS.md.tmpl index f903927..3384b6f 100644 --- a/flutter-cursor-templates/templates/root/AGENTS.md.tmpl +++ b/flutter-cursor-templates/templates/root/AGENTS.md.tmpl @@ -3,4 +3,5 @@ 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}}` -- **Slash skills** (see `.cursor/skills/`): `/build`, `/debug`, `/verify`, `/explain` +- **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`) diff --git a/flutter-cursor-templates/templates/root/lefthook.yaml.tmpl b/flutter-cursor-templates/templates/root/lefthook.yaml.tmpl index 2709629..84da03d 100644 --- a/flutter-cursor-templates/templates/root/lefthook.yaml.tmpl +++ b/flutter-cursor-templates/templates/root/lefthook.yaml.tmpl @@ -1,4 +1,5 @@ # {{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: diff --git a/flutter-cursor-templates/templates/root/tool/cursor_audit.sh.tmpl b/flutter-cursor-templates/templates/root/tool/cursor_audit.sh.tmpl new file mode 100644 index 0000000..0095e12 --- /dev/null +++ b/flutter-cursor-templates/templates/root/tool/cursor_audit.sh.tmpl @@ -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" diff --git a/flutter-cursor-templates/templates/rules/cicd/cicd.mdc.tmpl b/flutter-cursor-templates/templates/rules/cicd/cicd.mdc.tmpl new file mode 100644 index 0000000..fc7fd7a --- /dev/null +++ b/flutter-cursor-templates/templates/rules/cicd/cicd.mdc.tmpl @@ -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_.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}}`) diff --git a/flutter-cursor-templates/templates/rules/features/_stub.mdc.tmpl b/flutter-cursor-templates/templates/rules/features/_stub.mdc.tmpl new file mode 100644 index 0000000..a6961f1 --- /dev/null +++ b/flutter-cursor-templates/templates/rules/features/_stub.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/templates/rules/i18n/localization.mdc.tmpl b/flutter-cursor-templates/templates/rules/i18n/localization.mdc.tmpl index dff5105..d646f1a 100644 --- a/flutter-cursor-templates/templates/rules/i18n/localization.mdc.tmpl +++ b/flutter-cursor-templates/templates/rules/i18n/localization.mdc.tmpl @@ -1,6 +1,7 @@ --- description: "Localization / i18n conventions for {{PROJECT_NAME}}" -alwaysApply: true +globs: ["lib/l10n/**", "lib/**/*.dart", "test/**/*.dart"] +alwaysApply: false --- # Localization Standards — {{PROJECT_NAME}} diff --git a/flutter-cursor-templates/templates/rules/integrations/push-deeplink.mdc.tmpl b/flutter-cursor-templates/templates/rules/integrations/push-deeplink.mdc.tmpl new file mode 100644 index 0000000..aa8f82a --- /dev/null +++ b/flutter-cursor-templates/templates/rules/integrations/push-deeplink.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/templates/rules/telemetry/usage-logging.mdc.tmpl b/flutter-cursor-templates/templates/rules/telemetry/usage-logging.mdc.tmpl new file mode 100644 index 0000000..547ffd0 --- /dev/null +++ b/flutter-cursor-templates/templates/rules/telemetry/usage-logging.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/templates/rules/theming/theming.mdc.tmpl b/flutter-cursor-templates/templates/rules/theming/theming.mdc.tmpl new file mode 100644 index 0000000..971983c --- /dev/null +++ b/flutter-cursor-templates/templates/rules/theming/theming.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/templates/rules/universal/flutter-core.mdc.tmpl b/flutter-cursor-templates/templates/rules/universal/flutter-core.mdc.tmpl index bcafe6a..647e438 100644 --- a/flutter-cursor-templates/templates/rules/universal/flutter-core.mdc.tmpl +++ b/flutter-cursor-templates/templates/rules/universal/flutter-core.mdc.tmpl @@ -1,6 +1,7 @@ --- -description: "Core Flutter conventions for {{PROJECT_NAME}} — always applied" -alwaysApply: true +description: "Core Flutter conventions for {{PROJECT_NAME}}" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # Flutter Core Standards — {{PROJECT_NAME}} @@ -29,10 +30,7 @@ alwaysApply: true - Private members: `_camelCase` ## Imports -- Order: dart: → package: → relative -- Use relative imports within a feature; absolute for cross-feature -- Never import a feature's internal files from outside that feature - +{{IMPORT_POLICY_BLOCK}} ## Code quality - Max function length: 40 lines. Extract widgets and helpers aggressively - No `print()` in production code — use a logging package diff --git a/flutter-cursor-templates/templates/rules/universal/project-context.mdc.tmpl b/flutter-cursor-templates/templates/rules/universal/project-context.mdc.tmpl index 7d2f89c..5901ad4 100644 --- a/flutter-cursor-templates/templates/rules/universal/project-context.mdc.tmpl +++ b/flutter-cursor-templates/templates/rules/universal/project-context.mdc.tmpl @@ -1,6 +1,7 @@ --- -description: "Project context for {{PROJECT_NAME}} — always applied" -alwaysApply: true +description: "Stack summary and product context for {{PROJECT_NAME}}" +globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"] +alwaysApply: false --- # Project Context — {{PROJECT_NAME}} diff --git a/flutter-cursor-templates/templates/rules/universal/rule-authoring.mdc.tmpl b/flutter-cursor-templates/templates/rules/universal/rule-authoring.mdc.tmpl new file mode 100644 index 0000000..1b1e520 --- /dev/null +++ b/flutter-cursor-templates/templates/rules/universal/rule-authoring.mdc.tmpl @@ -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 diff --git a/flutter-cursor-templates/templates/rules/universal/ui-ux-standards.mdc.tmpl b/flutter-cursor-templates/templates/rules/universal/ui-ux-standards.mdc.tmpl index bfd07bb..2cde1db 100644 --- a/flutter-cursor-templates/templates/rules/universal/ui-ux-standards.mdc.tmpl +++ b/flutter-cursor-templates/templates/rules/universal/ui-ux-standards.mdc.tmpl @@ -1,6 +1,7 @@ --- -description: "UI/UX standards for {{PROJECT_NAME}} — always applied" -alwaysApply: true +description: "UI/UX standards for {{PROJECT_NAME}}" +globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"] +alwaysApply: false --- # UI / UX Standards — {{PROJECT_NAME}} diff --git a/flutter-cursor-templates/templates/telemetry/gitignore.tmpl b/flutter-cursor-templates/templates/telemetry/gitignore.tmpl new file mode 100644 index 0000000..6e3f01a --- /dev/null +++ b/flutter-cursor-templates/templates/telemetry/gitignore.tmpl @@ -0,0 +1,2 @@ +*.jsonl +*.log diff --git a/flutter-cursor-templates/templates/telemetry/log.sh.tmpl b/flutter-cursor-templates/templates/telemetry/log.sh.tmpl new file mode 100644 index 0000000..1910fc2 --- /dev/null +++ b/flutter-cursor-templates/templates/telemetry/log.sh.tmpl @@ -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" diff --git a/flutter-cursor-templates/tool/cursor_audit.sh b/flutter-cursor-templates/tool/cursor_audit.sh new file mode 100644 index 0000000..57dea0b --- /dev/null +++ b/flutter-cursor-templates/tool/cursor_audit.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Cursor rule hygiene — run from the Flutter app repository root. +# Usage: bash tool/cursor_audit.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +RULES_DIR="$REPO_ROOT/.cursor/rules" + +if [[ ! -d "$RULES_DIR" ]]; then + echo "No .cursor/rules at $RULES_DIR — run cursor_gen first." + exit 0 +fi + +echo "=== Cursor rule audit ===" +echo "Rules without fenced code blocks (\`\`\`):" +missing=0 +while IFS= read -r -d '' f; do + if ! grep -q '```' "$f" 2>/dev/null; then + echo " $f" + missing=1 + fi +done < <(find "$RULES_DIR" -name '*.mdc' -print0) + +if [[ "$missing" -eq 0 ]]; then + echo " (none — every .mdc contains at least one \`\`\` fence)" +fi + +echo "" +echo "Rules with alwaysApply: true (should be few):" +grep -rl 'alwaysApply: true' "$RULES_DIR" 2>/dev/null | while read -r p; do echo " $p"; done || true + +echo "" +feat_dir="$RULES_DIR/features" +if [[ -d "$feat_dir" ]]; then + echo "Stale feature rules (no lib/features/):" + stale=0 + for f in "$feat_dir"/*.mdc; do + [[ -e "$f" ]] || continue + name="$(basename "$f" .mdc)" + if [[ ! -d "$REPO_ROOT/lib/features/$name" ]]; then + echo " STALE: $f" + stale=1 + fi + done + if [[ "$stale" -eq 0 ]]; then + echo " (none)" + fi +else + echo "No rules/features/ directory." +fi + +echo "=== Done ==="