Initial commit of the Flutter Cursor Generator project, including the core generator tool, project brief schema, example project setup, and CI configuration. Added README documentation outlining repository structure, quick start guide, and detailed descriptions of features and architecture pillars.

This commit is contained in:
2026-05-12 22:29:55 +05:30
commit 6dfb9a8aa5
72 changed files with 4542 additions and 0 deletions
@@ -0,0 +1,86 @@
// telemetry.dart — Pillar 6: Opt-in local telemetry & feedback loop
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'logger.dart';
const _telemetryFile = '.cursor/telemetry.json';
class Telemetry {
/// Record a generation event (local-only, opt-in)
static Future<void> record({
required String projectName,
required String outputDir,
required List<String> templateFiles,
}) async {
final telemetryPath = p.join(outputDir, 'telemetry.json');
Map<String, dynamic> data;
try {
final file = File(telemetryPath);
data = file.existsSync()
? jsonDecode(await file.readAsString()) as Map<String, dynamic>
: _emptyData(projectName);
} catch (_) {
data = _emptyData(projectName);
}
final generations = data['generations'] as List;
generations.add({
'timestamp': DateTime.now().toIso8601String(),
'templateCount': templateFiles.length,
'templates': templateFiles,
});
// Keep only last 100 events
if (generations.length > 100) {
data['generations'] = generations.sublist(generations.length - 100);
}
data['lastGeneratedAt'] = DateTime.now().toIso8601String();
data['totalGenerations'] = (data['totalGenerations'] as int? ?? 0) + 1;
final file = File(telemetryPath);
await file.parent.create(recursive: true);
await file.writeAsString(const JsonEncoder.withIndent(' ').convert(data));
}
/// Print the telemetry report
static Future<void> printReport({required String outputDir}) async {
final file = File(p.join(outputDir, 'telemetry.json'));
if (!file.existsSync()) {
Logger.warn('No telemetry data found. Ensure telemetry_opt_in: true in project-brief.yaml');
return;
}
final data = jsonDecode(await file.readAsString()) as Map<String, dynamic>;
Logger.info('\n📊 Telemetry Report — ${data['projectName']}');
Logger.info('' * 42);
Logger.info('Total generations: ${data['totalGenerations']}');
Logger.info('Last generated: ${data['lastGeneratedAt']}');
final generations = data['generations'] as List;
if (generations.isNotEmpty) {
final last = generations.last as Map;
final templates = (last['templates'] as List).cast<String>();
Logger.info('\nLast generation used ${templates.length} templates:');
final ruleCount = templates.where((t) => t.startsWith('rules/')).length;
final agentCount = templates.where((t) => t.startsWith('agents/')).length;
final skillCount = templates.where((t) => t.startsWith('skills/')).length;
Logger.dim(' Rules: $ruleCount | Agents: $agentCount | Skills: $skillCount');
}
Logger.info('\n💡 Quarterly template quality review checklist:');
Logger.dim(' □ Review AI code review comments — which rules are most violated?');
Logger.dim(' □ Ask teams: which agents are actually consulted vs. ignored?');
Logger.dim(' □ Check if generated rules reduced hallucinations vs. last quarter');
Logger.dim(' □ Identify brief combinations that produce the most AI errors');
Logger.dim(' □ Update templates based on feedback, bump version in CHANGELOG.md');
}
static Map<String, dynamic> _emptyData(String projectName) => {
'projectName': projectName,
'totalGenerations': 0,
'lastGeneratedAt': '',
'generations': [],
'note': 'Local-only telemetry. Never sent anywhere. opt-in via telemetry_opt_in: true.',
};
}