sdd-full 4.8.0 → 4.8.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 (163) hide show
  1. package/bin.js +1 -1
  2. package/index.js +18 -12
  3. package/package.json +1 -1
  4. package/skills/VERSION.md +61 -175
  5. package/skills/design-planning/ai-coding-rules/SKILL.md +13 -5
  6. package/skills/design-planning/design-to-code/SKILL.md +14 -5
  7. package/skills/design-planning/enterprise-spec/SKILL.md +13 -5
  8. package/skills/design-planning/flutter-av/SKILL.md +16 -5
  9. package/skills/design-planning/flutter-map/SKILL.md +14 -5
  10. package/skills/design-planning/function-sdd/SKILL.md +13 -5
  11. package/skills/design-planning/global-overlay-stack-standard/SKILL.md +14 -4
  12. package/skills/design-planning/ui-motion-interaction-standard/SKILL.md +14 -4
  13. package/skills/design-planning/ui-sdd-specialized/SKILL.md +14 -5
  14. package/skills/development-execution/flutter-errors/SKILL.md +15 -5
  15. package/skills/quality-assurance/bdd-acceptance/SKILL.md +14 -5
  16. package/skills/quality-assurance/flutter-test/SKILL.md +16 -5
  17. package/skills/requirement-analysis/sdd/mock_sdd.md +156 -0
  18. package/skills/rules/project_rules.md +127 -538
  19. package/skills/rules/user_rules.md +263 -0
  20. package/skills/special-tools/env-check/SKILL.md +13 -5
  21. package/skills/special-tools/ios-full-auto-debug/SKILL.md +15 -5
  22. package/skills/flutter-skills/.github/dependabot.yaml +0 -15
  23. package/skills/flutter-skills/.github/workflows/dart_skills_lint_workflow.yaml +0 -68
  24. package/skills/flutter-skills/.github/workflows/skills_tool.yaml +0 -51
  25. package/skills/flutter-skills/CODE_OF_CONDUCT.md +0 -3
  26. package/skills/flutter-skills/CONTRIBUTING.md +0 -36
  27. package/skills/flutter-skills/LICENSE +0 -26
  28. package/skills/flutter-skills/README.md +0 -50
  29. package/skills/flutter-skills/pubspec.yaml +0 -9
  30. package/skills/flutter-skills/resources/flutter_skills.yaml +0 -434
  31. package/skills/flutter-skills/skills/flutter-add-integration-test/SKILL.md +0 -163
  32. package/skills/flutter-skills/skills/flutter-add-widget-preview/SKILL.md +0 -145
  33. package/skills/flutter-skills/skills/flutter-add-widget-test/SKILL.md +0 -154
  34. package/skills/flutter-skills/skills/flutter-apply-architecture-best-practices/SKILL.md +0 -162
  35. package/skills/flutter-skills/skills/flutter-build-responsive-layout/SKILL.md +0 -139
  36. package/skills/flutter-skills/skills/flutter-fix-layout-issues/SKILL.md +0 -130
  37. package/skills/flutter-skills/skills/flutter-implement-json-serialization/SKILL.md +0 -153
  38. package/skills/flutter-skills/skills/flutter-setup-declarative-routing/SKILL.md +0 -255
  39. package/skills/flutter-skills/skills/flutter-setup-localization/SKILL.md +0 -210
  40. package/skills/flutter-skills/skills/flutter-use-http-package/SKILL.md +0 -175
  41. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/add-dart-lint-validation-rule/SKILL.md +0 -196
  42. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-best-practices/SKILL.md +0 -65
  43. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-checks-migration/SKILL.md +0 -158
  44. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-cli-app-best-practices/SKILL.md +0 -168
  45. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-doc-validation/SKILL.md +0 -87
  46. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-long-lines/SKILL.md +0 -101
  47. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-matcher-best-practices/SKILL.md +0 -136
  48. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-modern-features/SKILL.md +0 -266
  49. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-package-maintenance/SKILL.md +0 -92
  50. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/SKILL.md +0 -92
  51. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/lib/src/calculator.dart +0 -7
  52. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.yaml +0 -8
  53. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/test/calculator_test.dart +0 -11
  54. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/interpret_coverage.dart +0 -95
  55. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/pubspec.yaml +0 -6
  56. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/test/interpret_coverage_test.dart +0 -93
  57. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-fundamentals/SKILL.md +0 -173
  58. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/definition-of-done/SKILL.md +0 -27
  59. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/flutter_skills_ignore.json +0 -3
  60. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/grill-me/SKILL.md +0 -10
  61. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/ignore.json +0 -3
  62. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/test-driven-development/SKILL.md +0 -371
  63. package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/test-driven-development/testing-anti-patterns.md +0 -299
  64. package/skills/flutter-skills/tool/dart_skills_lint/AUTHORS +0 -7
  65. package/skills/flutter-skills/tool/dart_skills_lint/CHANGELOG.md +0 -12
  66. package/skills/flutter-skills/tool/dart_skills_lint/CONTRIBUTING.md +0 -51
  67. package/skills/flutter-skills/tool/dart_skills_lint/LICENSE +0 -27
  68. package/skills/flutter-skills/tool/dart_skills_lint/README.md +0 -203
  69. package/skills/flutter-skills/tool/dart_skills_lint/analysis_options.yaml +0 -296
  70. package/skills/flutter-skills/tool/dart_skills_lint/bench/README.md +0 -23
  71. package/skills/flutter-skills/tool/dart_skills_lint/bench/baseline_throughput.dart +0 -230
  72. package/skills/flutter-skills/tool/dart_skills_lint/bin/cli.dart +0 -10
  73. package/skills/flutter-skills/tool/dart_skills_lint/dart_skills_lint.yaml +0 -14
  74. package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/PRODUCTION_READYNESS.md +0 -48
  75. package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/completion_migration_plan.md +0 -99
  76. package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/legacy_patterns_report.md +0 -110
  77. package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/pub_vs_skill_report.md +0 -56
  78. package/skills/flutter-skills/tool/dart_skills_lint/documentation/knowledge/SPECIFICATION.md +0 -79
  79. package/skills/flutter-skills/tool/dart_skills_lint/documentation/knowledge/architecture_overview.md +0 -64
  80. package/skills/flutter-skills/tool/dart_skills_lint/lib/dart_skills_lint.dart +0 -11
  81. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/config_parser.dart +0 -156
  82. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/entry_point.dart +0 -354
  83. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/fixable_rule.dart +0 -20
  84. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/analysis_severity.dart +0 -15
  85. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/check_type.dart +0 -17
  86. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/ignore_entry.dart +0 -34
  87. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/ignore_entry.g.dart +0 -19
  88. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skill_context.dart +0 -27
  89. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skill_rule.dart +0 -27
  90. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skills_ignores.dart +0 -26
  91. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skills_ignores.g.dart +0 -24
  92. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/validation_error.dart +0 -31
  93. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rule_registry.dart +0 -79
  94. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/absolute_paths_rule.dart +0 -74
  95. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/description_length_rule.dart +0 -49
  96. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/disallowed_field_rule.dart +0 -61
  97. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/name_format_rule.dart +0 -167
  98. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/relative_paths_rule.dart +0 -72
  99. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/trailing_whitespace_rule.dart +0 -93
  100. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/valid_yaml_metadata_rule.dart +0 -74
  101. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/skills_ignores_storage.dart +0 -36
  102. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/validation_session.dart +0 -559
  103. package/skills/flutter-skills/tool/dart_skills_lint/lib/src/validator.dart +0 -238
  104. package/skills/flutter-skills/tool/dart_skills_lint/pubspec.yaml +0 -28
  105. package/skills/flutter-skills/tool/dart_skills_lint/skills/README.md +0 -10
  106. package/skills/flutter-skills/tool/dart_skills_lint/skills/dart-skills-lint-validation/SKILL.md +0 -195
  107. package/skills/flutter-skills/tool/dart_skills_lint/skills-lock.json +0 -75
  108. package/skills/flutter-skills/tool/dart_skills_lint/test/absolute_paths_test.dart +0 -167
  109. package/skills/flutter-skills/tool/dart_skills_lint/test/cli_integration_test.dart +0 -683
  110. package/skills/flutter-skills/tool/dart_skills_lint/test/config_file_test.dart +0 -292
  111. package/skills/flutter-skills/tool/dart_skills_lint/test/custom_rule_test.dart +0 -122
  112. package/skills/flutter-skills/tool/dart_skills_lint/test/directory_structure_test.dart +0 -163
  113. package/skills/flutter-skills/tool/dart_skills_lint/test/field_constraints_test.dart +0 -178
  114. package/skills/flutter-skills/tool/dart_skills_lint/test/fixer_test.dart +0 -172
  115. package/skills/flutter-skills/tool/dart_skills_lint/test/ignore_models_test.dart +0 -63
  116. package/skills/flutter-skills/tool/dart_skills_lint/test/metadata_validation_test.dart +0 -116
  117. package/skills/flutter-skills/tool/dart_skills_lint/test/relative_path_flag_test.dart +0 -70
  118. package/skills/flutter-skills/tool/dart_skills_lint/test/relative_paths_test.dart +0 -172
  119. package/skills/flutter-skills/tool/dart_skills_lint/test/resolve_rules_test.dart +0 -82
  120. package/skills/flutter-skills/tool/dart_skills_lint/test/rule_naming_test.dart +0 -29
  121. package/skills/flutter-skills/tool/dart_skills_lint/test/skills_ignores_storage_test.dart +0 -89
  122. package/skills/flutter-skills/tool/dart_skills_lint/test/test_utils.dart +0 -19
  123. package/skills/flutter-skills/tool/dart_skills_lint/test/trailing_whitespace_test.dart +0 -152
  124. package/skills/flutter-skills/tool/generator/README.md +0 -150
  125. package/skills/flutter-skills/tool/generator/analysis_options.yaml +0 -143
  126. package/skills/flutter-skills/tool/generator/bin/skills.dart +0 -73
  127. package/skills/flutter-skills/tool/generator/lib/src/commands/base_skill_command.dart +0 -87
  128. package/skills/flutter-skills/tool/generator/lib/src/commands/base_yaml_command.dart +0 -83
  129. package/skills/flutter-skills/tool/generator/lib/src/commands/generate_skill_command.dart +0 -92
  130. package/skills/flutter-skills/tool/generator/lib/src/commands/update_readme_command.dart +0 -150
  131. package/skills/flutter-skills/tool/generator/lib/src/commands/update_skill_command.dart +0 -97
  132. package/skills/flutter-skills/tool/generator/lib/src/commands/validate_skill_command.dart +0 -284
  133. package/skills/flutter-skills/tool/generator/lib/src/models/skill_params.dart +0 -41
  134. package/skills/flutter-skills/tool/generator/lib/src/services/gemini_service.dart +0 -310
  135. package/skills/flutter-skills/tool/generator/lib/src/services/markdown_converter.dart +0 -226
  136. package/skills/flutter-skills/tool/generator/lib/src/services/prompts.dart +0 -72
  137. package/skills/flutter-skills/tool/generator/lib/src/services/resource_fetcher_service.dart +0 -84
  138. package/skills/flutter-skills/tool/generator/lib/src/services/skill_instructions.dart +0 -30
  139. package/skills/flutter-skills/tool/generator/pubspec.yaml +0 -32
  140. package/skills/flutter-skills/tool/generator/test/commands/base_skill_command_test.dart +0 -131
  141. package/skills/flutter-skills/tool/generator/test/commands/validate_skills_input_test.dart +0 -263
  142. package/skills/flutter-skills/tool/generator/test/custom_skill_rules/last_modified_rule.dart +0 -32
  143. package/skills/flutter-skills/tool/generator/test/generate_skills_retry_test.dart +0 -105
  144. package/skills/flutter-skills/tool/generator/test/generate_skills_test.dart +0 -519
  145. package/skills/flutter-skills/tool/generator/test/lint_skills_test.dart +0 -34
  146. package/skills/flutter-skills/tool/generator/test/markdown_converter_test.dart +0 -103
  147. package/skills/flutter-skills/tool/generator/test/markdown_table_test.dart +0 -131
  148. package/skills/flutter-skills/tool/generator/test/models/skill_params_test.dart +0 -37
  149. package/skills/flutter-skills/tool/generator/test/services/gemini_service_test.dart +0 -291
  150. package/skills/flutter-skills/tool/generator/test/services/markdown_converter_test.dart +0 -156
  151. package/skills/flutter-skills/tool/generator/test/services/resource_fetcher_service_test.dart +0 -188
  152. package/skills/flutter-skills/tool/generator/test/update_skills_test.dart +0 -241
  153. package/skills/flutter-skills/tool/generator/test/validate_skills_test.dart +0 -728
  154. /package/skills/{.agents → flutter}/skills/flutter-add-integration-test/SKILL.md +0 -0
  155. /package/skills/{.agents → flutter}/skills/flutter-add-widget-preview/SKILL.md +0 -0
  156. /package/skills/{.agents → flutter}/skills/flutter-add-widget-test/SKILL.md +0 -0
  157. /package/skills/{.agents → flutter}/skills/flutter-apply-architecture-best-practices/SKILL.md +0 -0
  158. /package/skills/{.agents → flutter}/skills/flutter-build-responsive-layout/SKILL.md +0 -0
  159. /package/skills/{.agents → flutter}/skills/flutter-fix-layout-issues/SKILL.md +0 -0
  160. /package/skills/{.agents → flutter}/skills/flutter-implement-json-serialization/SKILL.md +0 -0
  161. /package/skills/{.agents → flutter}/skills/flutter-setup-declarative-routing/SKILL.md +0 -0
  162. /package/skills/{.agents → flutter}/skills/flutter-setup-localization/SKILL.md +0 -0
  163. /package/skills/{.agents → flutter}/skills/flutter-use-http-package/SKILL.md +0 -0
@@ -1,728 +0,0 @@
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:convert';
6
- import 'dart:io';
7
-
8
- import 'package:args/command_runner.dart';
9
- import 'package:http/http.dart' as http;
10
- import 'package:http/testing.dart';
11
- import 'package:logging/logging.dart';
12
- import 'package:path/path.dart' as p;
13
- import 'package:skills/src/commands/validate_skill_command.dart';
14
- import 'package:test/test.dart';
15
-
16
- void main() {
17
- group('ValidateSkillCommand', () {
18
- late CommandRunner<void> runner;
19
- late Directory tempDir;
20
- late Directory skillsDir;
21
- late Directory validationDir;
22
- late MockClient mockClient;
23
- final logs = <String>[];
24
-
25
- setUp(() async {
26
- tempDir = await Directory.systemTemp.createTemp('validate_skills_test');
27
- skillsDir = Directory(p.join(tempDir.path, 'skills'));
28
- await skillsDir.create();
29
- validationDir = Directory(p.join(tempDir.path, 'validation'));
30
- await validationDir.create();
31
-
32
- mockClient = MockClient((request) async {
33
- return http.Response('', 200);
34
- });
35
-
36
- // Capture logs
37
- Logger.root.level = Level.INFO;
38
- Logger.root.onRecord.listen((record) {
39
- logs.add(record.message);
40
- });
41
- logs.clear();
42
- });
43
-
44
- tearDown(() async {
45
- await tempDir.delete(recursive: true);
46
- logs.clear();
47
- });
48
-
49
- test('validates single skill defined in config', () async {
50
- const skillName = 'test-skill';
51
- final skillDir = Directory(p.join(skillsDir.path, skillName));
52
- await skillDir.create();
53
- final skillFile = File(p.join(skillDir.path, 'SKILL.md'));
54
- await skillFile.writeAsString('content');
55
-
56
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
57
- await configFile.writeAsString(
58
- jsonEncode([
59
- {
60
- 'name': skillName,
61
- 'description': 'Description',
62
- 'resources': ['https://example.com'],
63
- },
64
- ]),
65
- );
66
-
67
- runner = CommandRunner<void>('skills', 'Test runner')
68
- ..addCommand(
69
- ValidateSkillCommand(
70
- environment: {'GEMINI_API_KEY': 'test-key'},
71
- outputDir: skillsDir,
72
- validationDir: validationDir,
73
- httpClient: mockClient,
74
- ),
75
- );
76
-
77
- await IOOverrides.runZoned(() async {
78
- await runner.run(['validate-skill', configFile.path]);
79
- }, getCurrentDirectory: () => tempDir);
80
-
81
- expect(logs, contains('Validating skill: $skillName...'));
82
- });
83
-
84
- test('validates and grades single skill', () async {
85
- const skillName = 'test-skill';
86
- final skillDir = Directory(p.join(skillsDir.path, skillName));
87
- await skillDir.create();
88
- final skillFile = File(p.join(skillDir.path, 'SKILL.md'));
89
- await skillFile.writeAsString('Existing content');
90
-
91
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
92
- await configFile.writeAsString(
93
- jsonEncode([
94
- {
95
- 'name': skillName,
96
- 'description': 'Description',
97
- 'resources': ['https://example.com/source'],
98
- },
99
- ]),
100
- );
101
-
102
- final mockClient = MockClient((request) async {
103
- final url = request.url.toString();
104
- if (url == 'https://example.com/source') {
105
- return http.Response(
106
- '<html><body><h1>Source</h1></body></html>',
107
- 200,
108
- );
109
- }
110
- if (url.contains('generativelanguage')) {
111
- return http.Response(
112
- jsonEncode({
113
- 'candidates': [
114
- {
115
- 'content': {
116
- 'parts': [
117
- {
118
- 'text':
119
- '# Validation Report\n\n'
120
- '- Accuracy: High\n'
121
- '- Structure: Correct\n'
122
- '- Completeness: Good\n\n'
123
- 'Conclusion: Valid\n'
124
- 'Similarity Score: 85',
125
- },
126
- ],
127
- },
128
- },
129
- ],
130
- }),
131
- 200,
132
- );
133
- }
134
- return http.Response('Not Found', 404);
135
- });
136
-
137
- runner = CommandRunner<void>('skills', 'Test runner')
138
- ..addCommand(
139
- ValidateSkillCommand(
140
- environment: {'GEMINI_API_KEY': 'test-key'},
141
- outputDir: skillsDir,
142
- validationDir: validationDir,
143
- httpClient: mockClient,
144
- ),
145
- );
146
-
147
- await IOOverrides.runZoned(() async {
148
- await runner.run(['validate-skill', configFile.path]);
149
- }, getCurrentDirectory: () => tempDir);
150
-
151
- expect(logs, contains('Validating skill: $skillName...'));
152
- expect(logs, contains(contains('Validation report written to')));
153
- });
154
-
155
- test('validates all skills in config', () async {
156
- const skill1Name = 'skill1';
157
- final skill1Dir = Directory(p.join(skillsDir.path, skill1Name));
158
- await skill1Dir.create();
159
- File(p.join(skill1Dir.path, 'SKILL.md')).writeAsStringSync('content');
160
-
161
- const skill2Name = 'skill2';
162
- final skill2Dir = Directory(p.join(skillsDir.path, skill2Name));
163
- await skill2Dir.create();
164
- File(p.join(skill2Dir.path, 'SKILL.md')).writeAsStringSync('content');
165
-
166
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
167
- await configFile.writeAsString(
168
- jsonEncode([
169
- {
170
- 'name': skill1Name,
171
- 'description': 'Desc 1',
172
- 'resources': ['https://example.com/1'],
173
- },
174
- {
175
- 'name': skill2Name,
176
- 'description': 'Desc 2',
177
- 'resources': ['https://example.com/2'],
178
- },
179
- ]),
180
- );
181
-
182
- runner = CommandRunner<void>('skills', 'Test runner')
183
- ..addCommand(
184
- ValidateSkillCommand(
185
- environment: {'GEMINI_API_KEY': 'test-key'},
186
- outputDir: skillsDir,
187
- validationDir: validationDir,
188
- httpClient: mockClient,
189
- ),
190
- );
191
-
192
- await IOOverrides.runZoned(() async {
193
- await runner.run(['validate-skill', configFile.path]);
194
- }, getCurrentDirectory: () => tempDir);
195
-
196
- expect(logs, contains('Validating skill: $skill1Name...'));
197
- expect(logs, contains('Validating skill: $skill2Name...'));
198
- });
199
-
200
- test('logs severe error when config file not found', () async {
201
- final path = p.join(tempDir.path, 'NON_EXISTENT.yaml');
202
-
203
- runner = CommandRunner<void>('skills', 'Test runner')
204
- ..addCommand(
205
- ValidateSkillCommand(
206
- environment: {'GEMINI_API_KEY': 'test-key'},
207
- outputDir: skillsDir,
208
- httpClient: mockClient,
209
- ),
210
- );
211
-
212
- await IOOverrides.runZoned(() async {
213
- await runner.run(['validate-skill', path]);
214
- }, getCurrentDirectory: () => tempDir);
215
-
216
- expect(logs, contains('Configuration file not found: $path'));
217
- });
218
-
219
- test('logs warning when existing skill file not found', () async {
220
- const skillName = 'missing-skill';
221
- // Do NOT create skill directory or file
222
-
223
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
224
- await configFile.writeAsString(
225
- jsonEncode([
226
- {
227
- 'name': skillName,
228
- 'description': 'Desc',
229
- 'resources': ['https://example.com'],
230
- },
231
- ]),
232
- );
233
-
234
- final mockClient = MockClient((request) async {
235
- final url = request.url.toString();
236
- if (url == 'https://example.com') {
237
- return http.Response('Content', 200);
238
- }
239
- if (url.contains('generativelanguage')) {
240
- return http.Response(
241
- '{"candidates": [{"content": {"parts": [{"text": "Generated Content"}]}}]}',
242
- 200,
243
- );
244
- }
245
- return http.Response('Not Found', 404);
246
- });
247
-
248
- runner = CommandRunner<void>('skills', 'Test runner')
249
- ..addCommand(
250
- ValidateSkillCommand(
251
- environment: {'GEMINI_API_KEY': 'test-key'},
252
- outputDir: skillsDir,
253
- httpClient: mockClient,
254
- ),
255
- );
256
-
257
- await IOOverrides.runZoned(() async {
258
- await runner.run(['validate-skill', configFile.path]);
259
- }, getCurrentDirectory: () => tempDir);
260
-
261
- expect(logs, contains(contains('Existing skill file not found')));
262
- });
263
-
264
- test('logs warning when failed to fetch URL', () async {
265
- const skillName = 'fail-skill';
266
- final skillDir = Directory(p.join(skillsDir.path, skillName));
267
- await skillDir.create();
268
- File(p.join(skillDir.path, 'SKILL.md')).writeAsStringSync('content');
269
-
270
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
271
- await configFile.writeAsString(
272
- jsonEncode([
273
- {
274
- 'name': skillName,
275
- 'description': 'Desc',
276
- 'resources': ['https://example.com/fail'],
277
- },
278
- ]),
279
- );
280
-
281
- final mockClient = MockClient((request) async {
282
- // Throw to trigger the 'Error fetching' catch block
283
- throw Exception('Network Error');
284
- });
285
-
286
- runner = CommandRunner<void>('skills', 'Test runner')
287
- ..addCommand(
288
- ValidateSkillCommand(
289
- environment: {'GEMINI_API_KEY': 'test-key'},
290
- outputDir: skillsDir,
291
- httpClient: mockClient,
292
- ),
293
- );
294
-
295
- await runner.run(['validate-skill', configFile.path]);
296
-
297
- expect(
298
- logs,
299
- contains(
300
- contains('Error validating $skillName: Exception: Network Error'),
301
- ),
302
- );
303
- });
304
-
305
- test(
306
- 'logs severe error when grading fails repeatedly/exceptionally',
307
- () async {
308
- const skillName = 'grade-fail';
309
- final skillDir = Directory(p.join(skillsDir.path, skillName));
310
- await skillDir.create();
311
- File(p.join(skillDir.path, 'SKILL.md')).writeAsStringSync('content');
312
-
313
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
314
- await configFile.writeAsString(
315
- jsonEncode([
316
- {
317
- 'name': skillName,
318
- 'description': 'Desc',
319
- 'resources': ['https://example.com/source'],
320
- },
321
- ]),
322
- );
323
-
324
- final mockClient = MockClient((request) async {
325
- if (request.url.toString() == 'https://example.com/source') {
326
- return http.Response('# Source', 200);
327
- }
328
- if (request.url.toString().contains('generativelanguage')) {
329
- // Validation (grading) fails
330
- throw Exception('Gemini API Error');
331
- }
332
- return http.Response('Not Found', 404);
333
- });
334
-
335
- runner = CommandRunner<void>('skills', 'Test runner')
336
- ..addCommand(
337
- ValidateSkillCommand(
338
- environment: {'GEMINI_API_KEY': 'test-key'},
339
- outputDir: skillsDir,
340
- validationDir: validationDir,
341
- httpClient: mockClient,
342
- ),
343
- );
344
-
345
- await runner.run(['validate-skill', configFile.path]);
346
-
347
- expect(
348
- logs,
349
- contains(contains('Failed to generate validation report')),
350
- );
351
- },
352
- );
353
-
354
- test('logs severe error when Gemini grading throws exception', () async {
355
- const skillName = 'gemini-fail';
356
- final skillDir = Directory(p.join(skillsDir.path, skillName));
357
- await skillDir.create();
358
- // Ensure name matches to avoid name mismatch error
359
- File(
360
- p.join(skillDir.path, 'SKILL.md'),
361
- ).writeAsStringSync('name: $skillName\nContent');
362
-
363
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
364
- await configFile.writeAsString(
365
- jsonEncode([
366
- {
367
- 'name': skillName,
368
- 'description': 'Desc',
369
- 'resources': ['https://example.com/source'],
370
- },
371
- ]),
372
- );
373
-
374
- final mockClient = MockClient((request) async {
375
- if (request.url.toString() == 'https://example.com/source') {
376
- return http.Response('# Source', 200);
377
- }
378
- if (request.url.toString().contains('generativelanguage')) {
379
- // Validation (grading) throws
380
- throw Exception('Gemini API Error');
381
- }
382
- return http.Response('Not Found', 404);
383
- });
384
-
385
- runner = CommandRunner<void>('skills', 'Test runner')
386
- ..addCommand(
387
- ValidateSkillCommand(
388
- environment: {'GEMINI_API_KEY': 'test-key'},
389
- outputDir: skillsDir,
390
- validationDir: validationDir,
391
- httpClient: mockClient,
392
- ),
393
- );
394
-
395
- await runner.run(['validate-skill', configFile.path]);
396
-
397
- expect(logs, contains(contains('Failed to generate validation report')));
398
- });
399
-
400
- test('logs severe error when skill name/description mismatch', () async {
401
- const skillName = 'mismatch-skill';
402
- final skillDir = Directory(p.join(skillsDir.path, skillName));
403
- await skillDir.create();
404
- // Write content WITHOUT name/description
405
- final skillFile = File(p.join(skillDir.path, 'SKILL.md'));
406
- await skillFile.writeAsString('Invalid content without frontmatter');
407
-
408
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
409
- await configFile.writeAsString(
410
- jsonEncode([
411
- {
412
- 'name': skillName,
413
- 'description': 'Expected Description',
414
- 'resources': ['https://example.com/source'],
415
- },
416
- ]),
417
- );
418
-
419
- final mockClient = MockClient((request) async {
420
- final url = request.url.toString();
421
- if (url == 'https://example.com/source') {
422
- return http.Response('# Source', 200);
423
- }
424
- if (url.contains('generativelanguage')) {
425
- return http.Response(
426
- '{"candidates": [{"content": {"parts": [{"text": "Generated Content"}]}}]}',
427
- 200,
428
- );
429
- }
430
- return http.Response('Not Found', 404);
431
- });
432
-
433
- runner = CommandRunner<void>('skills', 'Test runner')
434
- ..addCommand(
435
- ValidateSkillCommand(
436
- environment: {'GEMINI_API_KEY': 'test-key'},
437
- outputDir: skillsDir,
438
- validationDir: validationDir,
439
- httpClient: mockClient,
440
- ),
441
- );
442
-
443
- await IOOverrides.runZoned(() async {
444
- await runner.run(['validate-skill', configFile.path]);
445
- }, getCurrentDirectory: () => tempDir);
446
-
447
- expect(
448
- logs,
449
- contains(contains('Validation Failed: Skill name mismatch')),
450
- );
451
- expect(
452
- logs,
453
- contains(contains('Validation Failed: Skill name mismatch')),
454
- );
455
- // Description check was removed from the command
456
- expect(
457
- logs,
458
- isNot(
459
- contains(contains('Validation Failed: Skill description mismatch')),
460
- ),
461
- );
462
- });
463
-
464
- test('sends source URL header to Gemini during regeneration', () async {
465
- const skillName = 'header-skill';
466
- final skillDir = Directory(p.join(skillsDir.path, skillName));
467
- await skillDir.create();
468
- final skillFile = File(p.join(skillDir.path, 'SKILL.md'));
469
- await skillFile.writeAsString('''
470
- name: $skillName
471
- description: Desc
472
- ---
473
- Content
474
- ''');
475
-
476
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
477
- await configFile.writeAsString(
478
- jsonEncode([
479
- {
480
- 'name': skillName,
481
- 'description': 'Desc',
482
- 'resources': ['https://example.com/source'],
483
- },
484
- ]),
485
- );
486
-
487
- final geminiRequests = <String>[];
488
- final mockClient = MockClient((request) async {
489
- final url = request.url.toString();
490
- if (url == 'https://example.com/source') {
491
- return http.Response('Source Content', 200);
492
- }
493
- if (url.contains('generativelanguage')) {
494
- geminiRequests.add(request.body);
495
- return http.Response(
496
- jsonEncode({
497
- 'candidates': [
498
- {
499
- 'content': {
500
- 'parts': [
501
- {'text': 'Generated Content\nSimilarity Score: 50'},
502
- ],
503
- },
504
- },
505
- ],
506
- }),
507
- 200,
508
- );
509
- }
510
- return http.Response('Not Found', 404);
511
- });
512
-
513
- runner = CommandRunner<void>('skills', 'Test runner')
514
- ..addCommand(
515
- ValidateSkillCommand(
516
- environment: {'GEMINI_API_KEY': 'test-key'},
517
- outputDir: skillsDir,
518
- httpClient: mockClient,
519
- validationDir: validationDir,
520
- ),
521
- );
522
-
523
- await IOOverrides.runZoned(() async {
524
- await runner.run(['validate-skill', configFile.path]);
525
- }, getCurrentDirectory: () => tempDir);
526
-
527
- expect(geminiRequests, isNotEmpty);
528
- expect(
529
- geminiRequests.first,
530
- contains('--- Raw content from https://example.com/source ---'),
531
- );
532
- });
533
- test('fails upfront validation when resources list is empty', () async {
534
- const skillName = 'empty-fetch-val';
535
- final skillDir = Directory(p.join(skillsDir.path, skillName));
536
- await skillDir.create();
537
- File(
538
- p.join(skillDir.path, 'SKILL.md'),
539
- ).writeAsStringSync('name: $skillName\ncontent');
540
-
541
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
542
- await configFile.writeAsString(
543
- jsonEncode([
544
- {'name': skillName, 'description': 'Desc', 'resources': <String>[]},
545
- ]),
546
- );
547
-
548
- runner = CommandRunner<void>('skills', 'Test runner')
549
- ..addCommand(
550
- ValidateSkillCommand(
551
- environment: {'GEMINI_API_KEY': 'test-key'},
552
- outputDir: skillsDir,
553
- httpClient: mockClient,
554
- ),
555
- );
556
-
557
- await runner.run(['validate-skill', configFile.path]);
558
- expect(
559
- logs,
560
- contains(
561
- 'Skill "empty-fetch-val" field "resources" must not be empty.',
562
- ),
563
- );
564
- expect(logs, contains('Configuration validation failed.'));
565
- });
566
-
567
- test('logs severe error on generic exception during validation', () async {
568
- const skillName = 'exception-val';
569
- final skillDir = Directory(p.join(skillsDir.path, skillName));
570
- await skillDir.create();
571
- File(p.join(skillDir.path, 'SKILL.md')).writeAsStringSync('content');
572
-
573
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
574
- await configFile.writeAsString(
575
- jsonEncode([
576
- {
577
- 'name': skillName,
578
- 'description': 'Desc',
579
- 'resources': ['https://example.com/source'],
580
- },
581
- ]),
582
- );
583
-
584
- final mockClient = MockClient(
585
- (request) async => throw Exception('Generic Error'),
586
- );
587
-
588
- runner = CommandRunner<void>('skills', 'Test runner')
589
- ..addCommand(
590
- ValidateSkillCommand(
591
- environment: {'GEMINI_API_KEY': 'test-key'},
592
- outputDir: skillsDir,
593
- httpClient: mockClient,
594
- ),
595
- );
596
-
597
- await runner.run(['validate-skill', configFile.path]);
598
- expect(
599
- logs,
600
- contains(
601
- contains('Error validating $skillName: Exception: Generic Error'),
602
- ),
603
- );
604
- });
605
-
606
- test('validates with missing metadata fallbacks', () async {
607
- const skillName = 'fallback-meta';
608
- final skillDir = Directory(p.join(skillsDir.path, skillName));
609
- await skillDir.create();
610
- File(
611
- p.join(skillDir.path, 'SKILL.md'),
612
- ).writeAsStringSync('name: $skillName\ncontent WITHOUT metadata');
613
-
614
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
615
- await configFile.writeAsString(
616
- jsonEncode([
617
- {
618
- 'name': skillName,
619
- 'description': 'Desc',
620
- 'resources': ['https://example.com/source'],
621
- },
622
- ]),
623
- );
624
-
625
- final mockClient = MockClient((request) async {
626
- if (request.url.toString() == 'https://example.com/source') {
627
- return http.Response('# Source', 200);
628
- }
629
- if (request.url.toString().contains('generativelanguage')) {
630
- return http.Response(
631
- jsonEncode({
632
- 'candidates': [
633
- {
634
- 'content': {
635
- 'parts': [
636
- {'text': 'Generated Content\nGrade: 100'},
637
- ],
638
- },
639
- },
640
- ],
641
- }),
642
- 200,
643
- );
644
- }
645
- return http.Response('Not Found', 404);
646
- });
647
-
648
- runner = CommandRunner<void>('skills', 'Test runner')
649
- ..addCommand(
650
- ValidateSkillCommand(
651
- environment: {'GEMINI_API_KEY': 'test-key'},
652
- outputDir: skillsDir,
653
- validationDir: validationDir,
654
- httpClient: mockClient,
655
- ),
656
- );
657
-
658
- await runner.run(['validate-skill', configFile.path]);
659
- expect(logs, contains(contains('Validation report written to')));
660
- });
661
-
662
- test('dry run fetches content but skips gemini and file writes', () async {
663
- const skillName = 'dry-validate';
664
- final skillDir = Directory(p.join(skillsDir.path, skillName));
665
- await skillDir.create();
666
- File(
667
- p.join(skillDir.path, 'SKILL.md'),
668
- ).writeAsStringSync('name: $skillName\nExisting content');
669
-
670
- final configFile = File(p.join(tempDir.path, 'config.yaml'));
671
- await configFile.writeAsString(
672
- jsonEncode([
673
- {
674
- 'name': skillName,
675
- 'description': 'Desc',
676
- 'resources': ['https://example.com/source'],
677
- },
678
- ]),
679
- );
680
-
681
- var geminiCalled = false;
682
- final mockClient = MockClient((request) async {
683
- if (request.url.toString() == 'https://example.com/source') {
684
- return http.Response('# Source', 200);
685
- }
686
- if (request.url.toString().contains('generativelanguage')) {
687
- geminiCalled = true;
688
- return http.Response(
689
- jsonEncode({
690
- 'candidates': [
691
- {
692
- 'content': {
693
- 'parts': [
694
- {'text': 'Generated Content\nGrade: 100'},
695
- ],
696
- },
697
- },
698
- ],
699
- }),
700
- 200,
701
- );
702
- }
703
- return http.Response('Not Found', 404);
704
- });
705
-
706
- runner = CommandRunner<void>('skills', 'Test runner')
707
- ..addCommand(
708
- ValidateSkillCommand(
709
- environment: {'GEMINI_API_KEY': 'test-key'},
710
- outputDir: skillsDir,
711
- validationDir: validationDir,
712
- httpClient: mockClient,
713
- ),
714
- );
715
-
716
- await runner.run(['validate-skill', '--dry-run', configFile.path]);
717
-
718
- expect(geminiCalled, isFalse);
719
- expect(
720
- logs,
721
- contains(contains('[DRY RUN] Would validate skill: $skillName')),
722
- );
723
-
724
- final valDir = Directory(p.join(validationDir.path, skillName));
725
- expect(valDir.existsSync(), isFalse);
726
- });
727
- });
728
- }