agent-flutter 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +57 -0
  2. package/bin/agent-flutter.js +8 -0
  3. package/package.json +36 -0
  4. package/src/cli.js +494 -0
  5. package/templates/shared/.ignore +24 -0
  6. package/templates/shared/TEMPLATES.md +54 -0
  7. package/templates/shared/rules/document-workflow-function.md +98 -0
  8. package/templates/shared/rules/integration-api.md +3 -0
  9. package/templates/shared/rules/new-template-project.md +148 -0
  10. package/templates/shared/rules/ui.md +268 -0
  11. package/templates/shared/rules/unit-test.md +3 -0
  12. package/templates/shared/rules/widget-test.md +3 -0
  13. package/templates/shared/skills/dart-best-practices/SKILL.md +20 -0
  14. package/templates/shared/skills/dart-language-patterns/SKILL.md +47 -0
  15. package/templates/shared/skills/dart-model-reuse/SKILL.md +38 -0
  16. package/templates/shared/skills/dart-tooling-ci/SKILL.md +38 -0
  17. package/templates/shared/skills/flutter-assets-management/SKILL.md +78 -0
  18. package/templates/shared/skills/flutter-bloc-state-management/SKILL.md +48 -0
  19. package/templates/shared/skills/flutter-bloc-state-management/references/REFERENCE.md +19 -0
  20. package/templates/shared/skills/flutter-bloc-state-management/references/auth-bloc-example.md +96 -0
  21. package/templates/shared/skills/flutter-bloc-state-management/references/property-based-state.md +103 -0
  22. package/templates/shared/skills/flutter-dependency-injection-injectable/SKILL.md +64 -0
  23. package/templates/shared/skills/flutter-dependency-injection-injectable/references/REFERENCE.md +15 -0
  24. package/templates/shared/skills/flutter-dependency-injection-injectable/references/modules.md +42 -0
  25. package/templates/shared/skills/flutter-error-handling/SKILL.md +25 -0
  26. package/templates/shared/skills/flutter-error-handling/references/REFERENCE.md +38 -0
  27. package/templates/shared/skills/flutter-error-handling/references/error-mapping.md +44 -0
  28. package/templates/shared/skills/flutter-navigation-manager/SKILL.md +81 -0
  29. package/templates/shared/skills/flutter-navigation-manager/references/REFERENCE.md +71 -0
  30. package/templates/shared/skills/flutter-navigation-manager/references/router-config.md +57 -0
  31. package/templates/shared/skills/flutter-standard-lib-src-architecture/SKILL.md +89 -0
  32. package/templates/shared/skills/flutter-standard-lib-src-architecture/references/REFERENCE.md +19 -0
  33. package/templates/shared/skills/flutter-standard-lib-src-architecture/references/folder-structure.md +35 -0
  34. package/templates/shared/skills/flutter-standard-lib-src-architecture/references/modular-injection.md +27 -0
  35. package/templates/shared/skills/flutter-standard-lib-src-architecture/references/shared-core.md +29 -0
  36. package/templates/shared/skills/flutter-standard-lib-src-architecture-dependency-rules/SKILL.md +58 -0
  37. package/templates/shared/skills/flutter-standard-lib-src-architecture-dependency-rules/references/REFERENCE.md +113 -0
  38. package/templates/shared/skills/flutter-standard-lib-src-architecture-dependency-rules/references/repository-mapping.md +48 -0
  39. package/templates/shared/skills/flutter-ui-widgets/SKILL.md +152 -0
  40. package/templates/shared/skills/getx-localization-standard/SKILL.md +101 -0
  41. package/templates/shared/skills/getx-localization-standard/references/new-page-localization.example.md +61 -0
  42. package/templates/shared/skills/ui-documentation-workflow/SKILL.md +55 -0
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # agent-flutter
2
+
3
+ CLI to initialize a reusable Flutter Skill/Rule pack for multiple AI IDEs.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx agent-flutter@latest init
9
+ ```
10
+
11
+ By default `init` installs adapters for all supported IDEs:
12
+ - Trae
13
+ - Codex
14
+ - Cursor
15
+ - Windsurf
16
+ - Cline
17
+
18
+ ## Commands
19
+
20
+ ```bash
21
+ npx agent-flutter@latest init --ide all --cwd /path/to/project --force
22
+ npx agent-flutter@latest init --ide trae,codex
23
+ npx agent-flutter@latest list --cwd /path/to/project
24
+ ```
25
+
26
+ ## Publish to npm (one-time setup, Trusted Publishing)
27
+
28
+ 1. On npm package settings, add a **Trusted publisher**:
29
+ - Provider: `GitHub Actions`
30
+ - Owner: `thangnv1991`
31
+ - Repository: `agent-flutter`
32
+ - Workflow file: `.github/workflows/publish.yml`
33
+ 2. Push a semantic version tag:
34
+
35
+ ```bash
36
+ npm version patch
37
+ git push origin main --follow-tags
38
+ ```
39
+
40
+ GitHub Actions workflow `.github/workflows/publish.yml` will publish automatically on tags `v*.*.*` without `NPM_TOKEN`.
41
+
42
+ ## Release shortcuts
43
+
44
+ ```bash
45
+ npm run release:patch
46
+ npm run release:minor
47
+ npm run release:major
48
+ ```
49
+
50
+ ## Installed files
51
+
52
+ - Shared pack: `.agent-flutter/`
53
+ - Trae: `.trae/`
54
+ - Codex: `AGENTS.md`
55
+ - Cursor: `.cursor/rules/agent-flutter.mdc`
56
+ - Windsurf: `.windsurf/rules/agent-flutter.md`
57
+ - Cline: `.clinerules/agent-flutter.md`
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from '../src/cli.js';
3
+
4
+ runCli(process.argv.slice(2)).catch((error) => {
5
+ const message = error instanceof Error ? error.message : String(error);
6
+ console.error(`[agent-flutter] ${message}`);
7
+ process.exitCode = 1;
8
+ });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "agent-flutter",
3
+ "version": "0.1.1",
4
+ "description": "Portable Flutter skill/rule pack initializer for multiple AI IDEs.",
5
+ "type": "module",
6
+ "bin": {
7
+ "agent-flutter": "./bin/agent-flutter.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "templates",
13
+ "README.md"
14
+ ],
15
+ "keywords": [
16
+ "ai",
17
+ "agent",
18
+ "flutter",
19
+ "skills",
20
+ "rules",
21
+ "trae",
22
+ "cursor",
23
+ "codex"
24
+ ],
25
+ "license": "MIT",
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "scripts": {
30
+ "start": "node ./bin/agent-flutter.js",
31
+ "init:demo": "node ./bin/agent-flutter.js init --cwd ../tmp-agent-flutter-demo --ide all --force",
32
+ "release:patch": "npm version patch && git push origin main --follow-tags",
33
+ "release:minor": "npm version minor && git push origin main --follow-tags",
34
+ "release:major": "npm version major && git push origin main --follow-tags"
35
+ }
36
+ }
package/src/cli.js ADDED
@@ -0,0 +1,494 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+
5
+ const SUPPORTED_IDES = ['trae', 'codex', 'cursor', 'windsurf', 'cline'];
6
+
7
+ const USAGE = `
8
+ agent-flutter
9
+
10
+ Usage:
11
+ npx agent-flutter@latest init [--ide all|trae,codex,cursor,windsurf,cline] [--cwd <project_dir>] [--force]
12
+ npx agent-flutter@latest list [--cwd <project_dir>]
13
+
14
+ Commands:
15
+ init Install shared Flutter skills/rules and IDE adapters.
16
+ list Print available skills/rules from the shared pack.
17
+ `;
18
+
19
+ export async function runCli(argv) {
20
+ if (argv.length === 0 || argv.includes('-h') || argv.includes('--help')) {
21
+ console.log(USAGE.trim());
22
+ return;
23
+ }
24
+
25
+ const command = argv[0];
26
+ const options = parseOptions(argv.slice(1));
27
+
28
+ switch (command) {
29
+ case 'init':
30
+ await runInit(options);
31
+ return;
32
+ case 'list':
33
+ await runList(options);
34
+ return;
35
+ default:
36
+ throw new Error(`Unknown command: ${command}`);
37
+ }
38
+ }
39
+
40
+ function parseOptions(args) {
41
+ const values = new Map();
42
+ const flags = new Set();
43
+
44
+ for (let i = 0; i < args.length; i += 1) {
45
+ const arg = args[i];
46
+ if (arg === '--force') {
47
+ flags.add('force');
48
+ continue;
49
+ }
50
+
51
+ if (!arg.startsWith('--')) {
52
+ throw new Error(`Unknown argument: ${arg}`);
53
+ }
54
+
55
+ const key = arg.slice(2);
56
+ const next = args[i + 1];
57
+ if (!next || next.startsWith('--')) {
58
+ throw new Error(`Missing value for option: ${arg}`);
59
+ }
60
+ values.set(key, next);
61
+ i += 1;
62
+ }
63
+
64
+ return {
65
+ get(key, fallback = null) {
66
+ return values.has(key) ? values.get(key) : fallback;
67
+ },
68
+ hasFlag(flag) {
69
+ return flags.has(flag);
70
+ },
71
+ };
72
+ }
73
+
74
+ async function runInit(options) {
75
+ const packageRoot = getPackageRoot();
76
+ const templateRoot = path.join(packageRoot, 'templates', 'shared');
77
+ await assertDirExists(templateRoot, `Shared template not found: ${templateRoot}`);
78
+
79
+ const projectRoot = path.resolve(options.get('cwd', process.cwd()));
80
+ await assertDirExists(projectRoot, `Project directory not found: ${projectRoot}`);
81
+
82
+ const ideTargets = resolveIdeTargets(options.get('ide', 'all'));
83
+ const force = options.hasFlag('force');
84
+
85
+ const sharedTarget = path.join(projectRoot, '.agent-flutter');
86
+ if ((await exists(sharedTarget)) && !force) {
87
+ console.log(`Using existing shared pack: ${sharedTarget}`);
88
+ } else {
89
+ await copyTemplateDirectory({
90
+ sourceDir: templateRoot,
91
+ destinationDir: sharedTarget,
92
+ projectRoot,
93
+ force: true,
94
+ });
95
+ console.log(`Installed shared pack: ${sharedTarget}`);
96
+ }
97
+
98
+ if (ideTargets.has('trae')) {
99
+ const traeTarget = path.join(projectRoot, '.trae');
100
+ if ((await exists(traeTarget)) && !force) {
101
+ console.log(`Skipped Trae adapter (exists): ${traeTarget}`);
102
+ } else {
103
+ await copyTemplateDirectory({
104
+ sourceDir: templateRoot,
105
+ destinationDir: traeTarget,
106
+ projectRoot,
107
+ force: true,
108
+ });
109
+ console.log(`Installed Trae adapter: ${traeTarget}`);
110
+ }
111
+ }
112
+
113
+ const skills = await loadSkillMetadata(path.join(sharedTarget, 'skills'));
114
+ const rules = await loadRuleMetadata(path.join(sharedTarget, 'rules'));
115
+
116
+ if (ideTargets.has('codex')) {
117
+ const agentsPath = path.join(projectRoot, 'AGENTS.md');
118
+ const written = await writeTextFile(
119
+ agentsPath,
120
+ buildCodexAgents({
121
+ projectRoot,
122
+ projectName: path.basename(projectRoot),
123
+ skills,
124
+ rules,
125
+ }),
126
+ { force },
127
+ );
128
+ console.log(
129
+ written
130
+ ? `Installed Codex adapter: ${agentsPath}`
131
+ : `Skipped Codex adapter (exists): ${agentsPath}`,
132
+ );
133
+ }
134
+
135
+ if (ideTargets.has('cursor')) {
136
+ const cursorPath = path.join(projectRoot, '.cursor', 'rules', 'agent-flutter.mdc');
137
+ const written = await writeTextFile(
138
+ cursorPath,
139
+ buildCursorRule(),
140
+ { force },
141
+ );
142
+ console.log(
143
+ written
144
+ ? `Installed Cursor adapter: ${cursorPath}`
145
+ : `Skipped Cursor adapter (exists): ${cursorPath}`,
146
+ );
147
+ }
148
+
149
+ if (ideTargets.has('windsurf')) {
150
+ const windsurfPath = path.join(projectRoot, '.windsurf', 'rules', 'agent-flutter.md');
151
+ const written = await writeTextFile(
152
+ windsurfPath,
153
+ buildWindsurfRule(),
154
+ { force },
155
+ );
156
+ console.log(
157
+ written
158
+ ? `Installed Windsurf adapter: ${windsurfPath}`
159
+ : `Skipped Windsurf adapter (exists): ${windsurfPath}`,
160
+ );
161
+ }
162
+
163
+ if (ideTargets.has('cline')) {
164
+ const clinePath = path.join(projectRoot, '.clinerules', 'agent-flutter.md');
165
+ const written = await writeTextFile(
166
+ clinePath,
167
+ buildClineRule(),
168
+ { force },
169
+ );
170
+ console.log(
171
+ written
172
+ ? `Installed Cline adapter: ${clinePath}`
173
+ : `Skipped Cline adapter (exists): ${clinePath}`,
174
+ );
175
+ }
176
+
177
+ console.log('');
178
+ console.log('Done.');
179
+ console.log('Use --force to overwrite existing adapters.');
180
+ }
181
+
182
+ async function runList(options) {
183
+ const projectRoot = path.resolve(options.get('cwd', process.cwd()));
184
+ const sharedTarget = path.join(projectRoot, '.agent-flutter');
185
+ const templateRoot = await exists(sharedTarget)
186
+ ? sharedTarget
187
+ : path.join(getPackageRoot(), 'templates', 'shared');
188
+
189
+ const skills = await loadSkillMetadata(path.join(templateRoot, 'skills'));
190
+ const rules = await loadRuleMetadata(path.join(templateRoot, 'rules'));
191
+
192
+ console.log(`Source: ${templateRoot}`);
193
+ console.log('');
194
+ console.log(`Skills (${skills.length})`);
195
+ for (const skill of skills) {
196
+ console.log(`- ${skill.name} (${skill.slug})`);
197
+ }
198
+ console.log('');
199
+ console.log(`Rules (${rules.length})`);
200
+ for (const rule of rules) {
201
+ console.log(`- ${rule.file}`);
202
+ }
203
+ }
204
+
205
+ function resolveIdeTargets(rawValue) {
206
+ const value = String(rawValue || 'all')
207
+ .trim()
208
+ .toLowerCase();
209
+ if (!value || value === 'all') {
210
+ return new Set(SUPPORTED_IDES);
211
+ }
212
+
213
+ const requested = new Set(
214
+ value
215
+ .split(',')
216
+ .map((item) => item.trim().toLowerCase())
217
+ .filter(Boolean),
218
+ );
219
+ for (const ide of requested) {
220
+ if (!SUPPORTED_IDES.includes(ide)) {
221
+ throw new Error(`Unsupported IDE target: ${ide}`);
222
+ }
223
+ }
224
+ return requested;
225
+ }
226
+
227
+ function getPackageRoot() {
228
+ const currentFile = fileURLToPath(import.meta.url);
229
+ return path.resolve(path.dirname(currentFile), '..');
230
+ }
231
+
232
+ async function assertDirExists(dirPath, errorMessage) {
233
+ const stat = await safeStat(dirPath);
234
+ if (!stat || !stat.isDirectory()) {
235
+ throw new Error(errorMessage);
236
+ }
237
+ }
238
+
239
+ async function copyTemplateDirectory({
240
+ sourceDir,
241
+ destinationDir,
242
+ projectRoot,
243
+ force,
244
+ }) {
245
+ if (await exists(destinationDir)) {
246
+ if (force) {
247
+ await fs.rm(destinationDir, { recursive: true, force: true });
248
+ }
249
+ }
250
+
251
+ await fs.mkdir(destinationDir, { recursive: true });
252
+ await copyTemplateEntries({
253
+ sourceDir,
254
+ destinationDir,
255
+ projectRoot,
256
+ });
257
+ }
258
+
259
+ async function copyTemplateEntries({ sourceDir, destinationDir, projectRoot }) {
260
+ const entries = await fs.readdir(sourceDir, { withFileTypes: true });
261
+ for (const entry of entries) {
262
+ if (entry.name === '.DS_Store') continue;
263
+ const fromPath = path.join(sourceDir, entry.name);
264
+ const toPath = path.join(destinationDir, entry.name);
265
+
266
+ if (entry.isDirectory()) {
267
+ await fs.mkdir(toPath, { recursive: true });
268
+ await copyTemplateEntries({
269
+ sourceDir: fromPath,
270
+ destinationDir: toPath,
271
+ projectRoot,
272
+ });
273
+ continue;
274
+ }
275
+
276
+ if (!entry.isFile()) continue;
277
+
278
+ if (isTextFile(entry.name)) {
279
+ const raw = await fs.readFile(fromPath, 'utf8');
280
+ const transformed = replaceProjectPlaceholders(raw, projectRoot);
281
+ await fs.writeFile(toPath, transformed, 'utf8');
282
+ } else {
283
+ await fs.copyFile(fromPath, toPath);
284
+ }
285
+ }
286
+ }
287
+
288
+ function replaceProjectPlaceholders(content, projectRoot) {
289
+ const rootPath = toPosixPath(projectRoot);
290
+ const rootUri = stripTrailingSlash(
291
+ pathToFileURL(path.resolve(projectRoot, '.')).toString(),
292
+ );
293
+ return content
294
+ .replaceAll('{{PROJECT_ROOT_URI}}', rootUri)
295
+ .replaceAll('{{PROJECT_ROOT}}', rootPath);
296
+ }
297
+
298
+ function isTextFile(fileName) {
299
+ const ext = path.extname(fileName).toLowerCase();
300
+ if (!ext) return true;
301
+ const binaryExtensions = new Set([
302
+ '.a',
303
+ '.class',
304
+ '.dll',
305
+ '.dylib',
306
+ '.eot',
307
+ '.exe',
308
+ '.gif',
309
+ '.ico',
310
+ '.jpeg',
311
+ '.jpg',
312
+ '.mp3',
313
+ '.mp4',
314
+ '.otf',
315
+ '.pdf',
316
+ '.png',
317
+ '.so',
318
+ '.ttf',
319
+ '.wav',
320
+ '.webp',
321
+ '.woff',
322
+ '.woff2',
323
+ '.zip',
324
+ ]);
325
+ return !binaryExtensions.has(ext);
326
+ }
327
+
328
+ async function writeTextFile(filePath, content, { force }) {
329
+ if (!force && (await exists(filePath))) {
330
+ return false;
331
+ }
332
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
333
+ await fs.writeFile(filePath, content, 'utf8');
334
+ return true;
335
+ }
336
+
337
+ async function loadSkillMetadata(skillsDir) {
338
+ const result = [];
339
+ const root = await safeStat(skillsDir);
340
+ if (!root || !root.isDirectory()) return result;
341
+
342
+ const entries = await fs.readdir(skillsDir, { withFileTypes: true });
343
+ for (const entry of entries) {
344
+ if (!entry.isDirectory()) continue;
345
+ const slug = entry.name;
346
+ const skillPath = path.join(skillsDir, slug, 'SKILL.md');
347
+ if (!(await exists(skillPath))) continue;
348
+ const content = await fs.readFile(skillPath, 'utf8');
349
+ const frontmatter = parseFrontmatter(content);
350
+ result.push({
351
+ slug,
352
+ name: frontmatter.name || slug,
353
+ description: frontmatter.description || '',
354
+ path: skillPath,
355
+ });
356
+ }
357
+
358
+ result.sort((a, b) => a.slug.localeCompare(b.slug));
359
+ return result;
360
+ }
361
+
362
+ async function loadRuleMetadata(rulesDir) {
363
+ const result = [];
364
+ const root = await safeStat(rulesDir);
365
+ if (!root || !root.isDirectory()) return result;
366
+
367
+ const entries = await fs.readdir(rulesDir, { withFileTypes: true });
368
+ for (const entry of entries) {
369
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
370
+ result.push({
371
+ file: entry.name,
372
+ path: path.join(rulesDir, entry.name),
373
+ });
374
+ }
375
+
376
+ result.sort((a, b) => a.file.localeCompare(b.file));
377
+ return result;
378
+ }
379
+
380
+ function parseFrontmatter(content) {
381
+ const lines = content.split(/\r?\n/);
382
+ if (lines[0] !== '---') return {};
383
+ const data = {};
384
+ for (let i = 1; i < lines.length; i += 1) {
385
+ const line = lines[i];
386
+ if (line === '---') break;
387
+ const index = line.indexOf(':');
388
+ if (index <= 0) continue;
389
+ const key = line.slice(0, index).trim();
390
+ const value = line.slice(index + 1).trim().replace(/^["']|["']$/g, '');
391
+ data[key] = value;
392
+ }
393
+ return data;
394
+ }
395
+
396
+ function buildCodexAgents({ projectRoot, projectName, skills, rules }) {
397
+ const lines = [];
398
+ lines.push(`# AGENTS.md instructions for ${projectName}`);
399
+ lines.push('');
400
+ lines.push('## Agent Flutter Shared Pack');
401
+ lines.push('This project uses a shared local pack installed at `.agent-flutter`.');
402
+ lines.push('');
403
+ lines.push('### Available skills');
404
+ for (const skill of skills) {
405
+ lines.push(
406
+ `- ${skill.slug}: ${skill.description || 'No description'} (file: ${toPosixPath(skill.path)})`,
407
+ );
408
+ }
409
+ lines.push('');
410
+ lines.push('### Available rules');
411
+ for (const rule of rules) {
412
+ lines.push(`- ${rule.file} (file: ${toPosixPath(rule.path)})`);
413
+ }
414
+ lines.push('');
415
+ lines.push('### Trigger rules');
416
+ lines.push('- If a task clearly matches a skill description, apply that skill first.');
417
+ lines.push('- Apply matching rule files before making code changes.');
418
+ lines.push('- Keep generated docs/specs updated when UI or API behavior changes.');
419
+ lines.push('');
420
+ lines.push('### Location policy');
421
+ lines.push(`- Project root: ${toPosixPath(projectRoot)}`);
422
+ lines.push('- Shared pack root: `.agent-flutter`');
423
+ lines.push('- Do not duplicate skill/rule content outside the shared pack unless required.');
424
+ return `${lines.join('\n')}\n`;
425
+ }
426
+
427
+ function buildCursorRule() {
428
+ return `---
429
+ description: Agent Flutter shared skills and rules
430
+ alwaysApply: false
431
+ ---
432
+ Use shared instructions from \`.agent-flutter\`.
433
+
434
+ Priority:
435
+ 1. \`.agent-flutter/rules/ui.md\`
436
+ 2. \`.agent-flutter/rules/integration-api.md\`
437
+ 3. \`.agent-flutter/rules/document-workflow-function.md\`
438
+ 4. \`.agent-flutter/rules/unit-test.md\` and \`.agent-flutter/rules/widget-test.md\`
439
+
440
+ When a task matches a skill, load the corresponding \`SKILL.md\` under:
441
+ \`.agent-flutter/skills/<skill>/SKILL.md\`
442
+ `;
443
+ }
444
+
445
+ function buildWindsurfRule() {
446
+ return `# Agent Flutter Rules
447
+
448
+ Use the shared rule/skill pack at \`.agent-flutter\`.
449
+
450
+ Required order:
451
+ 1. Apply relevant files in \`.agent-flutter/rules/\`.
452
+ 2. If task matches a skill, load \`.agent-flutter/skills/<skill>/SKILL.md\`.
453
+ 3. Keep spec documentation synchronized after UI/API changes.
454
+ `;
455
+ }
456
+
457
+ function buildClineRule() {
458
+ return `# Agent Flutter Cline Rule
459
+
460
+ This repository uses shared instructions in \`.agent-flutter\`.
461
+
462
+ Execution checklist:
463
+ 1. Read matching rule files under \`.agent-flutter/rules\`.
464
+ 2. Apply matching skills from \`.agent-flutter/skills\`.
465
+ 3. Preserve Flutter architecture conventions and localization requirements.
466
+ 4. Update docs/specs after behavior changes.
467
+ `;
468
+ }
469
+
470
+ async function exists(filePath) {
471
+ try {
472
+ await fs.access(filePath);
473
+ return true;
474
+ } catch {
475
+ return false;
476
+ }
477
+ }
478
+
479
+ async function safeStat(filePath) {
480
+ try {
481
+ return await fs.stat(filePath);
482
+ } catch {
483
+ return null;
484
+ }
485
+ }
486
+
487
+ function toPosixPath(value) {
488
+ return String(value || '').replaceAll('\\', '/');
489
+ }
490
+
491
+ function stripTrailingSlash(value) {
492
+ if (value.endsWith('/')) return value.slice(0, -1);
493
+ return value;
494
+ }
@@ -0,0 +1,24 @@
1
+ # Ignore patterns for Trae IDE
2
+ # Only scan lib folder for this Flutter project
3
+
4
+ # Ignore everything outside of lib
5
+ /*
6
+ !/lib
7
+
8
+ # Ignore common Flutter build artifacts and dependencies
9
+ /lib/build/
10
+ /lib/.dart_tool/
11
+ /lib/.packages
12
+ /lib/pubspec.lock
13
+ /lib/ios/Pods/
14
+ /lib/android/.gradle/
15
+ /lib/android/app/build/
16
+ /lib/web/build/
17
+ /lib/macos/Pods/
18
+ /lib/linux/build/
19
+ /lib/windows/build/
20
+
21
+ # Ignore IDE specific files
22
+ /lib/.vscode/
23
+ /lib/.idea/
24
+ /lib/*.iml
@@ -0,0 +1,54 @@
1
+ # Standard Prompt Templates
2
+
3
+ Copy and paste these templates when assigning tasks to ensure the AI follows all project rules (Architecture, Localization, Widgets).
4
+
5
+ ---
6
+
7
+ ## **1. Create New Page (Tạo màn hình mới)**
8
+
9
+ Use this prompt to trigger the **7-Step UI Workflow**:
10
+
11
+ ```text
12
+ Create a new page:
13
+ - **Feature Name**: [e.g. work_report]
14
+ - **Route Type**: [Fullscreen (AppPages) OR Nested (HomeRouter/CustomerRouter) OR Common (CommonRouter)]
15
+ - **Key UI Components**: [List existing widgets or describe new ones, e.g. AppInput, DatePicker]
16
+ - **Logic**: [Brief description of what the page does]
17
+ ```
18
+
19
+ **What the AI will do:**
20
+ 1. Scaffold `lib/src/ui/work_report/{binding,interactor,components}`.
21
+ 2. Create `LocaleKey` & update `lang_*.dart` (Localization).
22
+ 3. Implement UI using `App*` widgets & `AppColors`.
23
+ 4. Register the route in the correct file (`app_pages.dart` or `*_router.dart` or `common_router.dart`).
24
+
25
+ ---
26
+
27
+ ## **2. Create New Widget (Tạo Widget chung)**
28
+
29
+ Use this when you need a reusable component in `lib/src/ui/widgets/`:
30
+
31
+ ```text
32
+ Create a shared widget:
33
+ - **Name**: App[Name] (e.g. AppRatingBar)
34
+ - **Usage**: [Where will it be used?]
35
+ - **Style**: [Reference AppColors/AppStyles]
36
+ ```
37
+
38
+ **What the AI will do:**
39
+ 1. Create `lib/src/ui/widgets/app_[name].dart`.
40
+ 2. Use `StatelessWidget` and `const` constructor.
41
+ 3. Apply `AppColors`/`AppStyles`.
42
+
43
+ ---
44
+
45
+ ## **3. Refactor UI (Clean Code)**
46
+
47
+ Use this when a file is getting too long:
48
+
49
+ ```text
50
+ Refactor this page: [Path to file]
51
+ - Extract sub-widgets to `components/` folder.
52
+ - Replace raw widgets with `App*` widgets.
53
+ - Fix hardcoded strings/colors.
54
+ ```