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,178 @@
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:dart_skills_lint/src/models/skill_context.dart';
8
+ import 'package:dart_skills_lint/src/rules/description_length_rule.dart';
9
+ import 'package:dart_skills_lint/src/rules/name_format_rule.dart';
10
+ import 'package:dart_skills_lint/src/rules/valid_yaml_metadata_rule.dart';
11
+ import 'package:dart_skills_lint/src/validator.dart';
12
+ import 'package:test/test.dart';
13
+ import 'package:yaml/yaml.dart';
14
+
15
+ import 'test_utils.dart';
16
+
17
+ void main() {
18
+ group('Field Specific Constraints Validation', () {
19
+ late Directory tempDir;
20
+
21
+ setUp(() async {
22
+ tempDir = await Directory.systemTemp.createTemp('fields_test.');
23
+ });
24
+
25
+ tearDown(() async {
26
+ if (tempDir.existsSync()) {
27
+ await tempDir.delete(recursive: true);
28
+ }
29
+ });
30
+
31
+ group('Skill Name', () {
32
+ test('fails if not lowercase', () async {
33
+ final Directory skillDir = await Directory('${tempDir.path}/Skill-Name').create();
34
+ await File('${skillDir.path}/SKILL.md').writeAsString('${buildFrontmatter()}Body');
35
+ final validator = Validator();
36
+ final ValidationResult result = await validator.validate(skillDir);
37
+ expect(result.isValid, isFalse);
38
+ expect(result.errors, contains(contains('lowercase')));
39
+ });
40
+
41
+ test('fails if too long (> ${NameFormatRule.maxNameLength} chars)', () async {
42
+ final String longName = 'a' * (NameFormatRule.maxNameLength + 1);
43
+ final Directory skillDir = await Directory('${tempDir.path}/$longName').create();
44
+ await File(
45
+ '${skillDir.path}/SKILL.md',
46
+ ).writeAsString('${buildFrontmatter(name: longName)}Body');
47
+ final validator = Validator();
48
+ final ValidationResult result = await validator.validate(skillDir);
49
+ expect(result.isValid, isFalse);
50
+ expect(
51
+ result.errors,
52
+ contains(contains('Maximum ${NameFormatRule.maxNameLength} characters')),
53
+ );
54
+ });
55
+
56
+ test('fails if contains invalid characters', () async {
57
+ final Directory skillDir = await Directory('${tempDir.path}/skill_name').create();
58
+ await File(
59
+ '${skillDir.path}/SKILL.md',
60
+ ).writeAsString('${buildFrontmatter(name: 'skill_name')}Body');
61
+ final validator = Validator();
62
+ final ValidationResult result = await validator.validate(skillDir);
63
+ expect(result.isValid, isFalse);
64
+ expect(result.errors, contains(contains('lowercase letters, digits, and hyphens')));
65
+ });
66
+
67
+ test('fails if has leading hyphen', () async {
68
+ final Directory skillDir = await Directory('${tempDir.path}/-skill-name').create();
69
+ await File(
70
+ '${skillDir.path}/SKILL.md',
71
+ ).writeAsString('${buildFrontmatter(name: '-skill-name')}Body');
72
+ final validator = Validator();
73
+ final ValidationResult result = await validator.validate(skillDir);
74
+ expect(result.isValid, isFalse);
75
+ expect(result.errors, contains(contains('leading or trailing hyphens')));
76
+ });
77
+
78
+ test('fails if has trailing hyphen', () async {
79
+ final Directory skillDir = await Directory('${tempDir.path}/skill-name-').create();
80
+ await File(
81
+ '${skillDir.path}/SKILL.md',
82
+ ).writeAsString('${buildFrontmatter(name: 'skill-name-')}Body');
83
+ final validator = Validator();
84
+ final ValidationResult result = await validator.validate(skillDir);
85
+ expect(result.isValid, isFalse);
86
+ expect(result.errors, contains(contains('leading or trailing hyphens')));
87
+ });
88
+
89
+ test('fails if has consecutive hyphens', () async {
90
+ final Directory skillDir = await Directory('${tempDir.path}/skill--name').create();
91
+ await File(
92
+ '${skillDir.path}/SKILL.md',
93
+ ).writeAsString('${buildFrontmatter(name: 'skill--name')}Body');
94
+ final validator = Validator();
95
+ final ValidationResult result = await validator.validate(skillDir);
96
+ expect(result.isValid, isFalse);
97
+ expect(result.errors, contains(contains('consecutive hyphens')));
98
+ });
99
+
100
+ test('fails if name does not match directory name', () async {
101
+ final Directory skillDir = await Directory('${tempDir.path}/wrong-name').create();
102
+ await File(
103
+ '${skillDir.path}/SKILL.md',
104
+ ).writeAsString('${buildFrontmatter(name: 'right-name')}Body');
105
+ final validator = Validator();
106
+ final ValidationResult result = await validator.validate(skillDir);
107
+ expect(result.isValid, isFalse);
108
+ expect(result.errors, contains(contains('must exactly match the parent directory name')));
109
+ });
110
+
111
+ test('fixes name to match directory name (not replacing underscores)', () async {
112
+ final Directory skillDir = await Directory('${tempDir.path}/my_skill').create();
113
+ final file = File('${skillDir.path}/SKILL.md');
114
+ await file.writeAsString('''
115
+ ---
116
+ name: wrong-name
117
+ description: A test skill
118
+ ---
119
+ Body''');
120
+
121
+ final rule = NameFormatRule();
122
+ final String content = await file.readAsString();
123
+ final RegExpMatch? match = RegExp(
124
+ r'^---\s*\n(.*?)\n---\s*\n',
125
+ dotAll: true,
126
+ ).firstMatch(content);
127
+ final parsedYaml = loadYaml(match!.group(1)!) as YamlMap?;
128
+ final context = SkillContext(
129
+ directory: skillDir,
130
+ rawContent: content,
131
+ parsedYaml: parsedYaml,
132
+ );
133
+
134
+ final String fixedContent = await rule.fix('SKILL.md', content, context.directory);
135
+
136
+ expect(fixedContent, contains('name: my_skill'));
137
+ });
138
+ });
139
+
140
+ group('Description', () {
141
+ test('fails if too long (> ${DescriptionLengthRule.maxDescriptionLength} chars)', () async {
142
+ final String longDesc = 'a' * (DescriptionLengthRule.maxDescriptionLength + 1);
143
+ final Directory skillDir = await Directory('${tempDir.path}/skill-name').create();
144
+ await File(
145
+ '${skillDir.path}/SKILL.md',
146
+ ).writeAsString('${buildFrontmatter(name: 'skill-name', description: longDesc)}Body');
147
+ final validator = Validator();
148
+ final ValidationResult result = await validator.validate(skillDir);
149
+ expect(result.isValid, isFalse);
150
+ expect(
151
+ result.errors,
152
+ contains(contains('Maximum ${DescriptionLengthRule.maxDescriptionLength} characters')),
153
+ );
154
+ });
155
+ });
156
+
157
+ group('Compatibility', () {
158
+ test('fails if too long (> ${ValidYamlMetadataRule.maxCompatibilityLength} chars)', () async {
159
+ final String longComp = 'a' * (ValidYamlMetadataRule.maxCompatibilityLength + 1);
160
+ final Directory skillDir = await Directory('${tempDir.path}/skill-name').create();
161
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
162
+ ---
163
+ name: skill-name
164
+ description: A test skill
165
+ compatibility: $longComp
166
+ ---
167
+ Body''');
168
+ final validator = Validator();
169
+ final ValidationResult result = await validator.validate(skillDir);
170
+ expect(result.isValid, isFalse);
171
+ expect(
172
+ result.errors,
173
+ contains(contains('Maximum ${ValidYamlMetadataRule.maxCompatibilityLength} characters')),
174
+ );
175
+ });
176
+ });
177
+ });
178
+ }
@@ -0,0 +1,172 @@
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:dart_skills_lint/src/entry_point.dart';
8
+ import 'package:dart_skills_lint/src/fixable_rule.dart';
9
+ import 'package:dart_skills_lint/src/models/analysis_severity.dart';
10
+ import 'package:dart_skills_lint/src/models/skill_context.dart';
11
+ import 'package:dart_skills_lint/src/models/skill_rule.dart';
12
+ import 'package:dart_skills_lint/src/models/validation_error.dart';
13
+ import 'package:path/path.dart' as p;
14
+ import 'package:test/test.dart';
15
+
16
+ class RuleA extends SkillRule implements FixableRule {
17
+ @override
18
+ String get name => 'rule-a';
19
+
20
+ @override
21
+ AnalysisSeverity get severity => AnalysisSeverity.warning;
22
+
23
+ @override
24
+ Future<List<ValidationError>> validate(SkillContext context) async {
25
+ return [
26
+ ValidationError(
27
+ ruleId: name,
28
+ message: 'Error A',
29
+ severity: AnalysisSeverity.warning,
30
+ file: 'SKILL.md',
31
+ ),
32
+ ];
33
+ }
34
+
35
+ @override
36
+ Future<String> fix(String filePath, String currentContent, Directory directory) async {
37
+ return '$currentContent A';
38
+ }
39
+ }
40
+
41
+ class RuleB extends SkillRule implements FixableRule {
42
+ @override
43
+ String get name => 'rule-b';
44
+
45
+ @override
46
+ AnalysisSeverity get severity => AnalysisSeverity.warning;
47
+
48
+ @override
49
+ Future<List<ValidationError>> validate(SkillContext context) async {
50
+ return [
51
+ ValidationError(
52
+ ruleId: name,
53
+ message: 'Error B',
54
+ severity: AnalysisSeverity.warning,
55
+ file: 'SKILL.md',
56
+ ),
57
+ ];
58
+ }
59
+
60
+ @override
61
+ Future<String> fix(String filePath, String currentContent, Directory directory) async {
62
+ return '$currentContent B';
63
+ }
64
+ }
65
+
66
+ class RuleThrows extends SkillRule implements FixableRule {
67
+ @override
68
+ String get name => 'rule-throws';
69
+
70
+ @override
71
+ AnalysisSeverity get severity => AnalysisSeverity.warning;
72
+
73
+ @override
74
+ Future<List<ValidationError>> validate(SkillContext context) async {
75
+ return [
76
+ ValidationError(
77
+ ruleId: name,
78
+ message: 'Error Throws',
79
+ severity: AnalysisSeverity.warning,
80
+ file: 'SKILL.md',
81
+ ),
82
+ ];
83
+ }
84
+
85
+ @override
86
+ Future<String> fix(String filePath, String currentContent, Directory directory) async {
87
+ throw Exception('Fix failed');
88
+ }
89
+ }
90
+
91
+ void main() {
92
+ group('Fixer Sequential Execution', () {
93
+ late Directory tempDir;
94
+
95
+ setUp(() async {
96
+ tempDir = await Directory.systemTemp.createTemp('fixer_test.');
97
+ });
98
+
99
+ tearDown(() async {
100
+ await tempDir.delete(recursive: true);
101
+ });
102
+
103
+ test('applies fixes in order', () async {
104
+ final skillDir = Directory(p.join(tempDir.path, 'test-skill'));
105
+ await skillDir.create();
106
+ final skillFile = File(p.join(skillDir.path, 'SKILL.md'));
107
+ await skillFile.writeAsString('Original');
108
+
109
+ final bool success = await validateSkillsInternal(
110
+ individualSkillPaths: [skillDir.path],
111
+ fixApply: true,
112
+ quiet: true,
113
+ customRules: [RuleA(), RuleB()],
114
+ );
115
+
116
+ expect(success, isFalse);
117
+
118
+ final String content = await skillFile.readAsString();
119
+ expect(content, 'Original A B');
120
+ });
121
+
122
+ test(
123
+ '--fast-fail stops processing subsequent skills but completes current skill fixes',
124
+ () async {
125
+ final skillDir1 = Directory(p.join(tempDir.path, 'test-skill-1'));
126
+ await skillDir1.create();
127
+ final skillFile1 = File(p.join(skillDir1.path, 'SKILL.md'));
128
+ await skillFile1.writeAsString('Original1');
129
+
130
+ final skillDir2 = Directory(p.join(tempDir.path, 'test-skill-2'));
131
+ await skillDir2.create();
132
+ final skillFile2 = File(p.join(skillDir2.path, 'SKILL.md'));
133
+ await skillFile2.writeAsString('Original2');
134
+
135
+ final bool success = await validateSkillsInternal(
136
+ individualSkillPaths: [skillDir1.path, skillDir2.path],
137
+ fixApply: true,
138
+ fastFail: true,
139
+ quiet: true,
140
+ customRules: [RuleA()],
141
+ );
142
+
143
+ expect(success, isFalse);
144
+
145
+ final String content1 = await skillFile1.readAsString();
146
+ expect(content1, 'Original1 A');
147
+
148
+ final String content2 = await skillFile2.readAsString();
149
+ expect(content2, 'Original2');
150
+ },
151
+ );
152
+
153
+ test('handles exceptions in fix method gracefully', () async {
154
+ final skillDir = Directory(p.join(tempDir.path, 'test-skill'));
155
+ await skillDir.create();
156
+ final skillFile = File(p.join(skillDir.path, 'SKILL.md'));
157
+ await skillFile.writeAsString('Original');
158
+
159
+ final bool success = await validateSkillsInternal(
160
+ individualSkillPaths: [skillDir.path],
161
+ fixApply: true,
162
+ quiet: true,
163
+ customRules: [RuleThrows()],
164
+ );
165
+
166
+ expect(success, isFalse);
167
+
168
+ final String content = await skillFile.readAsString();
169
+ expect(content, 'Original');
170
+ });
171
+ });
172
+ }
@@ -0,0 +1,63 @@
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 'package:dart_skills_lint/src/models/ignore_entry.dart';
6
+ import 'package:dart_skills_lint/src/models/skills_ignores.dart';
7
+ import 'package:test/test.dart';
8
+
9
+ void main() {
10
+ group('IgnoreEntry Serialization', () {
11
+ test('fromJson parses rule_id and file_name', () {
12
+ final Map<String, dynamic> json = {
13
+ IgnoreEntry.ruleIdKey: 'description_too_long',
14
+ IgnoreEntry.fileNameKey: 'SKILL.md',
15
+ };
16
+ final entry = IgnoreEntry.fromJson(json);
17
+ expect(entry.ruleId, equals('description_too_long'));
18
+ expect(entry.fileName, equals('SKILL.md'));
19
+ expect(entry.used, isFalse); // Default
20
+ });
21
+
22
+ test('toJson serializes rule_id and file_name', () {
23
+ final entry = IgnoreEntry(ruleId: 'description_too_long', fileName: 'SKILL.md');
24
+ final Map<String, dynamic> json = entry.toJson();
25
+ expect(json[IgnoreEntry.ruleIdKey], equals('description_too_long'));
26
+ expect(json[IgnoreEntry.fileNameKey], equals('SKILL.md'));
27
+ expect(json.containsKey('used'), isFalse); // Suppressed
28
+ });
29
+ });
30
+
31
+ group('SkillsIgnores Serialization', () {
32
+ test('fromJson parses nested skills map', () {
33
+ final Map<String, dynamic> json = {
34
+ SkillsIgnores.skillsKey: {
35
+ 'skill-a': [
36
+ {IgnoreEntry.ruleIdKey: 'rule1', IgnoreEntry.fileNameKey: 'file1.md'},
37
+ ],
38
+ },
39
+ };
40
+ final ignores = SkillsIgnores.fromJson(json);
41
+ expect(ignores.skills.containsKey('skill-a'), isTrue);
42
+ expect(ignores.skills['skill-a']!.length, equals(1));
43
+ expect(ignores.skills['skill-a']![0].ruleId, equals('rule1'));
44
+ });
45
+
46
+ test('toJson serializes nested skills map', () {
47
+ final entry = IgnoreEntry(ruleId: 'rule1', fileName: 'file1.md');
48
+ final ignores = SkillsIgnores(
49
+ skills: {
50
+ 'skill-a': [entry],
51
+ },
52
+ );
53
+ final Map<String, dynamic> json = ignores.toJson();
54
+
55
+ expect(json.containsKey(SkillsIgnores.skillsKey), isTrue);
56
+ final skillsJson = json[SkillsIgnores.skillsKey] as Map<String, dynamic>;
57
+ expect(skillsJson.containsKey('skill-a'), isTrue);
58
+ final skillAList = skillsJson['skill-a'] as List<dynamic>;
59
+ final firstItem = skillAList[0] as Map<String, dynamic>;
60
+ expect(firstItem[IgnoreEntry.ruleIdKey], equals('rule1'));
61
+ });
62
+ });
63
+ }
@@ -0,0 +1,116 @@
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:dart_skills_lint/src/models/validation_error.dart';
8
+ import 'package:dart_skills_lint/src/rules/disallowed_field_rule.dart';
9
+ import 'package:dart_skills_lint/src/validator.dart';
10
+ import 'package:test/test.dart';
11
+
12
+ import 'test_utils.dart';
13
+
14
+ void main() {
15
+ group('Metadata (YAML) Validation', () {
16
+ late Directory tempDir;
17
+
18
+ setUp(() async {
19
+ tempDir = await Directory.systemTemp.createTemp('metadata_test.');
20
+ });
21
+
22
+ tearDown(() async {
23
+ if (tempDir.existsSync()) {
24
+ await tempDir.delete(recursive: true);
25
+ }
26
+ });
27
+
28
+ test('fails if YAML metadata is invalid', () async {
29
+ await File('${tempDir.path}/SKILL.md').writeAsString('''
30
+ ---
31
+ invalid: yaml: frontmatter
32
+ ---
33
+ Body''');
34
+ final validator = Validator();
35
+ final ValidationResult result = await validator.validate(tempDir);
36
+
37
+ expect(result.isValid, isFalse);
38
+ expect(result.errors, contains(contains('Invalid YAML metadata')));
39
+ });
40
+
41
+ test('fails if required field "name" is missing', () async {
42
+ await File('${tempDir.path}/SKILL.md').writeAsString('''
43
+ ---
44
+ description: A test skill
45
+ ---
46
+ Body''');
47
+ final validator = Validator();
48
+ final ValidationResult result = await validator.validate(tempDir);
49
+
50
+ expect(result.isValid, isFalse);
51
+ expect(result.errors, contains(contains('Missing required field: name')));
52
+ });
53
+
54
+ test('fails if required field "description" is missing', () async {
55
+ await File('${tempDir.path}/SKILL.md').writeAsString('''
56
+ ---
57
+ name: metadata-test
58
+ ---
59
+ Body''');
60
+ final validator = Validator();
61
+ final ValidationResult result = await validator.validate(tempDir);
62
+
63
+ expect(result.isValid, isFalse);
64
+ expect(result.errors, contains(contains('Missing required field: description')));
65
+ });
66
+
67
+ test('passes without warning if disallowed fields are present', () async {
68
+ final skillDir = Directory('${tempDir.path}/metadata-test');
69
+ await skillDir.create();
70
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
71
+ ---
72
+ name: metadata-test
73
+ description: A test skill
74
+ extra-field: not allowed
75
+ ---
76
+ Body''');
77
+
78
+ final validator = Validator();
79
+ final ValidationResult result = await validator.validate(skillDir);
80
+
81
+ expect(result.isValid, isTrue);
82
+ expect(result.warnings, isEmpty);
83
+
84
+ final Iterable<ValidationError> disallowedErrors = result.validationErrors.where(
85
+ (e) => e.ruleId == DisallowedFieldRule.ruleName,
86
+ );
87
+ expect(disallowedErrors, isEmpty);
88
+ });
89
+
90
+ test('passes with all allowed fields and valid YAML', () async {
91
+ await File('${tempDir.path}/SKILL.md').writeAsString('''
92
+ ---
93
+ name: metadata-test
94
+ description: A test skill
95
+ license: MIT
96
+ compatibility: Python 3.10
97
+ metadata:
98
+ version: 1.0.0
99
+ allowed-tools: git
100
+ ---
101
+ Body''');
102
+ final validator = Validator();
103
+ // We need to make sure directory name matches name in metadata
104
+ final skillDir = Directory('${tempDir.path}/metadata-test');
105
+ await skillDir.create();
106
+ await File(
107
+ '${skillDir.path}/SKILL.md',
108
+ ).writeAsString('${buildFrontmatter(name: 'metadata-test')}Body');
109
+
110
+ final ValidationResult result = await validator.validate(skillDir);
111
+
112
+ expect(result.isValid, isTrue, reason: result.errors.isEmpty ? '' : result.errors.first);
113
+ expect(result.errors, isEmpty);
114
+ });
115
+ });
116
+ }
@@ -0,0 +1,70 @@
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:dart_skills_lint/src/models/analysis_severity.dart';
8
+ import 'package:dart_skills_lint/src/rules/absolute_paths_rule.dart';
9
+ import 'package:dart_skills_lint/src/rules/relative_paths_rule.dart';
10
+ import 'package:dart_skills_lint/src/validator.dart';
11
+ import 'package:test/test.dart';
12
+
13
+ import 'test_utils.dart';
14
+
15
+ void main() {
16
+ group('Relative Path Flag Validation', () {
17
+ late Directory tempDir;
18
+
19
+ setUp(() async {
20
+ tempDir = await Directory.systemTemp.createTemp('relative_path_test.');
21
+ });
22
+
23
+ tearDown(() async {
24
+ if (tempDir.existsSync()) {
25
+ await tempDir.delete(recursive: true);
26
+ }
27
+ });
28
+
29
+ test('validates links when relativePathsSeverity = warning', () async {
30
+ final skillDir = Directory('${tempDir.path}/test-skill');
31
+ await skillDir.create();
32
+ await File('${skillDir.path}/SKILL.md').writeAsString(
33
+ '${buildFrontmatter(name: 'test-skill')}Body with [broken link](missing.md) and [absolute link](/absolute/path.md)',
34
+ );
35
+
36
+ final validator = Validator(
37
+ ruleOverrides: {
38
+ RelativePathsRule.ruleName: AnalysisSeverity.warning,
39
+ AbsolutePathsRule.ruleName: AnalysisSeverity.error,
40
+ },
41
+ );
42
+ final ValidationResult result = await validator.validate(skillDir);
43
+
44
+ expect(result.isValid, isFalse);
45
+ expect(
46
+ result.errors,
47
+ contains(contains('Absolute filepath found in link: /absolute/path.md')),
48
+ );
49
+ expect(result.warnings, contains(contains('Linked file does not exist: missing.md')));
50
+ });
51
+
52
+ test('passes when relativePathsSeverity = warning and links are valid', () async {
53
+ final skillDir = Directory('${tempDir.path}/test-skill');
54
+ await skillDir.create();
55
+ await File('${skillDir.path}/SKILL.md').writeAsString(
56
+ '${buildFrontmatter(name: 'test-skill')}Body with [valid relative link](valid.md)',
57
+ );
58
+ await File('${skillDir.path}/valid.md').writeAsString('Valid file content');
59
+
60
+ final validator = Validator(
61
+ ruleOverrides: {RelativePathsRule.ruleName: AnalysisSeverity.warning},
62
+ );
63
+ final ValidationResult result = await validator.validate(skillDir);
64
+
65
+ expect(result.isValid, isTrue);
66
+ expect(result.errors, isEmpty);
67
+ expect(result.warnings, isEmpty);
68
+ });
69
+ });
70
+ }