sdd-full 4.6.2 → 4.8.0

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 (173) hide show
  1. package/bin.js +1 -1
  2. package/package.json +1 -1
  3. package/skills/.agents/skills/flutter-add-integration-test/SKILL.md +165 -0
  4. package/skills/.agents/skills/flutter-add-widget-preview/SKILL.md +147 -0
  5. package/skills/.agents/skills/flutter-add-widget-test/SKILL.md +156 -0
  6. package/skills/.agents/skills/flutter-apply-architecture-best-practices/SKILL.md +164 -0
  7. package/skills/.agents/skills/flutter-build-responsive-layout/SKILL.md +141 -0
  8. package/skills/.agents/skills/flutter-fix-layout-issues/SKILL.md +132 -0
  9. package/skills/.agents/skills/flutter-implement-json-serialization/SKILL.md +155 -0
  10. package/skills/.agents/skills/flutter-setup-declarative-routing/SKILL.md +257 -0
  11. package/skills/.agents/skills/flutter-setup-localization/SKILL.md +212 -0
  12. package/skills/.agents/skills/flutter-use-http-package/SKILL.md +177 -0
  13. package/skills/VERSION.md +176 -62
  14. package/skills/design-planning/ai-coding-rules/SKILL.md +5 -13
  15. package/skills/design-planning/design-to-code/SKILL.md +5 -14
  16. package/skills/design-planning/enterprise-spec/SKILL.md +5 -13
  17. package/skills/design-planning/flutter-av/SKILL.md +5 -16
  18. package/skills/design-planning/flutter-map/SKILL.md +5 -14
  19. package/skills/design-planning/function-sdd/SKILL.md +5 -13
  20. package/skills/design-planning/global-overlay-stack-standard/SKILL.md +73 -0
  21. package/skills/design-planning/ui-motion-interaction-standard/SKILL.md +69 -0
  22. package/skills/design-planning/ui-sdd-specialized/SKILL.md +5 -14
  23. package/skills/development-execution/flutter-errors/SKILL.md +5 -15
  24. package/skills/flutter-skills/.github/dependabot.yaml +15 -0
  25. package/skills/flutter-skills/.github/workflows/dart_skills_lint_workflow.yaml +68 -0
  26. package/skills/flutter-skills/.github/workflows/skills_tool.yaml +51 -0
  27. package/skills/flutter-skills/CODE_OF_CONDUCT.md +3 -0
  28. package/skills/flutter-skills/CONTRIBUTING.md +36 -0
  29. package/skills/flutter-skills/LICENSE +26 -0
  30. package/skills/flutter-skills/README.md +50 -0
  31. package/skills/flutter-skills/pubspec.yaml +9 -0
  32. package/skills/flutter-skills/resources/flutter_skills.yaml +434 -0
  33. package/skills/flutter-skills/skills/flutter-add-integration-test/SKILL.md +163 -0
  34. package/skills/flutter-skills/skills/flutter-add-widget-preview/SKILL.md +145 -0
  35. package/skills/flutter-skills/skills/flutter-add-widget-test/SKILL.md +154 -0
  36. package/skills/flutter-skills/skills/flutter-apply-architecture-best-practices/SKILL.md +162 -0
  37. package/skills/flutter-skills/skills/flutter-build-responsive-layout/SKILL.md +139 -0
  38. package/skills/flutter-skills/skills/flutter-fix-layout-issues/SKILL.md +130 -0
  39. package/skills/flutter-skills/skills/flutter-implement-json-serialization/SKILL.md +153 -0
  40. package/skills/flutter-skills/skills/flutter-setup-declarative-routing/SKILL.md +255 -0
  41. package/skills/flutter-skills/skills/flutter-setup-localization/SKILL.md +210 -0
  42. package/skills/flutter-skills/skills/flutter-use-http-package/SKILL.md +175 -0
  43. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/add-dart-lint-validation-rule/SKILL.md +196 -0
  44. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-best-practices/SKILL.md +65 -0
  45. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-checks-migration/SKILL.md +158 -0
  46. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-cli-app-best-practices/SKILL.md +168 -0
  47. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-doc-validation/SKILL.md +87 -0
  48. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-long-lines/SKILL.md +101 -0
  49. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-matcher-best-practices/SKILL.md +136 -0
  50. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-modern-features/SKILL.md +266 -0
  51. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-package-maintenance/SKILL.md +92 -0
  52. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/SKILL.md +92 -0
  53. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/lib/src/calculator.dart +7 -0
  54. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.yaml +8 -0
  55. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/test/calculator_test.dart +11 -0
  56. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/interpret_coverage.dart +95 -0
  57. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/pubspec.yaml +6 -0
  58. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/test/interpret_coverage_test.dart +93 -0
  59. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-fundamentals/SKILL.md +173 -0
  60. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/definition-of-done/SKILL.md +27 -0
  61. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/flutter_skills_ignore.json +3 -0
  62. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/grill-me/SKILL.md +10 -0
  63. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/ignore.json +3 -0
  64. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/test-driven-development/SKILL.md +371 -0
  65. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/test-driven-development/testing-anti-patterns.md +299 -0
  66. package/skills/flutter-skills/tool/dart_skills_lint/AUTHORS +7 -0
  67. package/skills/flutter-skills/tool/dart_skills_lint/CHANGELOG.md +12 -0
  68. package/skills/flutter-skills/tool/dart_skills_lint/CONTRIBUTING.md +51 -0
  69. package/skills/flutter-skills/tool/dart_skills_lint/LICENSE +27 -0
  70. package/skills/flutter-skills/tool/dart_skills_lint/README.md +203 -0
  71. package/skills/flutter-skills/tool/dart_skills_lint/analysis_options.yaml +296 -0
  72. package/skills/flutter-skills/tool/dart_skills_lint/bench/README.md +23 -0
  73. package/skills/flutter-skills/tool/dart_skills_lint/bench/baseline_throughput.dart +230 -0
  74. package/skills/flutter-skills/tool/dart_skills_lint/bin/cli.dart +10 -0
  75. package/skills/flutter-skills/tool/dart_skills_lint/dart_skills_lint.yaml +14 -0
  76. package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/PRODUCTION_READYNESS.md +48 -0
  77. package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/completion_migration_plan.md +99 -0
  78. package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/legacy_patterns_report.md +110 -0
  79. package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/pub_vs_skill_report.md +56 -0
  80. package/skills/flutter-skills/tool/dart_skills_lint/documentation/knowledge/SPECIFICATION.md +79 -0
  81. package/skills/flutter-skills/tool/dart_skills_lint/documentation/knowledge/architecture_overview.md +64 -0
  82. package/skills/flutter-skills/tool/dart_skills_lint/lib/dart_skills_lint.dart +11 -0
  83. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/config_parser.dart +156 -0
  84. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/entry_point.dart +354 -0
  85. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/fixable_rule.dart +20 -0
  86. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/analysis_severity.dart +15 -0
  87. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/check_type.dart +17 -0
  88. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/ignore_entry.dart +34 -0
  89. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/ignore_entry.g.dart +19 -0
  90. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skill_context.dart +27 -0
  91. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skill_rule.dart +27 -0
  92. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skills_ignores.dart +26 -0
  93. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skills_ignores.g.dart +24 -0
  94. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/validation_error.dart +31 -0
  95. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rule_registry.dart +79 -0
  96. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/absolute_paths_rule.dart +74 -0
  97. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/description_length_rule.dart +49 -0
  98. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/disallowed_field_rule.dart +61 -0
  99. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/name_format_rule.dart +167 -0
  100. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/relative_paths_rule.dart +72 -0
  101. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/trailing_whitespace_rule.dart +93 -0
  102. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/valid_yaml_metadata_rule.dart +74 -0
  103. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/skills_ignores_storage.dart +36 -0
  104. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/validation_session.dart +559 -0
  105. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/validator.dart +238 -0
  106. package/skills/flutter-skills/tool/dart_skills_lint/pubspec.yaml +28 -0
  107. package/skills/flutter-skills/tool/dart_skills_lint/skills/README.md +10 -0
  108. package/skills/flutter-skills/tool/dart_skills_lint/skills/dart-skills-lint-validation/SKILL.md +195 -0
  109. package/skills/flutter-skills/tool/dart_skills_lint/skills-lock.json +75 -0
  110. package/skills/flutter-skills/tool/dart_skills_lint/test/absolute_paths_test.dart +167 -0
  111. package/skills/flutter-skills/tool/dart_skills_lint/test/cli_integration_test.dart +683 -0
  112. package/skills/flutter-skills/tool/dart_skills_lint/test/config_file_test.dart +292 -0
  113. package/skills/flutter-skills/tool/dart_skills_lint/test/custom_rule_test.dart +122 -0
  114. package/skills/flutter-skills/tool/dart_skills_lint/test/directory_structure_test.dart +163 -0
  115. package/skills/flutter-skills/tool/dart_skills_lint/test/field_constraints_test.dart +178 -0
  116. package/skills/flutter-skills/tool/dart_skills_lint/test/fixer_test.dart +172 -0
  117. package/skills/flutter-skills/tool/dart_skills_lint/test/ignore_models_test.dart +63 -0
  118. package/skills/flutter-skills/tool/dart_skills_lint/test/metadata_validation_test.dart +116 -0
  119. package/skills/flutter-skills/tool/dart_skills_lint/test/relative_path_flag_test.dart +70 -0
  120. package/skills/flutter-skills/tool/dart_skills_lint/test/relative_paths_test.dart +172 -0
  121. package/skills/flutter-skills/tool/dart_skills_lint/test/resolve_rules_test.dart +82 -0
  122. package/skills/flutter-skills/tool/dart_skills_lint/test/rule_naming_test.dart +29 -0
  123. package/skills/flutter-skills/tool/dart_skills_lint/test/skills_ignores_storage_test.dart +89 -0
  124. package/skills/flutter-skills/tool/dart_skills_lint/test/test_utils.dart +19 -0
  125. package/skills/flutter-skills/tool/dart_skills_lint/test/trailing_whitespace_test.dart +152 -0
  126. package/skills/flutter-skills/tool/generator/README.md +150 -0
  127. package/skills/flutter-skills/tool/generator/analysis_options.yaml +143 -0
  128. package/skills/flutter-skills/tool/generator/bin/skills.dart +73 -0
  129. package/skills/flutter-skills/tool/generator/lib/src/commands/base_skill_command.dart +87 -0
  130. package/skills/flutter-skills/tool/generator/lib/src/commands/base_yaml_command.dart +83 -0
  131. package/skills/flutter-skills/tool/generator/lib/src/commands/generate_skill_command.dart +92 -0
  132. package/skills/flutter-skills/tool/generator/lib/src/commands/update_readme_command.dart +150 -0
  133. package/skills/flutter-skills/tool/generator/lib/src/commands/update_skill_command.dart +97 -0
  134. package/skills/flutter-skills/tool/generator/lib/src/commands/validate_skill_command.dart +284 -0
  135. package/skills/flutter-skills/tool/generator/lib/src/models/skill_params.dart +41 -0
  136. package/skills/flutter-skills/tool/generator/lib/src/services/gemini_service.dart +310 -0
  137. package/skills/flutter-skills/tool/generator/lib/src/services/markdown_converter.dart +226 -0
  138. package/skills/flutter-skills/tool/generator/lib/src/services/prompts.dart +72 -0
  139. package/skills/flutter-skills/tool/generator/lib/src/services/resource_fetcher_service.dart +84 -0
  140. package/skills/flutter-skills/tool/generator/lib/src/services/skill_instructions.dart +30 -0
  141. package/skills/flutter-skills/tool/generator/pubspec.yaml +32 -0
  142. package/skills/flutter-skills/tool/generator/test/commands/base_skill_command_test.dart +131 -0
  143. package/skills/flutter-skills/tool/generator/test/commands/validate_skills_input_test.dart +263 -0
  144. package/skills/flutter-skills/tool/generator/test/custom_skill_rules/last_modified_rule.dart +32 -0
  145. package/skills/flutter-skills/tool/generator/test/generate_skills_retry_test.dart +105 -0
  146. package/skills/flutter-skills/tool/generator/test/generate_skills_test.dart +519 -0
  147. package/skills/flutter-skills/tool/generator/test/lint_skills_test.dart +34 -0
  148. package/skills/flutter-skills/tool/generator/test/markdown_converter_test.dart +103 -0
  149. package/skills/flutter-skills/tool/generator/test/markdown_table_test.dart +131 -0
  150. package/skills/flutter-skills/tool/generator/test/models/skill_params_test.dart +37 -0
  151. package/skills/flutter-skills/tool/generator/test/services/gemini_service_test.dart +291 -0
  152. package/skills/flutter-skills/tool/generator/test/services/markdown_converter_test.dart +156 -0
  153. package/skills/flutter-skills/tool/generator/test/services/resource_fetcher_service_test.dart +188 -0
  154. package/skills/flutter-skills/tool/generator/test/update_skills_test.dart +241 -0
  155. package/skills/flutter-skills/tool/generator/test/validate_skills_test.dart +728 -0
  156. package/skills/quality-assurance/bdd-acceptance/SKILL.md +5 -14
  157. package/skills/quality-assurance/flutter-test/SKILL.md +5 -16
  158. package/skills/rules/project_rules.md +538 -127
  159. package/skills/special-tools/env-check/SKILL.md +5 -13
  160. package/skills/special-tools/ios-full-auto-debug/SKILL.md +5 -15
  161. package/skills/writing-skills/SKILL.md +654 -0
  162. package/skills/writing-skills/anthropic-best-practices.md +1149 -0
  163. package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  164. package/skills/writing-skills/graphviz-conventions.dot +172 -0
  165. package/skills/writing-skills/persuasion-principles.md +187 -0
  166. package/skills/writing-skills/render-graphs.js +168 -0
  167. package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  168. package/skills/checklist.md +0 -154
  169. package/skills/rules/user_rules.md +0 -263
  170. package/skills//345/256/214/346/225/264/345/274/200/345/217/221/346/265/201/347/250/213/346/211/213/345/206/214.md +0 -454
  171. package/skills//346/212/200/350/203/275/344/275/223/347/263/273/345/256/214/345/226/204/345/273/272/350/256/256.md +0 -308
  172. package/skills//346/212/200/350/203/275/344/275/277/347/224/250/346/214/207/345/215/227.md +0 -309
  173. package/skills//346/212/200/350/203/275/345/206/263/347/255/226/346/240/221.md +0 -338
@@ -0,0 +1,150 @@
1
+ // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
2
+ // for details. All rights reserved. Use of this source code is governed by a
3
+ // BSD-style license that can be found in the LICENSE file.
4
+
5
+ import 'dart:io';
6
+
7
+ import 'package:logging/logging.dart';
8
+ import 'package:path/path.dart' as p;
9
+
10
+ import '../models/skill_params.dart';
11
+ import 'base_yaml_command.dart';
12
+
13
+ /// Command to update the README.md file with a table of available skills.
14
+ class UpdateReadmeCommand extends BaseYamlCommand {
15
+ /// Creates a new [UpdateReadmeCommand].
16
+ UpdateReadmeCommand({super.outputDir})
17
+ : super(logger: Logger('UpdateReadmeCommand')) {
18
+ argParser.addOption(
19
+ 'readme',
20
+ help: 'Path to the README.md file to update.',
21
+ );
22
+ }
23
+
24
+ @override
25
+ String get name => 'update-readme';
26
+
27
+ @override
28
+ String get description =>
29
+ 'Updates the README.md with a table of available skills from the configuration.';
30
+
31
+ @override
32
+ Future<void> runWithSkills(
33
+ List<SkillParams> skills,
34
+ Directory outputDir, {
35
+ Directory? configDir,
36
+ }) async {
37
+ String readmePath;
38
+ if (argResults?['readme'] != null) {
39
+ readmePath = argResults!['readme'] as String;
40
+ } else if (argResults!.rest.length > 1) {
41
+ readmePath = argResults!.rest[1];
42
+ } else {
43
+ final dirReadme = File(p.join(outputDir.path, 'README.md'));
44
+ final parentReadme = File(p.join(outputDir.parent.path, 'README.md'));
45
+
46
+ if (dirReadme.existsSync()) {
47
+ readmePath = dirReadme.path;
48
+ } else if (parentReadme.existsSync()) {
49
+ readmePath = parentReadme.path;
50
+ } else {
51
+ readmePath = 'README.md';
52
+ }
53
+ }
54
+
55
+ final readmeFile = File(readmePath);
56
+
57
+ if (!readmeFile.existsSync()) {
58
+ logger.severe('README file not found at $readmePath');
59
+ return;
60
+ }
61
+
62
+ logger.info('Updating README at ${readmeFile.path}...');
63
+
64
+ final content = readmeFile.readAsStringSync();
65
+
66
+ // Parse existing example prompts from the README if they exist
67
+ final existingPrompts = <String, String>{};
68
+ final tableLineRegex = RegExp(
69
+ r'^\| \[([^\]]+)\]\([^)]+\) \| [^|]+ \| ([^|]+) \|',
70
+ multiLine: true,
71
+ );
72
+ for (final match in tableLineRegex.allMatches(content)) {
73
+ final skillName = match.group(1)!;
74
+ final prompt = match.group(2)!.trim();
75
+ if (prompt.isNotEmpty) {
76
+ existingPrompts[skillName] = prompt;
77
+ }
78
+ }
79
+
80
+ // Generate the table
81
+ final buffer = StringBuffer()
82
+ ..writeln('| Skill | Description | Example prompt |')
83
+ ..writeln('|---|---|---|');
84
+
85
+ // Sort skills by name for consistency
86
+ final sortedSkills = List<SkillParams>.from(skills)
87
+ ..sort((a, b) => a.name.compareTo(b.name));
88
+
89
+ for (final skill in sortedSkills) {
90
+ // Calculate relative link from README to the SKILL.md file
91
+ final skillFile = File(p.join(outputDir.path, skill.name, 'SKILL.md'));
92
+ var relativeLink = p.relative(
93
+ skillFile.path,
94
+ from: readmeFile.parent.path,
95
+ );
96
+
97
+ // Ensure the link contains the skills/ directory in the URL.
98
+ if (!relativeLink.contains('skills/')) {
99
+ relativeLink = p.join('skills', relativeLink);
100
+ }
101
+
102
+ // Ensure we use forward slashes for the markdown link.
103
+ relativeLink = relativeLink.replaceAll('\\', '/');
104
+
105
+ final prompt = skill.examplePrompt ?? existingPrompts[skill.name] ?? '';
106
+
107
+ buffer.writeln(
108
+ '| [${skill.name}]($relativeLink) | ${skill.description} | $prompt |',
109
+ );
110
+ }
111
+
112
+ final newTable = buffer.toString();
113
+
114
+ // Find where to insert the table
115
+ final sectionRegex = RegExp(
116
+ '^## (Available Skills|List of Skills|Skills List|Skill Index)',
117
+ caseSensitive: false,
118
+ multiLine: true,
119
+ );
120
+ final match = sectionRegex.firstMatch(content);
121
+
122
+ String updatedContent;
123
+ if (match != null) {
124
+ // Find the end of the line with the header
125
+ final headerEnd = content.indexOf('\n', match.start);
126
+ // Find the next section header or end of file
127
+ final nextSectionIndex = content.indexOf(
128
+ RegExp(r'\n##\s', multiLine: true),
129
+ headerEnd + 1,
130
+ );
131
+
132
+ final prefix = content.substring(
133
+ 0,
134
+ headerEnd == -1 ? content.length : headerEnd + 1,
135
+ );
136
+ final suffix = nextSectionIndex != -1
137
+ ? content.substring(nextSectionIndex)
138
+ : '';
139
+
140
+ updatedContent = '${prefix.trimRight()}\n\n$newTable${suffix.trimLeft()}';
141
+ } else {
142
+ // Add a new section at the end
143
+ updatedContent =
144
+ '${content.trimRight()}\n\n## Available Skills\n\n$newTable';
145
+ }
146
+
147
+ readmeFile.writeAsStringSync('${updatedContent.trimRight()}\n');
148
+ logger.info('Successfully updated $readmePath');
149
+ }
150
+ }
@@ -0,0 +1,97 @@
1
+ // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
2
+ // for details. All rights reserved. Use of this source code is governed by a
3
+ // BSD-style license that can be found in the LICENSE file.
4
+
5
+ import 'dart:io';
6
+
7
+ import 'package:logging/logging.dart';
8
+ import 'package:path/path.dart' as p;
9
+
10
+ import '../models/skill_params.dart';
11
+ import '../services/gemini_service.dart';
12
+ import '../services/resource_fetcher_service.dart';
13
+ import 'base_skill_command.dart';
14
+
15
+ /// Command to update skills from a configuration file, preserving existing content.
16
+ class UpdateSkillCommand extends BaseSkillCommand {
17
+ /// Creates a new [UpdateSkillCommand].
18
+ UpdateSkillCommand({
19
+ required super.httpClient,
20
+ super.outputDir,
21
+ super.environment,
22
+ }) : super(logger: Logger('UpdateSkillCommand'));
23
+
24
+ @override
25
+ String get name => 'update-skill';
26
+
27
+ @override
28
+ String get description =>
29
+ 'Updates an existing skill by combining its current content with fetched resources and new instructions.';
30
+
31
+ @override
32
+ Future<void> runSkill(
33
+ SkillParams skill,
34
+ GeminiService gemini,
35
+ Directory outputDir,
36
+ int thinkingBudget, {
37
+ Directory? configDir,
38
+ }) async {
39
+ logger.info('Updating skill: ${skill.name}...');
40
+
41
+ final skillFile = File(p.join(outputDir.path, skill.name, 'SKILL.md'));
42
+ if (!skillFile.existsSync()) {
43
+ logger.severe(
44
+ ' Skill file not found at ${skillFile.path}. Cannot update an non-existent skill.',
45
+ );
46
+ return;
47
+ }
48
+
49
+ try {
50
+ final existingContent = skillFile.readAsStringSync();
51
+
52
+ final fetcher = ResourceFetcherService(
53
+ httpClient: httpClient,
54
+ logger: logger,
55
+ );
56
+ final combinedMarkdown = await fetcher.fetchAndConvertContent(
57
+ skill.resources,
58
+ configDir: configDir,
59
+ );
60
+
61
+ if (combinedMarkdown.isEmpty) {
62
+ logger.warning(' No content fetched for ${skill.name}. Skipping.');
63
+ return;
64
+ }
65
+
66
+ final dryRun = argResults?['dry-run'] as bool? ?? false;
67
+ if (dryRun) {
68
+ logger
69
+ ..info(' [DRY RUN] Would update skill: ${skill.name}')
70
+ ..info(
71
+ ' [DRY RUN] Original file size: ${existingContent.split(' ').length} tokens -> Raw content size: ${combinedMarkdown.split(' ').length} tokens.',
72
+ );
73
+ return;
74
+ }
75
+
76
+ final generatedContent = await gemini.updateSkillContent(
77
+ existingContent,
78
+ combinedMarkdown,
79
+ skill.name,
80
+ skill.description,
81
+ instructions: skill.instructions,
82
+ thinkingBudget: thinkingBudget,
83
+ );
84
+
85
+ if (generatedContent != null && generatedContent.isNotEmpty) {
86
+ skillFile.writeAsStringSync(generatedContent);
87
+ logger.info(
88
+ ' Updated ${p.join(outputDir.path, skill.name, 'SKILL.md')}',
89
+ );
90
+ } else {
91
+ logger.severe(' Failed to update content for ${skill.name}');
92
+ }
93
+ } on Exception catch (e) {
94
+ logger.severe(' Error processing ${skill.name}: $e');
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,284 @@
1
+ // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
2
+ // for details. All rights reserved. Use of this source code is governed by a
3
+ // BSD-style license that can be found in the LICENSE file.
4
+
5
+ import 'dart:io';
6
+
7
+ import 'package:logging/logging.dart';
8
+ import 'package:path/path.dart' as p;
9
+ import 'package:yaml/yaml.dart';
10
+
11
+ import '../models/skill_params.dart';
12
+ import '../services/gemini_service.dart';
13
+ import '../services/resource_fetcher_service.dart';
14
+ import 'base_skill_command.dart';
15
+
16
+ /// Command to validate skills by re-generating and comparing with existing skills.
17
+ class ValidateSkillCommand extends BaseSkillCommand {
18
+ /// Creates a new [ValidateSkillCommand].
19
+ ValidateSkillCommand({
20
+ required super.httpClient,
21
+ super.outputDir,
22
+ super.environment,
23
+ this.validationDir,
24
+ }) : super(logger: Logger('ValidateSkillCommand'));
25
+
26
+ /// The directory to output validation reports.
27
+ final Directory? validationDir;
28
+
29
+ @override
30
+ String get name => 'validate-skill';
31
+
32
+ @override
33
+ String get description =>
34
+ 'Validates skills using existing skill files and yaml configuration.';
35
+
36
+ @override
37
+ Future<void> run() async {
38
+ final inputFile = argResults!.rest.isNotEmpty
39
+ ? argResults!.rest.first
40
+ : 'resources/flutter_skills.yaml';
41
+
42
+ final file = File(inputFile);
43
+ if (!file.existsSync()) {
44
+ logger.severe('Configuration file not found: $inputFile');
45
+ return;
46
+ }
47
+
48
+ final yamlContent = file.readAsStringSync();
49
+ YamlList yamlList;
50
+ try {
51
+ final decoded = loadYaml(yamlContent);
52
+ if (decoded is! YamlList) {
53
+ logger.severe('Invalid configuration: Root must be a YAML list.');
54
+ return;
55
+ }
56
+ yamlList = decoded;
57
+ } on Object catch (e) {
58
+ logger.severe('Invalid YAML syntax in $inputFile: $e');
59
+ return;
60
+ }
61
+
62
+ final isValid = _validateYamlStructure(yamlList, p.basename(file.path));
63
+ if (!isValid) {
64
+ logger.severe('Configuration validation failed.');
65
+ return;
66
+ }
67
+
68
+ await super.run();
69
+ }
70
+
71
+ bool _validateYamlStructure(YamlList yamlList, String fileName) {
72
+ if (yamlList.isEmpty) {
73
+ logger.severe('Configuration list must not be empty.');
74
+ return false;
75
+ }
76
+
77
+ var isValid = true;
78
+ final kabobCase = RegExp(r'^[a-z0-9]+(-[a-z0-9]+)*$');
79
+
80
+ for (var i = 0; i < yamlList.length; i++) {
81
+ final item = yamlList[i];
82
+ if (item is! Map) {
83
+ logger.severe('Item $i is not a Map.');
84
+ isValid = false;
85
+ continue;
86
+ }
87
+
88
+ final rawName = item['name'];
89
+ final name = rawName is String ? rawName : 'Item $i';
90
+ if (rawName == null) {
91
+ logger.severe('Item $i is missing required field "name".');
92
+ isValid = false;
93
+ } else if (rawName is! String) {
94
+ logger.severe('Item $i field "name" must be a string.');
95
+ isValid = false;
96
+ }
97
+
98
+ final rawDescription = item['description'];
99
+ if (rawDescription == null) {
100
+ logger.severe('Skill "$name" is missing required field "description".');
101
+ isValid = false;
102
+ } else if (rawDescription is! String) {
103
+ logger.severe('Skill "$name" field "description" must be a string.');
104
+ isValid = false;
105
+ }
106
+
107
+ if (item['resources'] == null) {
108
+ logger.severe('Skill "$name" is missing required field "resources".');
109
+ isValid = false;
110
+ }
111
+
112
+ if (item['instructions'] != null && item['instructions'] is! String) {
113
+ logger.severe('Skill "$name" field "instructions" must be a string.');
114
+ isValid = false;
115
+ }
116
+
117
+ if (rawName is String) {
118
+ if (!kabobCase.hasMatch(name)) {
119
+ logger.severe(
120
+ 'Skill name "$name" must be kabob-case (e.g. abc-def).',
121
+ );
122
+ isValid = false;
123
+ }
124
+
125
+ if (fileName == 'flutter_skills.yaml') {
126
+ if (!name.startsWith('flutter-')) {
127
+ logger.severe(
128
+ 'Skill name "$name" in flutter_skills.yaml must start with "flutter-".',
129
+ );
130
+ isValid = false;
131
+ }
132
+ } else if (fileName == 'dart_skills.yaml') {
133
+ if (!name.startsWith('dart-')) {
134
+ logger.severe(
135
+ 'Skill name "$name" in dart_skills.yaml must start with "dart-".',
136
+ );
137
+ isValid = false;
138
+ }
139
+ }
140
+ }
141
+
142
+ final resources = item['resources'];
143
+ if (resources != null) {
144
+ if (resources is! List) {
145
+ logger.severe('Skill "$name" field "resources" must be a list.');
146
+ isValid = false;
147
+ } else if (resources.isEmpty) {
148
+ logger.severe('Skill "$name" field "resources" must not be empty.');
149
+ isValid = false;
150
+ } else {
151
+ for (final resource in resources) {
152
+ if (resource is! String) {
153
+ logger.severe('Skill "$name" resource must be a string.');
154
+ isValid = false;
155
+ } else {
156
+ if (resource.contains('://') &&
157
+ !resource.startsWith('https://')) {
158
+ logger.severe(
159
+ 'Skill "$name" resource URL "$resource" must use secure HTTPS.',
160
+ );
161
+ isValid = false;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ return isValid;
170
+ }
171
+
172
+ @override
173
+ Future<void> runSkill(
174
+ SkillParams skill,
175
+ GeminiService gemini,
176
+ Directory outputDir,
177
+ int thinkingBudget, {
178
+ Directory? configDir,
179
+ }) async {
180
+ logger.info('Validating skill: ${skill.name}...');
181
+
182
+ try {
183
+ // Re-generate markdown content
184
+ final fetcher = ResourceFetcherService(
185
+ httpClient: httpClient,
186
+ logger: logger,
187
+ );
188
+ final markdown = await fetcher.fetchAndConvertContent(
189
+ skill.resources,
190
+ configDir: configDir,
191
+ );
192
+
193
+ if (markdown.isEmpty) {
194
+ logger.warning(
195
+ ' No content fetched for ${skill.name}. Skipping validation.',
196
+ );
197
+ return;
198
+ }
199
+
200
+ // Read existing content
201
+ final existingSkillFile = File(
202
+ p.join(outputDir.path, skill.name, 'SKILL.md'),
203
+ );
204
+ if (!existingSkillFile.existsSync()) {
205
+ logger.warning(
206
+ ' Existing skill file not found at ${existingSkillFile.path}',
207
+ );
208
+ return;
209
+ }
210
+
211
+ final existingSkillFileContent = existingSkillFile.readAsStringSync();
212
+
213
+ // Check for verbatim name
214
+ final namePattern = RegExp(
215
+ 'name:\\s*["\']?${RegExp.escape(skill.name)}["\']?',
216
+ );
217
+ if (!namePattern.hasMatch(existingSkillFileContent)) {
218
+ logger.severe(
219
+ ' Validation Failed: Skill name mismatch in ${existingSkillFile.path}. '
220
+ 'Expected "name: ${skill.name}" (quotes allowed)',
221
+ );
222
+ }
223
+
224
+ // Extract metadata from existing content
225
+ final generationDate =
226
+ RegExp(
227
+ 'last_modified: (.*)',
228
+ ).firstMatch(existingSkillFileContent)?.group(1) ??
229
+ 'Unknown';
230
+ final modelName =
231
+ RegExp(
232
+ 'model: (.*)',
233
+ ).firstMatch(existingSkillFileContent)?.group(1) ??
234
+ 'Unknown';
235
+
236
+ // Compare
237
+ final dryRun = argResults?['dry-run'] as bool? ?? false;
238
+ if (dryRun) {
239
+ logger
240
+ ..info(' [DRY RUN] Would validate skill: ${skill.name}')
241
+ ..info(
242
+ ' [DRY RUN] existing file size: ${existingSkillFileContent.split(' ').length} tokens -> new fetched content size: ${markdown.split(' ').length} tokens.',
243
+ );
244
+ return;
245
+ }
246
+
247
+ logger.info(' Comparing versions...');
248
+ final result = await gemini.validateExistingSkillContent(
249
+ markdown,
250
+ skill.name,
251
+ skill.instructions ?? 'No instructions provided',
252
+ generationDate,
253
+ modelName,
254
+ existingSkillFileContent,
255
+ thinkingBudget: thinkingBudget,
256
+ );
257
+
258
+ if (result != null) {
259
+ final valDirBase = validationDir ?? Directory('validation');
260
+ final valDir = Directory(p.join(valDirBase.path, skill.name));
261
+ if (!valDir.existsSync()) {
262
+ valDir.createSync(recursive: true);
263
+ }
264
+
265
+ File(p.join(valDir.path, 'validation.md')).writeAsStringSync(result);
266
+
267
+ // Extract and log the grade
268
+ final gradeMatch = RegExp(r'Grade:\s*(\d+)').firstMatch(result);
269
+ final grade = gradeMatch?.group(1);
270
+
271
+ logger.info(
272
+ ' Validation report written to ${p.join(valDir.path, 'validation.md')} '
273
+ '${grade != null ? '(Grade: $grade)' : ''}',
274
+ );
275
+ } else {
276
+ logger.severe(
277
+ ' Failed to generate validation report for ${skill.name}',
278
+ );
279
+ }
280
+ } on Exception catch (e) {
281
+ logger.severe(' Error validating ${skill.name}: $e');
282
+ }
283
+ }
284
+ }
@@ -0,0 +1,41 @@
1
+ // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
2
+ // for details. All rights reserved. Use of this source code is governed by a
3
+ // BSD-style license that can be found in the LICENSE file.
4
+
5
+ /// Parameters for generating a skill from resources.
6
+ class SkillParams {
7
+ /// Creates a new [SkillParams] instance.
8
+ SkillParams({
9
+ required this.name,
10
+ required this.description,
11
+ required this.resources,
12
+ this.instructions,
13
+ this.examplePrompt,
14
+ });
15
+
16
+ /// Creates a [SkillParams] instance from a JSON map.
17
+ factory SkillParams.fromJson(Map<String, dynamic> json) {
18
+ return SkillParams(
19
+ name: json['name'] as String,
20
+ description: json['description'] as String,
21
+ resources: (json['resources'] as List).cast<String>(),
22
+ instructions: json['instructions'] as String?,
23
+ examplePrompt: json['examplePrompt'] as String?,
24
+ );
25
+ }
26
+
27
+ /// The name of the skill.
28
+ final String name;
29
+
30
+ /// The description of the skill.
31
+ final String description;
32
+
33
+ /// The example prompt for the skill.
34
+ final String? examplePrompt;
35
+
36
+ /// Optional instructions for generating the skill.
37
+ final String? instructions;
38
+
39
+ /// The resources to fetch content from.
40
+ final List<String> resources;
41
+ }