feat(flutter-cursor-templates): introduce MCP integration and conventions in project brief
- Added optional MCP integration settings in project-brief.yaml, allowing for environment-based server configurations. - Introduced conventions for strict package imports to enhance code organization and maintainability. - Updated brief schema to validate new MCP properties and ensure correct usage. - Implemented MCP JSON builder to generate .cursor/mcp.json based on project brief settings. - Enhanced resolver to include MCP configuration in generated files when enabled. This update improves integration capabilities and enforces coding standards across the project.
This commit is contained in:
@@ -116,6 +116,17 @@ localization:
|
|||||||
enabled: true
|
enabled: true
|
||||||
locales: ["en", "es", "fr"]
|
locales: ["en", "es", "fr"]
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Integrations & conventions (optional)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
integrations:
|
||||||
|
mcp:
|
||||||
|
enabled: false # true → .cursor/mcp.json (secrets only via env vars)
|
||||||
|
preset: auto # auto | minimal
|
||||||
|
|
||||||
|
conventions:
|
||||||
|
strict_package_imports: false
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Telemetry (Pillar 6) — local-only generation / usage log under .cursor/
|
# Telemetry (Pillar 6) — local-only generation / usage log under .cursor/
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -162,6 +162,38 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Pillar 6: Opt-in local telemetry for rule trigger analytics",
|
"description": "Pillar 6: Opt-in local telemetry for rule trigger analytics",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"integrations": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Optional third-party integrations for generated Cursor config",
|
||||||
|
"properties": {
|
||||||
|
"mcp": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "When true, emit .cursor/mcp.json with env-placeholder server stubs only"
|
||||||
|
},
|
||||||
|
"preset": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["auto", "minimal"],
|
||||||
|
"default": "auto",
|
||||||
|
"description": "minimal = empty mcpServers; auto = brief-derived stubs (still no committed secrets)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"conventions": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"strict_package_imports": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "When true, flutter-core rule enforces package: imports across feature boundaries"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class BriefLoader {
|
|||||||
final appCtx = yaml['app_context'] as YamlMap? ?? YamlMap();
|
final appCtx = yaml['app_context'] as YamlMap? ?? YamlMap();
|
||||||
final features = yaml['features'] as YamlMap? ?? YamlMap();
|
final features = yaml['features'] as YamlMap? ?? YamlMap();
|
||||||
final l10n = yaml['localization'] as YamlMap? ?? YamlMap();
|
final l10n = yaml['localization'] as YamlMap? ?? YamlMap();
|
||||||
|
final integrations = yaml['integrations'] as YamlMap? ?? YamlMap();
|
||||||
|
final mcp = integrations['mcp'] as YamlMap? ?? YamlMap();
|
||||||
|
final conventions = yaml['conventions'] as YamlMap? ?? YamlMap();
|
||||||
|
|
||||||
// Parse backends (can be "firebase+rest" shorthand or list)
|
// Parse backends (can be "firebase+rest" shorthand or list)
|
||||||
final backendRaw = stack['backend']?.toString() ?? 'rest';
|
final backendRaw = stack['backend']?.toString() ?? 'rest';
|
||||||
@@ -75,6 +78,11 @@ class BriefLoader {
|
|||||||
}(),
|
}(),
|
||||||
rolesEnabled: appCtx['roles_enabled'] as bool? ?? false,
|
rolesEnabled: appCtx['roles_enabled'] as bool? ?? false,
|
||||||
roleNames: _toStringList(appCtx['role_names']) ?? [],
|
roleNames: _toStringList(appCtx['role_names']) ?? [],
|
||||||
|
|
||||||
|
mcpConfigEnabled: mcp['enabled'] as bool? ?? false,
|
||||||
|
mcpPreset: mcp['preset']?.toString() ?? 'auto',
|
||||||
|
strictPackageImports:
|
||||||
|
conventions['strict_package_imports'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// mcp_json.dart — Builds .cursor/mcp.json from project brief (env placeholders only).
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
class McpJsonBuilder {
|
||||||
|
/// JSON string for Cursor MCP config. Never embed secrets — use `\${VAR}` in strings.
|
||||||
|
static String build(ProjectBrief brief) {
|
||||||
|
if (brief.mcpPreset == 'minimal') {
|
||||||
|
return const JsonEncoder.withIndent(' ')
|
||||||
|
.convert(<String, dynamic>{'mcpServers': <String, dynamic>{}});
|
||||||
|
}
|
||||||
|
|
||||||
|
final servers = <String, Map<String, dynamic>>{
|
||||||
|
'filesystem': {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-filesystem', '.'],
|
||||||
|
'description': 'Read/write project files under the workspace root',
|
||||||
|
},
|
||||||
|
'flutter-docs': {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-fetch'],
|
||||||
|
'env': {'BASE_URL': 'https://api.flutter.dev/flutter'},
|
||||||
|
'description': 'Flutter API documentation lookup',
|
||||||
|
},
|
||||||
|
'dart-pub': {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-fetch'],
|
||||||
|
'env': {'BASE_URL': 'https://pub.dev/api'},
|
||||||
|
'description': 'Pub.dev package metadata and versions',
|
||||||
|
},
|
||||||
|
'github': {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-github'],
|
||||||
|
'env': {'GITHUB_TOKEN': r'${GITHUB_TOKEN}'},
|
||||||
|
'description': 'GitHub issues and PR context (requires GITHUB_TOKEN)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (brief.backends.contains('firebase')) {
|
||||||
|
servers['firebase'] = {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-firebase'],
|
||||||
|
'env': {'FIREBASE_PROJECT': r'${FIREBASE_PROJECT_ID}'},
|
||||||
|
'description':
|
||||||
|
'Firebase project context (configure FIREBASE_PROJECT_ID)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brief.designSource == 'figma_mcp') {
|
||||||
|
servers['figma'] = {
|
||||||
|
'url': 'https://mcp.figma.com/mcp',
|
||||||
|
'type': 'http',
|
||||||
|
'headers': {
|
||||||
|
'Authorization': r'Bearer ${FIGMA_ACCESS_TOKEN}',
|
||||||
|
},
|
||||||
|
'description':
|
||||||
|
'Figma MCP — set FIGMA_ACCESS_TOKEN (never commit the value)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brief.backends.contains('supabase')) {
|
||||||
|
servers['supabase'] = {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@supabase/mcp-server-supabase@latest'],
|
||||||
|
'env': {
|
||||||
|
'SUPABASE_ACCESS_TOKEN': r'${SUPABASE_ACCESS_TOKEN}',
|
||||||
|
},
|
||||||
|
'description': 'Supabase MCP — token from env only',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brief.apiDocsFormat == 'openapi' && brief.apiDocsPath.isNotEmpty) {
|
||||||
|
servers['openapi-ref'] = {
|
||||||
|
'command': 'npx',
|
||||||
|
'args': ['-y', '@modelcontextprotocol/server-fetch'],
|
||||||
|
'env': {
|
||||||
|
'OPENAPI_SPEC_URL': r'${OPENAPI_SPEC_URL}',
|
||||||
|
},
|
||||||
|
'description':
|
||||||
|
'Optional fetch MCP — point OPENAPI_SPEC_URL at a hosted spec or file:// URL you expose locally',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return const JsonEncoder.withIndent(' ').convert({'mcpServers': servers});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,6 +59,15 @@ class ProjectBrief {
|
|||||||
final bool rolesEnabled;
|
final bool rolesEnabled;
|
||||||
final List<String> roleNames;
|
final List<String> roleNames;
|
||||||
|
|
||||||
|
/// When true, emit `.cursor/mcp.json` with safe env-placeholder servers.
|
||||||
|
final bool mcpConfigEnabled;
|
||||||
|
|
||||||
|
/// When [mcpConfigEnabled]: `minimal` (empty `mcpServers`) or `auto` (stubs from brief).
|
||||||
|
final String mcpPreset;
|
||||||
|
|
||||||
|
/// When true, generated flutter-core rule enforces package imports (no cross-feature relatives).
|
||||||
|
final bool strictPackageImports;
|
||||||
|
|
||||||
const ProjectBrief({
|
const ProjectBrief({
|
||||||
required this.projectName,
|
required this.projectName,
|
||||||
required this.packageId,
|
required this.packageId,
|
||||||
@@ -90,6 +99,9 @@ class ProjectBrief {
|
|||||||
this.themeVariants = const ['light', 'dark'],
|
this.themeVariants = const ['light', 'dark'],
|
||||||
this.rolesEnabled = false,
|
this.rolesEnabled = false,
|
||||||
this.roleNames = const [],
|
this.roleNames = const [],
|
||||||
|
this.mcpConfigEnabled = false,
|
||||||
|
this.mcpPreset = 'auto',
|
||||||
|
this.strictPackageImports = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Local snapshot for tooling (written as `cursor-gen-metadata.json` under the output dir).
|
/// Local snapshot for tooling (written as `cursor-gen-metadata.json` under the output dir).
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'mcp_json.dart';
|
||||||
import 'models.dart';
|
import 'models.dart';
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
@@ -10,22 +11,32 @@ class Renderer {
|
|||||||
required List<String> templateFiles,
|
required List<String> templateFiles,
|
||||||
required String templateSrc,
|
required String templateSrc,
|
||||||
}) async {
|
}) async {
|
||||||
final context = _buildContext(brief);
|
final baseContext = _buildContext(brief);
|
||||||
final output = <String, String>{};
|
final output = <String, String>{};
|
||||||
|
|
||||||
for (final key in templateFiles) {
|
for (final key in templateFiles) {
|
||||||
|
if (key == 'config/mcp-json') {
|
||||||
|
output['mcp.json'] = McpJsonBuilder.build(brief);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final outPath = _outputPath(key);
|
||||||
final tmplPath = _templatePath(templateSrc, key);
|
final tmplPath = _templatePath(templateSrc, key);
|
||||||
final file = File(tmplPath);
|
final file = File(tmplPath);
|
||||||
if (!file.existsSync()) {
|
if (!file.existsSync()) {
|
||||||
// Gracefully skip missing optional templates with a placeholder
|
output[outPath] = _missingTemplatePlaceholder(key);
|
||||||
output[_outputPath(key)] = _missingTemplatePlaceholder(key);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var content = await file.readAsString();
|
var content = await file.readAsString();
|
||||||
content = _substituteAll(content, context);
|
final ctx = Map<String, String>.from(baseContext);
|
||||||
_checkUnreplacedPlaceholders(
|
if (key.startsWith('rules/features/')) {
|
||||||
content, key); // Pillar 3: validate no broken {{VAR}}
|
final slug = p.basename(key);
|
||||||
output[_outputPath(key)] = content;
|
ctx['FEATURE_MODULE'] = slug;
|
||||||
|
ctx['FEATURE_MODULE_TITLE'] = _titleCase(slug);
|
||||||
|
}
|
||||||
|
content = _substituteAll(content, ctx);
|
||||||
|
_checkUnreplacedPlaceholders(content, key);
|
||||||
|
output[outPath] = content;
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -71,9 +82,35 @@ class Renderer {
|
|||||||
'TEST_PATTERN': _testPattern(brief.stateManagement),
|
'TEST_PATTERN': _testPattern(brief.stateManagement),
|
||||||
'LOCALES_LIST': brief.locales.join(', '),
|
'LOCALES_LIST': brief.locales.join(', '),
|
||||||
'TEMPLATE_VERSION': '1.0.4',
|
'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:<your_pubspec_name>/...` imports everywhere in `lib/` and `test/` — **no** relative `../` across feature boundaries (the brief `project.package` id is often the app bundle id; prefer the **pubspec.yaml `name`** for Dart imports)
|
||||||
|
- Barrel files (`index.dart`) at feature roots; do not wildcard re-export third-party packages
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
return '''
|
||||||
|
### Imports (default)
|
||||||
|
- Order: `dart:` → `package:` → relative
|
||||||
|
- Relative imports are allowed **within** the same feature directory; use `package:` imports for cross-feature code
|
||||||
|
- Never import another feature's internals from outside that feature
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
static String _substituteAll(String content, Map<String, String> ctx) {
|
static String _substituteAll(String content, Map<String, String> ctx) {
|
||||||
for (final entry in ctx.entries) {
|
for (final entry in ctx.entries) {
|
||||||
content = content.replaceAll('{{${entry.key}}}', entry.value);
|
content = content.replaceAll('{{${entry.key}}}', entry.value);
|
||||||
@@ -107,6 +144,25 @@ class Renderer {
|
|||||||
}
|
}
|
||||||
return p.join(templateSrc, 'hooks', '${p.basename(key)}.ts.tmpl');
|
return p.join(templateSrc, 'hooks', '${p.basename(key)}.ts.tmpl');
|
||||||
}
|
}
|
||||||
|
if (key.startsWith('commands/')) {
|
||||||
|
final name = p.basename(key);
|
||||||
|
return p.join(templateSrc, 'commands', '$name.md.tmpl');
|
||||||
|
}
|
||||||
|
if (key == 'onboarding/ONBOARDING') {
|
||||||
|
return p.join(templateSrc, 'onboarding', 'ONBOARDING.md.tmpl');
|
||||||
|
}
|
||||||
|
if (key.startsWith('telemetry/')) {
|
||||||
|
final leaf = p.basename(key);
|
||||||
|
if (leaf == 'gitignore') {
|
||||||
|
return p.join(templateSrc, 'telemetry', 'gitignore.tmpl');
|
||||||
|
}
|
||||||
|
if (leaf == 'log-sh') {
|
||||||
|
return p.join(templateSrc, 'telemetry', 'log.sh.tmpl');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key.startsWith('rules/features/')) {
|
||||||
|
return p.join(templateSrc, 'rules', 'features', '_stub.mdc.tmpl');
|
||||||
|
}
|
||||||
return p.join(templateSrc, '$key.mdc.tmpl');
|
return p.join(templateSrc, '$key.mdc.tmpl');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +178,22 @@ class Renderer {
|
|||||||
if (key.endsWith('hooks-json')) return 'hooks/hooks.json';
|
if (key.endsWith('hooks-json')) return 'hooks/hooks.json';
|
||||||
return 'hooks/${p.basename(key)}.ts';
|
return 'hooks/${p.basename(key)}.ts';
|
||||||
}
|
}
|
||||||
|
if (key.startsWith('commands/')) {
|
||||||
|
final name = p.basename(key);
|
||||||
|
return 'commands/$name.md';
|
||||||
|
}
|
||||||
|
if (key == 'onboarding/ONBOARDING') {
|
||||||
|
return 'ONBOARDING.md';
|
||||||
|
}
|
||||||
|
if (key == 'telemetry/gitignore') {
|
||||||
|
return 'telemetry/.gitignore';
|
||||||
|
}
|
||||||
|
if (key == 'telemetry/log-sh') {
|
||||||
|
return 'telemetry/log.sh';
|
||||||
|
}
|
||||||
|
if (key == 'config/mcp-json') {
|
||||||
|
return 'mcp.json';
|
||||||
|
}
|
||||||
return '${key.replaceAll('rules/', 'rules/')}.mdc';
|
return '${key.replaceAll('rules/', 'rules/')}.mdc';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ class Resolver {
|
|||||||
static List<String> resolve(ProjectBrief brief) {
|
static List<String> resolve(ProjectBrief brief) {
|
||||||
final files = <String>[];
|
final files = <String>[];
|
||||||
|
|
||||||
|
// ── Meta: rule authoring (first — governs other .mdc files) ─────────
|
||||||
|
files.add('rules/universal/rule-authoring');
|
||||||
|
|
||||||
// ── Universal (every project) ──────────────────────────────────────
|
// ── Universal (every project) ──────────────────────────────────────
|
||||||
files.addAll([
|
files.addAll([
|
||||||
'rules/universal/flutter-core',
|
'rules/universal/flutter-core',
|
||||||
@@ -15,6 +18,11 @@ class Resolver {
|
|||||||
'rules/universal/project-context',
|
'rules/universal/project-context',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// ── Theming tokens (when high contrast or extra variants) ───────────
|
||||||
|
if (_emitThemingRule(brief)) {
|
||||||
|
files.add('rules/theming/theming');
|
||||||
|
}
|
||||||
|
|
||||||
// ── Security (Pillar 5: always-on, not just for large/auth projects) ──
|
// ── Security (Pillar 5: always-on, not just for large/auth projects) ──
|
||||||
files.add('rules/security/security-standards');
|
files.add('rules/security/security-standards');
|
||||||
|
|
||||||
@@ -28,11 +36,19 @@ class Resolver {
|
|||||||
files.add('rules/architecture/${brief.architecture}');
|
files.add('rules/architecture/${brief.architecture}');
|
||||||
|
|
||||||
// ── Backend — one or more ─────────────────────────────────────────
|
// ── Backend — one or more ─────────────────────────────────────────
|
||||||
for (final b in brief.backends) files.add('rules/backend/$b');
|
for (final b in brief.backends) {
|
||||||
|
files.add('rules/backend/$b');
|
||||||
|
}
|
||||||
if (brief.specialFeatures.contains('realtime')) {
|
if (brief.specialFeatures.contains('realtime')) {
|
||||||
files.add('rules/backend/realtime');
|
files.add('rules/backend/realtime');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Push / deep linking ───────────────────────────────────────────
|
||||||
|
if (brief.specialFeatures.contains('push_notifications') ||
|
||||||
|
brief.specialFeatures.contains('deep_linking')) {
|
||||||
|
files.add('rules/integrations/push-deeplink');
|
||||||
|
}
|
||||||
|
|
||||||
// ── Routing — exactly one ─────────────────────────────────────────
|
// ── Routing — exactly one ─────────────────────────────────────────
|
||||||
files.add('rules/routing/${brief.routing}');
|
files.add('rules/routing/${brief.routing}');
|
||||||
|
|
||||||
@@ -67,6 +83,19 @@ class Resolver {
|
|||||||
files.add('rules/i18n/localization');
|
files.add('rules/i18n/localization');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── CI/CD + flavours ─────────────────────────────────────────────
|
||||||
|
if (brief.cicd != 'none' || brief.flavors.length > 1) {
|
||||||
|
files.add('rules/cicd/cicd');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Feature-scoped rule stubs ─────────────────────────────────────
|
||||||
|
final featureKeys = <String>{};
|
||||||
|
for (final m in brief.featureModules) {
|
||||||
|
final k = featureRuleKey(m);
|
||||||
|
if (k != null) featureKeys.add(k);
|
||||||
|
}
|
||||||
|
files.addAll(featureKeys);
|
||||||
|
|
||||||
// ── Skills ────────────────────────────────────────────────────────
|
// ── Skills ────────────────────────────────────────────────────────
|
||||||
files.addAll([
|
files.addAll([
|
||||||
'skills/scaffold-feature',
|
'skills/scaffold-feature',
|
||||||
@@ -78,8 +107,8 @@ class Resolver {
|
|||||||
'skills/explain-code',
|
'skills/explain-code',
|
||||||
]);
|
]);
|
||||||
if (brief.apiDocsFormat != 'none') files.add('skills/generate-api-client');
|
if (brief.apiDocsFormat != 'none') files.add('skills/generate-api-client');
|
||||||
if (brief.flavors.length > 1) files.add('skills/create-flavor');
|
if (brief.flavors.length > 1) files.add('skills/create-flavor');
|
||||||
if (brief.cicd.isNotEmpty) files.add('skills/deploy');
|
if (brief.cicd != 'none') files.add('skills/deploy');
|
||||||
|
|
||||||
// ── Agents ────────────────────────────────────────────────────────
|
// ── Agents ────────────────────────────────────────────────────────
|
||||||
files.addAll([
|
files.addAll([
|
||||||
@@ -97,11 +126,51 @@ class Resolver {
|
|||||||
files.add('agents/migration-agent');
|
files.add('agents/migration-agent');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Cursor workspace extras (reference architecture) ────────────────
|
||||||
|
files.addAll([
|
||||||
|
'root/.cursorignore',
|
||||||
|
'root/tool/cursor_audit.sh',
|
||||||
|
'onboarding/ONBOARDING',
|
||||||
|
'commands/build',
|
||||||
|
'commands/debug-issue',
|
||||||
|
'commands/verify-change',
|
||||||
|
'commands/explain-code',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (brief.mcpConfigEnabled) {
|
||||||
|
files.add('config/mcp-json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brief.telemetryOptIn) {
|
||||||
|
files.addAll([
|
||||||
|
'telemetry/gitignore',
|
||||||
|
'telemetry/log-sh',
|
||||||
|
'rules/telemetry/usage-logging',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
files.addAll(['root/AGENTS.md', 'root/lefthook.yaml']);
|
files.addAll(['root/AGENTS.md', 'root/lefthook.yaml']);
|
||||||
|
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `lib/features/<slug>/` ↔ `rules/features/<slug>.mdc`
|
||||||
|
static String? featureRuleKey(String module) {
|
||||||
|
var s = module
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replaceAll(RegExp(r'[^a-z0-9]+'), '_');
|
||||||
|
s = s.replaceAll(RegExp(r'_+'), '_');
|
||||||
|
s = s.replaceAll(RegExp(r'(^_+|_+$)'), '');
|
||||||
|
if (s.isEmpty) return null;
|
||||||
|
return 'rules/features/$s';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _emitThemingRule(ProjectBrief brief) {
|
||||||
|
return brief.themeVariants.contains('high_contrast') ||
|
||||||
|
brief.themeVariants.length > 2;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns human-readable description of why each file was included
|
/// Returns human-readable description of why each file was included
|
||||||
static Map<String, String> resolveWithReasons(ProjectBrief brief) {
|
static Map<String, String> resolveWithReasons(ProjectBrief brief) {
|
||||||
final resolved = resolve(brief);
|
final resolved = resolve(brief);
|
||||||
@@ -113,29 +182,100 @@ class Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String _reason(String key, ProjectBrief brief) {
|
static String _reason(String key, ProjectBrief brief) {
|
||||||
if (key.contains('universal')) return 'Always included';
|
if (key == 'rules/universal/rule-authoring') {
|
||||||
if (key.contains('security')) return 'Always included — Pillar 5';
|
return 'Always included — meta rules for authoring .cursor/rules';
|
||||||
|
}
|
||||||
|
if (key.contains('universal')) return 'Always included';
|
||||||
|
if (key == 'rules/theming/theming') {
|
||||||
|
return 'Theme variants include high contrast or extended set';
|
||||||
|
}
|
||||||
|
if (key.contains('security')) {
|
||||||
|
return 'Always included — Pillar 5';
|
||||||
|
}
|
||||||
if (key.contains('error-handling')) return 'Always included';
|
if (key.contains('error-handling')) return 'Always included';
|
||||||
if (key.contains('state-management')) return 'Matches stack.state_management: ${brief.stateManagement}';
|
if (key.contains('state-management')) {
|
||||||
if (key.contains('architecture')) return 'Matches stack.architecture: ${brief.architecture}';
|
return 'Matches stack.state_management: ${brief.stateManagement}';
|
||||||
if (key.contains('routing')) return 'Matches stack.routing: ${brief.routing}';
|
}
|
||||||
if (key.contains('testing-e2e')) return 'testing.depth includes e2e';
|
if (key.contains('architecture')) {
|
||||||
if (key.contains('testing')) return 'Matches state_management testing patterns';
|
return 'Matches stack.architecture: ${brief.architecture}';
|
||||||
if (key.contains('platform')) return 'Matches stack.platforms';
|
}
|
||||||
|
if (key == 'rules/integrations/push-deeplink') {
|
||||||
|
return 'features.special includes push_notifications or deep_linking';
|
||||||
|
}
|
||||||
|
if (key.contains('routing')) {
|
||||||
|
return 'Matches stack.routing: ${brief.routing}';
|
||||||
|
}
|
||||||
|
if (key.contains('testing-e2e')) {
|
||||||
|
return 'testing.depth includes e2e';
|
||||||
|
}
|
||||||
|
if (key.contains('testing')) {
|
||||||
|
return 'Matches state_management testing patterns';
|
||||||
|
}
|
||||||
|
if (key.contains('platform')) {
|
||||||
|
return 'Matches stack.platforms';
|
||||||
|
}
|
||||||
if (key.startsWith('hooks/')) {
|
if (key.startsWith('hooks/')) {
|
||||||
return 'stack.codegen non-empty — Cursor hooks for analyze, boundaries, and tests';
|
return 'stack.codegen non-empty — Cursor hooks for analyze, boundaries, and tests';
|
||||||
}
|
}
|
||||||
if (key.contains('codegen')) return 'Matches stack.codegen';
|
if (key.contains('codegen')) {
|
||||||
if (key.contains('i18n')) return 'localization.enabled: true';
|
return 'Matches stack.codegen';
|
||||||
if (key.contains('migration')) return 'state_management is GetX — migration guidance included';
|
}
|
||||||
if (key.contains('security-agent')) return 'scale: ${brief.scale} or auth is configured';
|
if (key.contains('i18n')) {
|
||||||
if (key == 'skills/build') return 'Always included — universal TDD-first feature implementation command';
|
return 'localization.enabled: true';
|
||||||
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 == 'rules/cicd/cicd') {
|
||||||
if (key == 'skills/explain-code') return 'Always included — explain-only walkthrough of code paths and stack behavior';
|
return 'environments.cicd is set or multiple flavors';
|
||||||
if (key.contains('api-client')) return 'api_docs.format is set';
|
}
|
||||||
if (key.contains('realtime')) return 'features.special contains realtime';
|
if (key.startsWith('rules/features/')) {
|
||||||
if (key.startsWith('root/')) return 'Repo-level companion files';
|
return 'Listed under features.modules in project-brief.yaml';
|
||||||
|
}
|
||||||
|
if (key.contains('migration')) {
|
||||||
|
return 'state_management is GetX — migration guidance included';
|
||||||
|
}
|
||||||
|
if (key.contains('security-agent')) {
|
||||||
|
return 'scale: ${brief.scale} or auth is configured';
|
||||||
|
}
|
||||||
|
if (key == 'skills/build') {
|
||||||
|
return 'Always included — universal TDD-first feature implementation command';
|
||||||
|
}
|
||||||
|
if (key == 'skills/debug-issue') {
|
||||||
|
return 'Always included — structured bug triage and evidence-first debugging';
|
||||||
|
}
|
||||||
|
if (key == 'skills/verify-change') {
|
||||||
|
return 'Always included — pre-PR verification checklist without full /build lifecycle';
|
||||||
|
}
|
||||||
|
if (key == 'skills/explain-code') {
|
||||||
|
return 'Always included — explain-only walkthrough of code paths and stack behavior';
|
||||||
|
}
|
||||||
|
if (key.contains('api-client')) {
|
||||||
|
return 'api_docs.format is set';
|
||||||
|
}
|
||||||
|
if (key.contains('realtime')) {
|
||||||
|
return 'features.special contains realtime';
|
||||||
|
}
|
||||||
|
if (key == 'root/.cursorignore') {
|
||||||
|
return 'Root ignore patterns for Cursor indexing';
|
||||||
|
}
|
||||||
|
if (key == 'root/tool/cursor_audit.sh') {
|
||||||
|
return 'Maintenance script for rule drift checks';
|
||||||
|
}
|
||||||
|
if (key == 'onboarding/ONBOARDING') {
|
||||||
|
return 'Team onboarding for Cursor layout and slash skills';
|
||||||
|
}
|
||||||
|
if (key.startsWith('commands/')) {
|
||||||
|
return 'Project slash command → skill mapping';
|
||||||
|
}
|
||||||
|
if (key == 'config/mcp-json') {
|
||||||
|
return 'integrations.mcp.enabled: true in project-brief.yaml';
|
||||||
|
}
|
||||||
|
if (key == 'telemetry/gitignore' ||
|
||||||
|
key == 'telemetry/log-sh' ||
|
||||||
|
key == 'rules/telemetry/usage-logging') {
|
||||||
|
return 'telemetry_opt_in: true — local usage logging helpers';
|
||||||
|
}
|
||||||
|
if (key.startsWith('root/')) {
|
||||||
|
return 'Repo-level companion files';
|
||||||
|
}
|
||||||
return 'Included';
|
return 'Included';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class Validator {
|
|||||||
};
|
};
|
||||||
static const _validApiFormats = {'openapi', 'postman', 'markdown', 'none'};
|
static const _validApiFormats = {'openapi', 'postman', 'markdown', 'none'};
|
||||||
static const _validThemeVariants = {'light', 'dark', 'high_contrast'};
|
static const _validThemeVariants = {'light', 'dark', 'high_contrast'};
|
||||||
|
static const _validMcpPresets = {'auto', 'minimal'};
|
||||||
|
|
||||||
static Future<ValidationResult> validateFile(String path) async {
|
static Future<ValidationResult> validateFile(String path) async {
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
@@ -178,6 +179,18 @@ class Validator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final integrations = yaml['integrations'] as YamlMap?;
|
||||||
|
if (integrations != null) {
|
||||||
|
final mcp = integrations['mcp'] as YamlMap?;
|
||||||
|
if (mcp != null) {
|
||||||
|
final preset = mcp['preset']?.toString();
|
||||||
|
if (preset != null && !_validMcpPresets.contains(preset)) {
|
||||||
|
warnings.add(
|
||||||
|
'integrations.mcp.preset "$preset" is not valid. Use: ${_validMcpPresets.join(", ")}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid: errors.isEmpty,
|
isValid: errors.isEmpty,
|
||||||
errors: errors,
|
errors: errors,
|
||||||
@@ -214,6 +227,10 @@ class Validator {
|
|||||||
if (brief.rolesEnabled && brief.roleNames.isEmpty) {
|
if (brief.rolesEnabled && brief.roleNames.isEmpty) {
|
||||||
warnings.add('roles_enabled is true but role_names is empty');
|
warnings.add('roles_enabled is true but role_names is empty');
|
||||||
}
|
}
|
||||||
|
if (!_validMcpPresets.contains(brief.mcpPreset)) {
|
||||||
|
warnings.add(
|
||||||
|
'integrations.mcp.preset "${brief.mcpPreset}" is not valid. Use: ${_validMcpPresets.join(", ")}');
|
||||||
|
}
|
||||||
|
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid: errors.isEmpty, errors: errors, warnings: warnings);
|
isValid: errors.isEmpty, errors: errors, warnings: warnings);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
@@ -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).
|
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}}`
|
- **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`)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# {{PROJECT_NAME}} — generated by cursor_gen; adjust commands to your repo
|
# {{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:
|
pre-commit:
|
||||||
commands:
|
commands:
|
||||||
flutter-analyze:
|
flutter-analyze:
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# {{PROJECT_NAME}} — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for {{PROJECT_NAME}}
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **{{FLAVORS_LIST}}**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — {{E2E_TOOL}} on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **{{CICD_TOOL}}** (`{{CICD_RAW}}`)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module {{FEATURE_MODULE}} — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/{{FEATURE_MODULE}}/**", "test/**/{{FEATURE_MODULE}}/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — {{FEATURE_MODULE_TITLE}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `{{FEATURE_MODULE}}` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Localization / i18n conventions for {{PROJECT_NAME}}"
|
description: "Localization / i18n conventions for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["lib/l10n/**", "lib/**/*.dart", "test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Localization Standards — {{PROJECT_NAME}}
|
# Localization Standards — {{PROJECT_NAME}}
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: Push notifications and deep linking for {{PROJECT_NAME}}
|
||||||
|
globs: ["lib/**/*.dart", "android/**", "ios/**", "test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Push & deep linking — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Special capabilities from `project-brief.yaml`: **{{SPECIAL_FEATURES}}**. Native and server configuration must stay consistent.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- **Push:** request permissions through the platform flow; handle denial gracefully; no silent failures on token registration
|
||||||
|
- **Routing:** deep links and notification taps MUST go through **{{ROUTING}}** (no ad-hoc `Navigator` stacks for inbound links)
|
||||||
|
- **Payloads:** map FCM/APNs/Supabase payloads to domain models in the data layer — no JSON parsing scattered in widgets
|
||||||
|
- **iOS:** exercise push on a **physical device** when possible (simulator limitations)
|
||||||
|
- **Android:** declare required permissions explicitly; target API behaviour for POST_NOTIFICATIONS where applicable
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- Unit-test payload → domain mapping and routing targets
|
||||||
|
- Integration/E2E: cold start, background, and foreground tap-to-open flows when `testing.depth` allows
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
description: "Policy for optional local AI usage logs under .cursor/telemetry/"
|
||||||
|
globs: [".cursor/telemetry/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Telemetry — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
When `telemetry_opt_in: true`, this repo may record **local-only** generation or usage notes under `.cursor/telemetry/`. This is **not** production analytics.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- **Never** commit secrets, tokens, or PII into JSONL or shell history
|
||||||
|
- Prefer redacted summaries over raw prompts
|
||||||
|
- Add `.cursor/telemetry/*.jsonl` to `.gitignore` unless your team explicitly version-controls sanitized samples
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Append one JSON object per line (JSONL) with ISO timestamps and event type
|
||||||
|
- Rotate or truncate files if they grow beyond a few MB
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
description: Semantic colours and theme extensions for {{PROJECT_NAME}}
|
||||||
|
globs: ["lib/**/*.dart", "test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Theming — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Theme variants from brief: **{{THEME_SUMMARY}}**.{{HIGH_CONTRAST_NOTE}}
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Prefer **`ThemeExtension`** (or design-system equivalents) for app-specific colours and radii — avoid raw `Color(0xFF...)` in feature code except in central token definitions
|
||||||
|
- Use **`Theme.of(context).colorScheme`** / `TextTheme` for Material-aligned roles where appropriate
|
||||||
|
- **High contrast:** when supported, verify WCAG contrast for text and controls; never rely on colour alone for state (pair with icon or label){{HIGH_CONTRAST_UX_LINE}}
|
||||||
|
- Touch targets: respect platform minimums (e.g. ~48 logical pixels) for interactive widgets
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Hard-coded `Colors.*` scattered across feature widgets instead of theme tokens
|
||||||
|
- Ignoring `MediaQuery.of(context).platformBrightness` / high-contrast modes when the product claims support
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Core Flutter conventions for {{PROJECT_NAME}} — always applied"
|
description: "Core Flutter conventions for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Flutter Core Standards — {{PROJECT_NAME}}
|
# Flutter Core Standards — {{PROJECT_NAME}}
|
||||||
@@ -29,10 +30,7 @@ alwaysApply: true
|
|||||||
- Private members: `_camelCase`
|
- Private members: `_camelCase`
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
- Order: dart: → package: → relative
|
{{IMPORT_POLICY_BLOCK}}
|
||||||
- Use relative imports within a feature; absolute for cross-feature
|
|
||||||
- Never import a feature's internal files from outside that feature
|
|
||||||
|
|
||||||
## Code quality
|
## Code quality
|
||||||
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
||||||
- No `print()` in production code — use a logging package
|
- No `print()` in production code — use a logging package
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Project context for {{PROJECT_NAME}} — always applied"
|
description: "Stack summary and product context for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project Context — {{PROJECT_NAME}}
|
# Project Context — {{PROJECT_NAME}}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
description: How Cursor rules in this repository must be written and maintained.
|
||||||
|
globs: [".cursor/rules/**/*.mdc"]
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rule authoring — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Rules are version-controlled contracts for AI assistants. Poor rules waste context and silently steer every edit.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- One focused concern per file; split broad topics instead of one mega-rule
|
||||||
|
- Every rule MUST have a clear `description` in frontmatter (one sentence)
|
||||||
|
- Prefer `alwaysApply: false` with **narrow** `globs` for domain rules — reserve `alwaysApply: true` for meta and safety
|
||||||
|
- `globs` must be as specific as possible — never `["**/*"]` unless tooling requires it
|
||||||
|
- Code samples in rules MUST be valid for this project (Dart/Flutter/YAML as appropriate)
|
||||||
|
- Deprecated guidance is removed, not left commented out
|
||||||
|
- Each substantive rule includes **Context** (why), **Constraints** (must/must not), and where helpful **Patterns** / **Anti-patterns**
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Domain rules (testing, l10n, a feature) with `alwaysApply: true` — burns context
|
||||||
|
- Rules with no concrete examples when the topic is code-facing
|
||||||
|
- Stale feature rules after modules are removed — run `tool/cursor_audit.sh` periodically
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "UI/UX standards for {{PROJECT_NAME}} — always applied"
|
description: "UI/UX standards for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# UI / UX Standards — {{PROJECT_NAME}}
|
# UI / UX Standards — {{PROJECT_NAME}}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*.jsonl
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Append a JSONL usage line (local only). Requires jq when passing structured payload.
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
LOG="${ROOT}/telemetry/usage.jsonl"
|
||||||
|
mkdir -p "$(dirname "$LOG")"
|
||||||
|
echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"event\":\"${1:-note}\",\"detail\":\"${2:-}\"}" >>"$LOG"
|
||||||
@@ -8,6 +8,7 @@ import '../src/resolver.dart';
|
|||||||
import '../src/renderer.dart';
|
import '../src/renderer.dart';
|
||||||
import '../src/validator.dart';
|
import '../src/validator.dart';
|
||||||
import '../src/models.dart';
|
import '../src/models.dart';
|
||||||
|
import '../src/mcp_json.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// ─── Resolver tests ─────────────────────────────────────────────────────────
|
// ─── Resolver tests ─────────────────────────────────────────────────────────
|
||||||
@@ -17,10 +18,26 @@ void main() {
|
|||||||
final files = Resolver.resolve(kBlocCleanFirebaseBrief);
|
final files = Resolver.resolve(kBlocCleanFirebaseBrief);
|
||||||
|
|
||||||
// Universal — always present
|
// Universal — always present
|
||||||
|
expect(files.first, equals('rules/universal/rule-authoring'));
|
||||||
expect(files, contains('rules/universal/flutter-core'));
|
expect(files, contains('rules/universal/flutter-core'));
|
||||||
expect(files, contains('rules/universal/ui-ux-standards'));
|
expect(files, contains('rules/universal/ui-ux-standards'));
|
||||||
expect(files, contains('rules/universal/project-context'));
|
expect(files, contains('rules/universal/project-context'));
|
||||||
|
|
||||||
|
// CI/CD rule when cicd set or multiple flavors
|
||||||
|
expect(files, contains('rules/cicd/cicd'));
|
||||||
|
|
||||||
|
// Feature stubs from brief
|
||||||
|
expect(files, contains('rules/features/auth'));
|
||||||
|
expect(files, contains('rules/features/home'));
|
||||||
|
expect(files, contains('rules/features/products'));
|
||||||
|
|
||||||
|
// Cursor workspace extras
|
||||||
|
expect(files, contains('root/.cursorignore'));
|
||||||
|
expect(files, contains('root/tool/cursor_audit.sh'));
|
||||||
|
expect(files, contains('onboarding/ONBOARDING'));
|
||||||
|
expect(files, contains('commands/build'));
|
||||||
|
expect(files, contains('commands/debug-issue'));
|
||||||
|
|
||||||
// Security — always present (Pillar 5)
|
// Security — always present (Pillar 5)
|
||||||
expect(files, contains('rules/security/security-standards'));
|
expect(files, contains('rules/security/security-standards'));
|
||||||
|
|
||||||
@@ -57,6 +74,9 @@ void main() {
|
|||||||
expect(files, containsNot('rules/architecture/feature_first'));
|
expect(files, containsNot('rules/architecture/feature_first'));
|
||||||
expect(files, containsNot('rules/routing/getx_nav'));
|
expect(files, containsNot('rules/routing/getx_nav'));
|
||||||
|
|
||||||
|
expect(files, contains('commands/verify-change'));
|
||||||
|
expect(files, contains('commands/explain-code'));
|
||||||
|
expect(files, containsNot('config/mcp-json'));
|
||||||
expect(files, contains('root/AGENTS.md'));
|
expect(files, contains('root/AGENTS.md'));
|
||||||
expect(files, contains('root/lefthook.yaml'));
|
expect(files, contains('root/lefthook.yaml'));
|
||||||
});
|
});
|
||||||
@@ -72,6 +92,175 @@ void main() {
|
|||||||
expect(files, containsNot('agents/migration-agent'));
|
expect(files, containsNot('agents/migration-agent'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('featureRuleKey sanitizes module names', () {
|
||||||
|
expect(Resolver.featureRuleKey('My Cart'), 'rules/features/my_cart');
|
||||||
|
expect(Resolver.featureRuleKey('auth'), 'rules/features/auth');
|
||||||
|
expect(Resolver.featureRuleKey(' '), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('MCP template key when integrations.mcp.enabled', () {
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'McpApp',
|
||||||
|
packageId: 'com.test.mcp',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['firebase'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
mcpConfigEnabled: true,
|
||||||
|
);
|
||||||
|
expect(Resolver.resolve(brief), contains('config/mcp-json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Push/deeplink rule when special features request it', () {
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'PushApp',
|
||||||
|
packageId: 'com.test.push',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['rest'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: ['push_notifications'],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
);
|
||||||
|
expect(Resolver.resolve(brief), contains('rules/integrations/push-deeplink'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Theming rule when high contrast requested', () {
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'A11yApp',
|
||||||
|
packageId: 'com.test.a11y',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['rest'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
themeVariants: const ['light', 'dark', 'high_contrast'],
|
||||||
|
);
|
||||||
|
expect(Resolver.resolve(brief), contains('rules/theming/theming'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Telemetry helpers when telemetry_opt_in', () {
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'TelApp',
|
||||||
|
packageId: 'com.test.tel',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['rest'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
telemetryOptIn: true,
|
||||||
|
);
|
||||||
|
final files = Resolver.resolve(brief);
|
||||||
|
expect(files, contains('telemetry/gitignore'));
|
||||||
|
expect(files, contains('telemetry/log-sh'));
|
||||||
|
expect(files, contains('rules/telemetry/usage-logging'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('McpJsonBuilder minimal preset emits empty mcpServers', () {
|
||||||
|
const brief = ProjectBrief(
|
||||||
|
projectName: 'M',
|
||||||
|
packageId: 'com.m.m',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: ['rest'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: ['ios'],
|
||||||
|
codegenTools: [],
|
||||||
|
flavors: ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: [],
|
||||||
|
localPaths: [],
|
||||||
|
featureModules: [],
|
||||||
|
specialFeatures: [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: ['en'],
|
||||||
|
mcpConfigEnabled: true,
|
||||||
|
mcpPreset: 'minimal',
|
||||||
|
);
|
||||||
|
expect(McpJsonBuilder.build(brief), contains('"mcpServers": {}'));
|
||||||
|
});
|
||||||
|
|
||||||
test('GetX + MVC includes migration-agent', () {
|
test('GetX + MVC includes migration-agent', () {
|
||||||
final files = Resolver.resolve(kGetxMvcRestBrief);
|
final files = Resolver.resolve(kGetxMvcRestBrief);
|
||||||
expect(files, contains('agents/migration-agent'));
|
expect(files, contains('agents/migration-agent'));
|
||||||
@@ -245,6 +434,51 @@ void main() {
|
|||||||
'Default template dir should resolve real arch-guard.ts.tmpl');
|
'Default template dir should resolve real arch-guard.ts.tmpl');
|
||||||
expect(content, contains('arch-guard'));
|
expect(content, contains('arch-guard'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('mcp.json renders from config/mcp-json key', () async {
|
||||||
|
final templateDir = _templateDir();
|
||||||
|
if (!Directory(templateDir).existsSync()) {
|
||||||
|
markTestSkipped('Template directory not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final brief = ProjectBrief(
|
||||||
|
projectName: 'McpApp',
|
||||||
|
packageId: 'com.test.mcp',
|
||||||
|
description: '',
|
||||||
|
scale: 'small',
|
||||||
|
stateManagement: 'bloc',
|
||||||
|
routing: 'gorouter',
|
||||||
|
architecture: 'clean',
|
||||||
|
backends: const ['firebase'],
|
||||||
|
auth: 'none',
|
||||||
|
platforms: const ['ios'],
|
||||||
|
codegenTools: const [],
|
||||||
|
flavors: const ['dev'],
|
||||||
|
cicd: 'none',
|
||||||
|
testingDepth: 'unit_widget',
|
||||||
|
e2eTool: 'patrol',
|
||||||
|
designSource: 'none',
|
||||||
|
figmaUrl: '',
|
||||||
|
apiDocsFormat: 'none',
|
||||||
|
apiDocsPath: '',
|
||||||
|
referenceRepos: const [],
|
||||||
|
localPaths: const [],
|
||||||
|
featureModules: const [],
|
||||||
|
specialFeatures: const [],
|
||||||
|
i18nEnabled: false,
|
||||||
|
locales: const ['en'],
|
||||||
|
mcpConfigEnabled: true,
|
||||||
|
);
|
||||||
|
final rendered = await Renderer.render(
|
||||||
|
brief: brief,
|
||||||
|
templateFiles: const ['config/mcp-json'],
|
||||||
|
templateSrc: templateDir,
|
||||||
|
);
|
||||||
|
final json = rendered['mcp.json']!;
|
||||||
|
expect(json, contains('mcpServers'));
|
||||||
|
expect(json, contains('filesystem'));
|
||||||
|
expect(json, contains('firebase'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Validator tests ─────────────────────────────────────────────────────────
|
// ─── Validator tests ─────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — TestApp
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
+2
-1
@@ -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).
|
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`
|
- **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`)
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
# TestApp — generated by cursor_gen; adjust commands to your repo
|
# 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:
|
pre-commit:
|
||||||
commands:
|
commands:
|
||||||
flutter-analyze:
|
flutter-analyze:
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# TestApp — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for TestApp
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — TestApp
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **dev, prod**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — patrol on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **GitHub Actions** (`github_actions`)
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module auth — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/auth/**", "test/**/auth/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Auth
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `auth` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module home — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/home/**", "test/**/home/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Home
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `home` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module products — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/products/**", "test/**/products/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Products
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `products` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+6
-5
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Core Flutter conventions for TestApp — always applied"
|
description: "Core Flutter conventions for TestApp"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Flutter Core Standards — TestApp
|
# Flutter Core Standards — TestApp
|
||||||
@@ -29,9 +30,9 @@ alwaysApply: true
|
|||||||
- Private members: `_camelCase`
|
- Private members: `_camelCase`
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
- Order: dart: → package: → relative
|
### Imports (strict — `conventions.strict_package_imports: true`)
|
||||||
- Use relative imports within a feature; absolute for cross-feature
|
- Use `package:<your_pubspec_name>/...` imports everywhere in `lib/` and `test/` — **no** relative `../` across feature boundaries (the brief `project.package` id is often the app bundle id; prefer the **pubspec.yaml `name`** for Dart imports)
|
||||||
- Never import a feature's internal files from outside that feature
|
- Barrel files (`index.dart`) at feature roots; do not wildcard re-export third-party packages
|
||||||
|
|
||||||
## Code quality
|
## Code quality
|
||||||
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Project context for TestApp — always applied"
|
description: "Stack summary and product context for TestApp"
|
||||||
alwaysApply: true
|
globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project Context — TestApp
|
# Project Context — TestApp
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
description: How Cursor rules in this repository must be written and maintained.
|
||||||
|
globs: [".cursor/rules/**/*.mdc"]
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rule authoring — TestApp
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Rules are version-controlled contracts for AI assistants. Poor rules waste context and silently steer every edit.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- One focused concern per file; split broad topics instead of one mega-rule
|
||||||
|
- Every rule MUST have a clear `description` in frontmatter (one sentence)
|
||||||
|
- Prefer `alwaysApply: false` with **narrow** `globs` for domain rules — reserve `alwaysApply: true` for meta and safety
|
||||||
|
- `globs` must be as specific as possible — never `["**/*"]` unless tooling requires it
|
||||||
|
- Code samples in rules MUST be valid for this project (Dart/Flutter/YAML as appropriate)
|
||||||
|
- Deprecated guidance is removed, not left commented out
|
||||||
|
- Each substantive rule includes **Context** (why), **Constraints** (must/must not), and where helpful **Patterns** / **Anti-patterns**
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Domain rules (testing, l10n, a feature) with `alwaysApply: true` — burns context
|
||||||
|
- Rules with no concrete examples when the topic is code-facing
|
||||||
|
- Stale feature rules after modules are removed — run `tool/cursor_audit.sh` periodically
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "UI/UX standards for TestApp — always applied"
|
description: "UI/UX standards for TestApp"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# UI / UX Standards — TestApp
|
# UI / UX Standards — TestApp
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — LegacyApp
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
@@ -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).
|
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`
|
- **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`)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# LegacyApp — generated by cursor_gen; adjust commands to your repo
|
# 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:
|
pre-commit:
|
||||||
commands:
|
commands:
|
||||||
flutter-analyze:
|
flutter-analyze:
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# LegacyApp — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"filesystem": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-filesystem",
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"description": "Read/write project files under the workspace root"
|
||||||
|
},
|
||||||
|
"flutter-docs": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-fetch"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"BASE_URL": "https://api.flutter.dev/flutter"
|
||||||
|
},
|
||||||
|
"description": "Flutter API documentation lookup"
|
||||||
|
},
|
||||||
|
"dart-pub": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-fetch"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"BASE_URL": "https://pub.dev/api"
|
||||||
|
},
|
||||||
|
"description": "Pub.dev package metadata and versions"
|
||||||
|
},
|
||||||
|
"github": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-github"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
|
||||||
|
},
|
||||||
|
"description": "GitHub issues and PR context (requires GITHUB_TOKEN)"
|
||||||
|
},
|
||||||
|
"openapi-ref": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-fetch"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"OPENAPI_SPEC_URL": "${OPENAPI_SPEC_URL}"
|
||||||
|
},
|
||||||
|
"description": "Optional fetch MCP — point OPENAPI_SPEC_URL at a hosted spec or file:// URL you expose locally"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for LegacyApp
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — LegacyApp
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **dev, prod**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — patrol on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **Codemagic** (`codemagic`)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module auth — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/auth/**", "test/**/auth/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Auth
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `auth` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module dashboard — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/dashboard/**", "test/**/dashboard/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Dashboard
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `dashboard` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+7
-5
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Core Flutter conventions for LegacyApp — always applied"
|
description: "Core Flutter conventions for LegacyApp"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Flutter Core Standards — LegacyApp
|
# Flutter Core Standards — LegacyApp
|
||||||
@@ -29,9 +30,10 @@ alwaysApply: true
|
|||||||
- Private members: `_camelCase`
|
- Private members: `_camelCase`
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
- Order: dart: → package: → relative
|
### Imports (default)
|
||||||
- Use relative imports within a feature; absolute for cross-feature
|
- Order: `dart:` → `package:` → relative
|
||||||
- Never import a feature's internal files from outside that feature
|
- Relative imports are allowed **within** the same feature directory; use `package:` imports for cross-feature code
|
||||||
|
- Never import another feature's internals from outside that feature
|
||||||
|
|
||||||
## Code quality
|
## Code quality
|
||||||
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Project context for LegacyApp — always applied"
|
description: "Stack summary and product context for LegacyApp"
|
||||||
alwaysApply: true
|
globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project Context — LegacyApp
|
# Project Context — LegacyApp
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
description: How Cursor rules in this repository must be written and maintained.
|
||||||
|
globs: [".cursor/rules/**/*.mdc"]
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rule authoring — LegacyApp
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Rules are version-controlled contracts for AI assistants. Poor rules waste context and silently steer every edit.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- One focused concern per file; split broad topics instead of one mega-rule
|
||||||
|
- Every rule MUST have a clear `description` in frontmatter (one sentence)
|
||||||
|
- Prefer `alwaysApply: false` with **narrow** `globs` for domain rules — reserve `alwaysApply: true` for meta and safety
|
||||||
|
- `globs` must be as specific as possible — never `["**/*"]` unless tooling requires it
|
||||||
|
- Code samples in rules MUST be valid for this project (Dart/Flutter/YAML as appropriate)
|
||||||
|
- Deprecated guidance is removed, not left commented out
|
||||||
|
- Each substantive rule includes **Context** (why), **Constraints** (must/must not), and where helpful **Patterns** / **Anti-patterns**
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Domain rules (testing, l10n, a feature) with `alwaysApply: true` — burns context
|
||||||
|
- Rules with no concrete examples when the topic is code-facing
|
||||||
|
- Stale feature rules after modules are removed — run `tool/cursor_audit.sh` periodically
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "UI/UX standards for LegacyApp — always applied"
|
description: "UI/UX standards for LegacyApp"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# UI / UX Standards — LegacyApp
|
# UI / UX Standards — LegacyApp
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — TaskFlow
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
+2
-1
@@ -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).
|
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`
|
- **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`)
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
# TaskFlow — generated by cursor_gen; adjust commands to your repo
|
# 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:
|
pre-commit:
|
||||||
commands:
|
commands:
|
||||||
flutter-analyze:
|
flutter-analyze:
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# TaskFlow — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for TaskFlow
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — TaskFlow
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **dev, prod**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — patrol on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **GitHub Actions** (`github_actions`)
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module auth — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/auth/**", "test/**/auth/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — Auth
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `auth` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module 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
|
||||||
+21
@@ -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
|
||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Localization / i18n conventions for TaskFlow"
|
description: "Localization / i18n conventions for TaskFlow"
|
||||||
alwaysApply: true
|
globs: ["lib/l10n/**", "lib/**/*.dart", "test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Localization Standards — TaskFlow
|
# Localization Standards — TaskFlow
|
||||||
|
|||||||
+23
@@ -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
|
||||||
+7
-5
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Core Flutter conventions for TaskFlow — always applied"
|
description: "Core Flutter conventions for TaskFlow"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Flutter Core Standards — TaskFlow
|
# Flutter Core Standards — TaskFlow
|
||||||
@@ -29,9 +30,10 @@ alwaysApply: true
|
|||||||
- Private members: `_camelCase`
|
- Private members: `_camelCase`
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
- Order: dart: → package: → relative
|
### Imports (default)
|
||||||
- Use relative imports within a feature; absolute for cross-feature
|
- Order: `dart:` → `package:` → relative
|
||||||
- Never import a feature's internal files from outside that feature
|
- Relative imports are allowed **within** the same feature directory; use `package:` imports for cross-feature code
|
||||||
|
- Never import another feature's internals from outside that feature
|
||||||
|
|
||||||
## Code quality
|
## Code quality
|
||||||
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
- Max function length: 40 lines. Extract widgets and helpers aggressively
|
||||||
|
|||||||
+6
-3
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Project context for TaskFlow — always applied"
|
description: "Stack summary and product context for TaskFlow"
|
||||||
alwaysApply: true
|
globs: ["project-brief.yaml", ".cursor/**/*.md", ".cursor/**/*.mdc", "pubspec.yaml"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project Context — TaskFlow
|
# 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.
|
_No local paths listed._ Add monorepo packages or sibling folders under `references.local_paths` in project-brief.yaml when relevant.
|
||||||
|
|
||||||
## Product UX / themes & roles
|
## Product UX / themes & roles
|
||||||
- **Theme variants:** light, dark
|
- **Theme variants:** light, dark, high contrast
|
||||||
- **Roles:** Not enabled (`app_context.roles_enabled: false`).
|
- **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
|
## Reviews — which rule owns what
|
||||||
- **Theme, colors, typography, spacing/radius tokens** → `ui-ux-standards.mdc` (widgets read `Theme.of(context)` only)
|
- **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)
|
- **User-visible copy & locales** → `localization.mdc` (ARB / `AppLocalizations`; no UI string literals)
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
description: How Cursor rules in this repository must be written and maintained.
|
||||||
|
globs: [".cursor/rules/**/*.mdc"]
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rule authoring — 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
|
||||||
+4
-2
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "UI/UX standards for TaskFlow — always applied"
|
description: "UI/UX standards for TaskFlow"
|
||||||
alwaysApply: true
|
globs: ["lib/**/*.dart", "test/**/*.dart", "integration_test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# UI / UX Standards — TaskFlow
|
# UI / UX Standards — TaskFlow
|
||||||
@@ -46,3 +47,4 @@ alwaysApply: true
|
|||||||
- Minimum contrast ratio: 4.5:1 (WCAG AA)
|
- Minimum contrast ratio: 4.5:1 (WCAG AA)
|
||||||
- Test with TalkBack / VoiceOver before each release
|
- 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).
|
||||||
|
|||||||
@@ -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',
|
||||||
|
);
|
||||||
+35
@@ -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"
|
||||||
@@ -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<void> main() async {
|
||||||
|
final templateSrc = _templateDir();
|
||||||
|
if (!Directory(templateSrc).existsSync()) {
|
||||||
|
stderr.writeln('Template dir not found: $templateSrc');
|
||||||
|
exitCode = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final cases = <String, ProjectBrief>{
|
||||||
|
'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'));
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
End-to-end feature implementation (research, TDD, integration tests, verification). Follow the workflow and constraints in `@file:.cursor/skills/build/SKILL.md`. Use `project-brief.yaml` as the source of truth for stack and platforms.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Structured bug triage and evidence-first debugging. Follow `@file:.cursor/skills/debug-issue/SKILL.md`. Gather reproduction steps, logs, and failing commands before proposing fixes.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Explain-only walkthrough of code paths and stack behavior (no edits). Follow `@file:.cursor/skills/explain-code/SKILL.md`. Do not modify source files unless the user explicitly asks.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Pre-PR verification checklist (analyze, tests, hooks) without full /build lifecycle. Follow `@file:.cursor/skills/verify-change/SKILL.md` for the change in scope.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cursor — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Open this repo in **Cursor** so `.cursor/rules/` and `.cursor/skills/` load automatically.
|
||||||
|
2. Read **`rules/universal/rule-authoring.mdc`** — how we maintain AI rules.
|
||||||
|
3. Stack and product context live in **`project-brief.yaml`** at the repo root; regenerate `.cursor/` with `cursor_gen` after changing it.
|
||||||
|
4. **Slash skills** (primary workflows): open the Command Palette and use the project commands, or reference:
|
||||||
|
- `.cursor/skills/build/SKILL.md` — end-to-end feature implementation
|
||||||
|
- `.cursor/skills/debug-issue/SKILL.md` — structured debugging
|
||||||
|
- `.cursor/skills/verify-change/SKILL.md` — pre-PR verification
|
||||||
|
- `.cursor/skills/explain-code/SKILL.md` — explain-only walkthroughs
|
||||||
|
5. **Agents** (`.cursor/agents/*.mdc`) are reusable reviewer personas — attach when asking for code review or focused passes.
|
||||||
|
6. **Custom overrides**: files under `.cursor/custom/` and `CURSOR:CUSTOM` blocks in generated files are preserved on `cursor_gen --refresh` (see generator docs).
|
||||||
|
|
||||||
|
## Optional
|
||||||
|
|
||||||
|
- **`integrations.mcp.enabled`** in `project-brief.yaml` — generates `.cursor/mcp.json` with env-based server entries (no secrets in git).
|
||||||
|
- **`telemetry_opt_in: true`** — emits local telemetry helpers under `.cursor/telemetry/` (never commit usage logs if you enable logging).
|
||||||
|
- Run **`bash tool/cursor_audit.sh`** from the project root periodically to catch stale feature rules and overly broad `alwaysApply` usage.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Build artefacts
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.gr.dart
|
||||||
|
*.config.dart
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
firebase_options.dart
|
||||||
|
google-services.json
|
||||||
|
GoogleService-Info.plist
|
||||||
|
|
||||||
|
# Large binary assets
|
||||||
|
assets/fonts/
|
||||||
|
assets/videos/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
@@ -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).
|
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}}`
|
- **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`)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# {{PROJECT_NAME}} — generated by cursor_gen; adjust commands to your repo
|
# {{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:
|
pre-commit:
|
||||||
commands:
|
commands:
|
||||||
flutter-analyze:
|
flutter-analyze:
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# {{PROJECT_NAME}} — Cursor rule hygiene (generated by cursor_gen)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
CURSOR="${ROOT}/.cursor"
|
||||||
|
RULES="${CURSOR}/rules"
|
||||||
|
|
||||||
|
echo "== cursor_audit (${ROOT}) =="
|
||||||
|
|
||||||
|
if [[ ! -d "$CURSOR" ]]; then
|
||||||
|
echo "ERROR: missing ${CURSOR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES" ]]; then
|
||||||
|
ac="$(grep -R "alwaysApply: true" "$RULES" --include='*.mdc' 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
echo "alwaysApply: true occurrences in .cursor/rules: ${ac}"
|
||||||
|
echo " (expect a small number — meta/safety; prefer scoped globs for domain rules)"
|
||||||
|
else
|
||||||
|
echo "WARN: no ${RULES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$RULES/features" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$RULES/features"/*.mdc; do
|
||||||
|
base="$(basename "$f" .mdc)"
|
||||||
|
if [[ ! -d "${ROOT}/lib/features/${base}" ]] && [[ ! -d "${ROOT}/lib/feature_${base}" ]]; then
|
||||||
|
echo "WARN: rules/features/${base}.mdc has no obvious lib/features/${base} folder — update features.modules or lib layout"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK — review warnings above after brief or folder renames"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: CI/CD, flavours, and quality gates for {{PROJECT_NAME}}
|
||||||
|
globs: [".github/**", "codemagic.yaml", "Makefile", "pubspec.yaml", "fastlane/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD & flavours — {{PROJECT_NAME}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Pipeline and flavour setup must stay aligned with how the app is built and released.
|
||||||
|
|
||||||
|
## Flavours
|
||||||
|
- Documented in `project-brief.yaml`: **{{FLAVORS_LIST}}**
|
||||||
|
- Use `--flavor` / `-t lib/main_<flavour>.dart` (or your project’s entrypoints) consistently across local, CI, and store builds
|
||||||
|
- Configuration via `--dart-define` / `--dart-define-from-file` — **never** hardcode secrets in source
|
||||||
|
|
||||||
|
## Quality gates (recommended stages)
|
||||||
|
- **Lint:** `dart format --set-exit-if-changed .` and `flutter analyze`
|
||||||
|
- **Unit + widget:** `flutter test` (with coverage if the team tracks it)
|
||||||
|
- **Goldens:** fixed device/locale; CI fails on drift unless intentionally updated
|
||||||
|
- **Smoke build:** e.g. `flutter build apk` or `flutter build ios --no-codesign` for the primary flavour
|
||||||
|
- **E2E:** when `testing.depth` includes e2e — {{E2E_TOOL}} on CI devices or Firebase Test Lab
|
||||||
|
|
||||||
|
## Cursor integration
|
||||||
|
- After substantive edits: run analyze and relevant tests before PR
|
||||||
|
- For release-oriented tasks, use the **deploy** skill at `.cursor/skills/deploy/SKILL.md` when present
|
||||||
|
|
||||||
|
## CI/CD tool
|
||||||
|
- Selected in brief: **{{CICD_TOOL}}** (`{{CICD_RAW}}`)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
description: "Feature module {{FEATURE_MODULE}} — contracts, boundaries, and ownership (fill after design)"
|
||||||
|
globs: ["lib/**/{{FEATURE_MODULE}}/**", "test/**/{{FEATURE_MODULE}}/**"]
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature — {{FEATURE_MODULE_TITLE}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
This stub was generated from `features.modules` in `project-brief.yaml`. Use it to capture **public contracts** (routes, DTOs, events) and **dependencies** for `{{FEATURE_MODULE}}` so agents do not invent cross-feature wiring.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- List external dependencies (other features, packages, backend endpoints) explicitly
|
||||||
|
- Document invariants (auth required, idempotency, offline behavior) when known
|
||||||
|
- Update or delete this file when the module is removed or renamed — run `bash tool/cursor_audit.sh` to catch drift
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
- Link to key entry points: primary screen(s), state holder(s), repository interface(s)
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
- Empty file left forever — either fill it or delete the module entry from the brief
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: "Localization / i18n conventions for {{PROJECT_NAME}}"
|
description: "Localization / i18n conventions for {{PROJECT_NAME}}"
|
||||||
alwaysApply: true
|
globs: ["lib/l10n/**", "lib/**/*.dart", "test/**/*.dart"]
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Localization Standards — {{PROJECT_NAME}}
|
# Localization Standards — {{PROJECT_NAME}}
|
||||||
|
|||||||
@@ -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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user