chore: bump cursor_templates_version to 1.0.4 and update related files

- Updated cursor_templates_version in project-brief.yaml to 1.0.4 for reproducibility.
- Enhanced CHANGELOG with fixes and features for version 1.0.4, including improved template resolution for global installs.
- Updated pubspec.yaml and VERSION file to reflect the new version.
- Added new templates for AGENTS.md and lefthook.yaml to the generator.
- Adjusted tests to include new root-level files in the output.

This release addresses template resolution issues and introduces new repo-level configuration files.
This commit is contained in:
2026-05-14 12:25:13 +05:30
parent 3ee83389bd
commit 44b5d814d4
31 changed files with 956 additions and 172 deletions
@@ -70,7 +70,7 @@ class Renderer {
'ARCH_IMPORT_RULES': _archImportRules(brief.architecture),
'TEST_PATTERN': _testPattern(brief.stateManagement),
'LOCALES_LIST': brief.locales.join(', '),
'TEMPLATE_VERSION': '1.0.1',
'TEMPLATE_VERSION': '1.0.4',
};
}
@@ -93,6 +93,9 @@ class Renderer {
}
static String _templatePath(String templateSrc, String key) {
if (key.startsWith('root/')) {
return p.join(templateSrc, '$key.tmpl');
}
if (key.startsWith('skills/')) {
final skillName = p.basename(key);
return p.join(templateSrc, 'skills', skillName, 'SKILL.md.tmpl');
@@ -108,6 +111,9 @@ class Renderer {
}
static String _outputPath(String key) {
if (key.startsWith('root/')) {
return '__root__/${key.substring(5)}';
}
if (key.startsWith('skills/')) {
final name = p.basename(key);
return 'skills/$name/SKILL.md';
@@ -94,6 +94,8 @@ class Resolver {
files.add('agents/migration-agent');
}
files.addAll(['root/AGENTS.md', 'root/lefthook.yaml']);
return files;
}
@@ -127,6 +129,7 @@ class Resolver {
if (key == 'skills/build') return 'Always included — universal TDD-first feature implementation command';
if (key.contains('api-client')) return 'api_docs.format is set';
if (key.contains('realtime')) return 'features.special contains realtime';
if (key.startsWith('root/')) return 'Repo-level companion files';
return 'Included';
}
}
@@ -8,17 +8,19 @@ import 'models.dart';
import 'logger.dart';
const _lockFileName = '.cursor-gen-lock.json';
const _currentVersion = '1.0.1';
/// Current flutter-cursor-templates bundle version (lock file, wizard, --check-updates).
const kCursorTemplatesVersion = '1.0.4';
class VersionManager {
/// Check if the project's locked version differs from the current template version
static Future<VersionStatus> check({required ProjectBrief brief}) async {
final locked = brief.cursorTemplatesVersion ?? 'unset';
final hasUpdate = locked != _currentVersion && locked != 'unset';
final hasUpdate = locked != kCursorTemplatesVersion && locked != 'unset';
return VersionStatus(
hasUpdate: hasUpdate,
currentVersion: locked,
latestVersion: _currentVersion,
latestVersion: kCursorTemplatesVersion,
);
}
@@ -30,7 +32,7 @@ class VersionManager {
}) async {
final briefHash = await _fileHash(briefPath);
final lock = {
'templateVersion': _currentVersion,
'templateVersion': kCursorTemplatesVersion,
'generatedAt': DateTime.now().toIso8601String(),
'briefHash': briefHash,
'projectName': brief.projectName,
@@ -72,15 +74,15 @@ class VersionManager {
final lockedVersion = lock['templateVersion'] as String? ?? 'unknown';
Logger.info('Template version check:');
Logger.info(' Locked: $lockedVersion');
Logger.info(' Latest: $_currentVersion');
Logger.info(' Latest: $kCursorTemplatesVersion');
if (lockedVersion == _currentVersion) {
if (lockedVersion == kCursorTemplatesVersion) {
Logger.success(' ✔ You are on the latest template version.');
} else {
Logger.warn(' ⚠ Update available!');
Logger.info('\nTo update:');
Logger.info(
' 1. Update cursor_templates_version in project-brief.yaml to "$_currentVersion"');
' 1. Update cursor_templates_version in project-brief.yaml to "$kCursorTemplatesVersion"');
Logger.info(' 2. Run: cursor_gen --diff (preview changes)');
Logger.info(' 3. Run: cursor_gen --refresh (apply updates)');
Logger.info(
@@ -2,6 +2,7 @@
import 'dart:io';
import 'logger.dart';
import 'version_manager.dart';
class Wizard {
static Future<void> run({required String outputPath}) async {
@@ -80,6 +81,22 @@ class Wizard {
Logger.info('');
// Features
final modulesRaw = _ask(
'Feature modules (comma-separated, optional)', hint: '');
answers['feature_modules'] = _splitComma(modulesRaw);
answers['special_features'] = _askMultiChoice(
'Special capabilities (optional)',
[
'realtime',
'push_notifications',
'deep_linking',
'offline_first',
],
defaults: []);
Logger.info('');
// Environments
final flavorsInput =
_ask('Build flavors (comma-separated)', hint: 'dev,staging,prod');
@@ -92,10 +109,60 @@ class Wizard {
answers['testing_depth'] = _askChoice(
'Testing depth', ['unit_widget', 'integration', 'e2e', 'full'],
defaultIdx: 0);
final testingDepth = answers['testing_depth'] as String;
if (testingDepth == 'e2e' || testingDepth == 'full') {
answers['e2e_tool'] = _askChoice(
'E2E tool', ['patrol', 'maestro'], defaultIdx: 0);
} else {
answers['e2e_tool'] = 'patrol';
}
Logger.info('');
// Design (validator-aligned sources)
answers['design_source'] = _askChoice('Design source', [
'figma_mcp',
'figma_manual',
'native_ref',
'html_ref',
'none',
], defaultIdx: 4);
final designSource = answers['design_source'] as String;
if (designSource == 'figma_mcp' || designSource == 'figma_manual') {
answers['figma_url'] =
_ask('Figma file URL', hint: 'https://www.figma.com/design/...');
} else {
answers['figma_url'] = '';
}
Logger.info('');
// API docs
answers['api_docs_format'] = _askChoice(
'API docs format', ['openapi', 'postman', 'markdown', 'none'],
defaultIdx: 3);
final apiFmt = answers['api_docs_format'] as String;
if (apiFmt != 'none') {
answers['api_docs_path'] =
_ask('Path to API spec (repo-relative)', hint: 'docs/api.yaml');
} else {
answers['api_docs_path'] = '';
}
Logger.info('');
// Localization
final l10n = _askBool('Enable localization / i18n?', defaultYes: false);
answers['i18n'] = l10n;
if (l10n) {
final locRaw =
_ask('Locale codes (comma-separated)', hint: 'en');
var locales = _splitComma(locRaw);
if (locales.isEmpty) locales = ['en'];
answers['locales'] = locales;
} else {
answers['locales'] = <String>['en'];
}
// Telemetry opt-in (Pillar 6)
final telemetry = _askBool(
@@ -183,13 +250,18 @@ class Wizard {
final refLocals = List<String>.from(a['local_paths'] as List);
final themes = List<String>.from(a['theme_variants'] as List);
final roles = List<String>.from(a['role_names'] as List);
final featureMods = List<String>.from(a['feature_modules'] as List);
final specialFeats = List<String>.from(a['special_features'] as List);
final locales = List<String>.from(a['locales'] as List);
final figmaUrl = a['figma_url'] as String;
final apiPath = a['api_docs_path'] as String;
return '''# project-brief.yaml — cursor_gen configuration
# Generated by cursor_gen --wizard
# Run: cursor_gen to generate .cursor/
# Run: cursor_gen --refresh to update after changes
# Pillar 1: Pin to template version for reproducibility
cursor_templates_version: "1.0.1"
cursor_templates_version: "$kCursorTemplatesVersion"
project:
name: "${a['name']}"
@@ -216,15 +288,15 @@ environments:
testing:
depth: "${a['testing_depth']}"
e2e_tool: "patrol"
e2e_tool: "${a['e2e_tool']}"
design:
source: "none"
figma_url: ""
source: "${a['design_source']}"
figma_url: "${_yamlEsc(figmaUrl)}"
api_docs:
format: "none"
path: ""
format: "${a['api_docs_format']}"
path: "${_yamlEsc(apiPath)}"
references:
repos: [${_yamlQStringList(refRepos)}]
@@ -236,12 +308,12 @@ app_context:
role_names: [${_yamlQStringList(roles)}]
features:
modules: []
special: []
modules: [${_yamlQStringList(featureMods)}]
special: [${_yamlQStringList(specialFeats)}]
localization:
enabled: ${a['i18n']}
locales: ["en"]
locales: [${_yamlQStringList(locales)}]
# Pillar 6: Opt-in local telemetry (logs rule trigger frequency locally)
telemetry_opt_in: ${a['telemetry']}