54c66efe9b
- Changed CLI usage instructions from `dart run cursor_gen` to `cursor_gen` for global activation. - Updated project-brief.yaml example and README to reflect new command usage. - Added app_context section in project-brief.yaml for theme variants and RBAC roles. - Fixed bundled template resolution for local and global installs to prevent 'Template not found' errors. - Version bump to 1.0.1 with corresponding updates in CHANGELOG and pubspec.yaml.
93 lines
3.5 KiB
Dart
93 lines
3.5 KiB
Dart
// 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';
|
|
|
|
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.',
|
|
};
|
|
}
|