chore: update README and CLI usage for cursor_gen, version bump to 1.0.1

- 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.
This commit is contained in:
2026-05-13 12:08:52 +05:30
parent b05cdb7fbe
commit 54c66efe9b
157 changed files with 8233 additions and 570 deletions
@@ -0,0 +1,141 @@
#!/usr/bin/env ts-node
// arch-guard.ts — Pre-commit hook: enforces {{ARCHITECTURE}} import boundaries
// Generated for {{PROJECT_NAME}} ({{ARCHITECTURE}} architecture)
import * as fs from 'fs';
import * as path from 'path';
import { execSync } from 'child_process';
const RED = '\x1b[31m';
const YELLOW = '\x1b[33m';
const GREEN = '\x1b[32m';
const RESET = '\x1b[0m';
interface ArchRule {
sourcePattern: RegExp;
forbiddenImports: RegExp[];
message: string;
}
// Architecture-specific rules for {{ARCHITECTURE}}
const ARCH_RULES: ArchRule[] = [
...getArchRules()
];
function getArchRules(): ArchRule[] {
const arch = '{{ARCH_RAW}}';
if (arch === 'clean') {
return [
{
sourcePattern: /features\/\w+\/domain\//,
forbiddenImports: [/features\/\w+\/data\//, /features\/\w+\/presentation\//],
message: 'domain/ must not import from data/ or presentation/',
},
{
sourcePattern: /features\/\w+\/presentation\//,
forbiddenImports: [/features\/\w+\/data\//],
message: 'presentation/ must not import from data/ directly (use domain interfaces)',
},
];
}
if (arch === 'feature_first') {
return [
{
// A feature file must not import from another feature
sourcePattern: /features\/(\w+)\//,
forbiddenImports: [], // checked dynamically below
message: 'Features must not import from other feature folders',
},
];
}
if (arch === 'mvvm') {
return [
{
sourcePattern: /viewmodels\//,
forbiddenImports: [/package:flutter\//, /widgets\//],
message: 'ViewModels must not import Flutter widgets — they must be plain Dart testable',
},
];
}
return [];
}
function getChangedDartFiles(): string[] {
try {
const result = execSync('git diff --cached --name-only --diff-filter=ACM', { encoding: 'utf8' });
return result.split('\n').filter(f => f.endsWith('.dart') && fs.existsSync(f));
} catch {
return [];
}
}
function checkFile(filePath: string): string[] {
const violations: string[] = [];
const content = fs.readFileSync(filePath, 'utf8');
const imports = content.match(/^import\s+'[^']+'/gm) ?? [];
for (const rule of ARCH_RULES) {
if (!rule.sourcePattern.test(filePath)) continue;
// Feature-first cross-feature check
if ('{{ARCH_RAW}}' === 'feature_first') {
const srcFeatureMatch = filePath.match(/features\/(\w+)\//);
if (srcFeatureMatch) {
const srcFeature = srcFeatureMatch[1];
for (const imp of imports) {
const impFeatureMatch = imp.match(/features\/(\w+)\//);
if (impFeatureMatch && impFeatureMatch[1] !== srcFeature) {
violations.push(
`${RED}ARCH VIOLATION${RESET} in ${filePath}:\n` +
` ${YELLOW}${rule.message}${RESET}\n` +
` Import: ${imp}\n` +
` Fix: Move shared code to core/ or shared/`
);
}
}
}
continue;
}
// Standard forbidden import check
for (const imp of imports) {
for (const forbidden of rule.forbiddenImports) {
if (forbidden.test(imp)) {
violations.push(
`${RED}ARCH VIOLATION${RESET} in ${filePath}:\n` +
` ${YELLOW}${rule.message}${RESET}\n` +
` Import: ${imp}`
);
}
}
}
}
return violations;
}
const changedFiles = getChangedDartFiles();
if (changedFiles.length === 0) {
console.log(`${GREEN}✔ arch-guard: no Dart files changed${RESET}`);
process.exit(0);
}
console.log(`🏛 arch-guard checking ${changedFiles.length} file(s) for {{ARCHITECTURE}} violations...`);
const allViolations: string[] = [];
for (const file of changedFiles) {
allViolations.push(...checkFile(file));
}
if (allViolations.length > 0) {
console.error(`\n${RED}✘ Architecture boundary violations detected:${RESET}\n`);
for (const v of allViolations) console.error(v + '\n');
console.error(`Total: ${allViolations.length} violation(s). Fix before committing.`);
process.exit(1);
}
console.log(`${GREEN}✔ arch-guard: no violations found${RESET}`);
@@ -0,0 +1,32 @@
#!/usr/bin/env ts-node
// flutter-analyze.ts — Pre-commit hook: runs dart analyze and dart format check
import { execSync } from 'child_process';
const RED = '\x1b[31m';
const GREEN = '\x1b[32m';
const RESET = '\x1b[0m';
function run(cmd: string): { stdout: string; code: number } {
try {
const stdout = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
return { stdout, code: 0 };
} catch (e: any) {
return { stdout: e.stdout ?? e.message, code: e.status ?? 1 };
}
}
console.log('🔍 Running flutter analyze...');
const analyze = run('flutter analyze --no-congratulate');
if (analyze.code !== 0) {
console.error(`${RED}✘ flutter analyze failed:\n${analyze.stdout}${RESET}`);
process.exit(1);
}
console.log('🎨 Checking dart format...');
const format = run('dart format --output=none --set-exit-if-changed lib/ test/');
if (format.code !== 0) {
console.error(`${RED}✘ Unformatted files detected. Run: dart format lib/ test/${RESET}`);
process.exit(1);
}
console.log(`${GREEN}✔ flutter analyze + dart format passed${RESET}`);
@@ -0,0 +1,16 @@
#!/usr/bin/env ts-node
// grind-tests.ts — Pre-push hook: runs flutter test
import { execSync } from 'child_process';
const RED = '\x1b[31m';
const GREEN = '\x1b[32m';
const RESET = '\x1b[0m';
console.log('🧪 Running flutter test...');
try {
execSync('flutter test --coverage', { stdio: 'inherit' });
console.log(`${GREEN}✔ All tests passed${RESET}`);
} catch {
console.error(`${RED}✘ Tests failed. Fix failing tests before pushing.${RESET}`);
process.exit(1);
}
@@ -0,0 +1,19 @@
{
"hooks": [
{
"name": "pre-commit: flutter analyze",
"command": "npx ts-node .cursor/hooks/flutter-analyze.ts",
"events": ["pre-commit"]
},
{
"name": "pre-commit: arch guard",
"command": "npx ts-node .cursor/hooks/arch-guard.ts",
"events": ["pre-commit"]
},
{
"name": "pre-push: run tests",
"command": "npx ts-node .cursor/hooks/grind-tests.ts",
"events": ["pre-push"]
}
]
}