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,292 @@
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:path/path.dart' as p;
9
+ import 'package:test/test.dart';
10
+ import 'package:test_process/test_process.dart';
11
+
12
+ void main() {
13
+ group('Configuration File Integration', () {
14
+ late Directory tempDir;
15
+
16
+ setUp(() async {
17
+ tempDir = await Directory.systemTemp.createTemp('config_test.');
18
+ });
19
+
20
+ tearDown(() async {
21
+ if (tempDir.existsSync()) {
22
+ await tempDir.delete(recursive: true);
23
+ }
24
+ });
25
+
26
+ test('obeys disabled relative paths in config', () async {
27
+ final Directory skillDir = await Directory('${tempDir.path}/test-skill').create();
28
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
29
+ ---
30
+ name: test-skill
31
+ description: A test skill
32
+ ---
33
+ [broken](missing.md)''');
34
+
35
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
36
+ dart_skills_lint:
37
+ rules:
38
+ check-relative-paths: disabled
39
+ ''');
40
+
41
+ final TestProcess process = await TestProcess.start('dart', [
42
+ p.normalize(p.absolute('bin/cli.dart')),
43
+ '-s',
44
+ 'test-skill',
45
+ ], workingDirectory: tempDir.path);
46
+
47
+ final List<String> stdout = await process.stdout.rest.toList();
48
+ expect(stdout.join('\n'), contains('Skill is valid.'));
49
+ await process.shouldExit(0);
50
+ });
51
+
52
+ test('obeys warning absolute paths in config', () async {
53
+ final Directory skillDir = await Directory('${tempDir.path}/test-skill').create();
54
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
55
+ ---
56
+ name: test-skill
57
+ description: A test skill
58
+ ---
59
+ [absolute](/absolute/path.md)''');
60
+
61
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
62
+ dart_skills_lint:
63
+ rules:
64
+ check-absolute-paths: warning
65
+ ''');
66
+
67
+ final TestProcess process = await TestProcess.start('dart', [
68
+ p.normalize(p.absolute('bin/cli.dart')),
69
+ '-s',
70
+ 'test-skill',
71
+ ], workingDirectory: tempDir.path);
72
+
73
+ final List<String> stdout = await process.stdout.rest.toList();
74
+ expect(stdout.join('\n'), contains('Warnings:'));
75
+ await process.shouldExit(0);
76
+ });
77
+
78
+ test('CLI flags override config', () async {
79
+ final Directory skillDir = await Directory('${tempDir.path}/test-skill').create();
80
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
81
+ ---
82
+ name: test-skill
83
+ description: A test skill
84
+ ---
85
+ [broken](missing.md)''');
86
+
87
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
88
+ dart_skills_lint:
89
+ rules:
90
+ check-relative-paths: disabled
91
+ ''');
92
+
93
+ final TestProcess process = await TestProcess.start('dart', [
94
+ p.normalize(p.absolute('bin/cli.dart')),
95
+ '-s',
96
+ 'test-skill',
97
+ '--check-relative-paths',
98
+ ], workingDirectory: tempDir.path);
99
+
100
+ final List<String> stderr = await process.stderr.rest.toList();
101
+ expect(stderr.join('\n'), contains('Skill is invalid:'));
102
+ await process.shouldExit(1);
103
+ });
104
+
105
+ test('writes empty ignore-file if missing and specified in config', () async {
106
+ await Directory('${tempDir.path}/test-skill').create();
107
+ await File('${tempDir.path}/test-skill/SKILL.md').writeAsString('''
108
+ ---
109
+ name: test-skill
110
+ description: A test skill
111
+ ---
112
+ Body''');
113
+
114
+ const ignorePath = 'custom_ignore.json';
115
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
116
+ dart_skills_lint:
117
+ directories:
118
+ - path: "test-skill"
119
+ ignore_file: "$ignorePath"
120
+ ''');
121
+
122
+ final TestProcess process = await TestProcess.start('dart', [
123
+ p.normalize(p.absolute('bin/cli.dart')),
124
+ '-s',
125
+ 'test-skill',
126
+ ], workingDirectory: tempDir.path);
127
+
128
+ final List<String> stdout = await process.stdout.rest.toList();
129
+ expect(stdout.join('\n'), contains('File not found generating-baseline'));
130
+ await process.shouldExit(0);
131
+
132
+ final writtenFile = File('${tempDir.path}/$ignorePath');
133
+ expect(writtenFile.existsSync(), isTrue);
134
+ final String fileContent = await writtenFile.readAsString();
135
+ expect(fileContent, contains('"skills":'));
136
+ });
137
+
138
+ test('ignores config when --ignore-config is passed', () async {
139
+ final Directory skillDir = await Directory('${tempDir.path}/TEST-SKILL').create();
140
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
141
+ ---
142
+ name: TEST-SKILL
143
+ description: A test skill
144
+ license: MIT
145
+ ---
146
+ Body''');
147
+
148
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
149
+ dart_skills_lint:
150
+ rules:
151
+ invalid-skill-name: disabled
152
+ ''');
153
+
154
+ // 1. Run without --ignore-config. Should pass because config disables the check.
155
+ final TestProcess passProcess = await TestProcess.start('dart', [
156
+ p.normalize(p.absolute('bin/cli.dart')),
157
+ '-s',
158
+ 'TEST-SKILL',
159
+ ], workingDirectory: tempDir.path);
160
+ await passProcess.shouldExit(0);
161
+
162
+ // 2. Run with --ignore-config. Should fail because config is ignored and default is used.
163
+ final TestProcess failProcess = await TestProcess.start('dart', [
164
+ p.normalize(p.absolute('bin/cli.dart')),
165
+ '-s',
166
+ 'TEST-SKILL',
167
+ '--ignore-config',
168
+ ], workingDirectory: tempDir.path);
169
+ await failProcess.shouldExit(1);
170
+ });
171
+
172
+ test('ignores config when generating baseline with --ignore-config', () async {
173
+ final Directory skillDir = await Directory('${tempDir.path}/TEST-SKILL').create();
174
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
175
+ ---
176
+ name: TEST-SKILL
177
+ description: A test skill
178
+ license: MIT
179
+ ---
180
+ Body''');
181
+
182
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
183
+ dart_skills_lint:
184
+ rules:
185
+ invalid-skill-name: disabled
186
+ ''');
187
+
188
+ // 1. Generate baseline with --ignore-config. It should ignore config (so the rule is enabled) and find violations to generate baseline for!
189
+ final TestProcess genProcess = await TestProcess.start('dart', [
190
+ p.normalize(p.absolute('bin/cli.dart')),
191
+ '-s',
192
+ 'TEST-SKILL',
193
+ '--generate-baseline',
194
+ '--ignore-config',
195
+ ], workingDirectory: tempDir.path);
196
+ await genProcess.shouldExit(0); // Exits 0 if --generate-baseline passed
197
+
198
+ final ignoreFile = File('${skillDir.parent.path}/$defaultIgnoreFileName');
199
+ expect(ignoreFile.existsSync(), isTrue);
200
+
201
+ final String content = await ignoreFile.readAsString();
202
+ expect(content, contains('invalid-skill-name')); // It should generate baseline for it!
203
+ });
204
+
205
+ test('fails on invalid top-level key in config by default', () async {
206
+ await Directory('${tempDir.path}/test-skill').create();
207
+ await File('${tempDir.path}/test-skill/SKILL.md').writeAsString('''
208
+ ---
209
+ name: test-skill
210
+ description: A test skill
211
+ ---
212
+ Body''');
213
+
214
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
215
+ dart_skills_lint:
216
+ invalid-key: value
217
+ ''');
218
+
219
+ final TestProcess process = await TestProcess.start('dart', [
220
+ p.normalize(p.absolute('bin/cli.dart')),
221
+ '-s',
222
+ 'test-skill',
223
+ ], workingDirectory: tempDir.path);
224
+
225
+ final List<String> stderr = await process.stderr.rest.toList();
226
+ expect(
227
+ stderr.join('\n'),
228
+ contains('Configuration error: Unrecognized top-level key "invalid-key"'),
229
+ );
230
+ await process.shouldExit(1);
231
+ });
232
+
233
+ test('fails on invalid directory key in config by default', () async {
234
+ await Directory('${tempDir.path}/test-skill').create();
235
+ await File('${tempDir.path}/test-skill/SKILL.md').writeAsString('''
236
+ ---
237
+ name: test-skill
238
+ description: A test skill
239
+ ---
240
+ Body''');
241
+
242
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
243
+ dart_skills_lint:
244
+ directories:
245
+ - path: "test-skill"
246
+ invalid-dir-key: value
247
+ ''');
248
+
249
+ final TestProcess process = await TestProcess.start('dart', [
250
+ p.normalize(p.absolute('bin/cli.dart')),
251
+ '-s',
252
+ 'test-skill',
253
+ ], workingDirectory: tempDir.path);
254
+
255
+ final List<String> stderr = await process.stderr.rest.toList();
256
+ expect(
257
+ stderr.join('\n'),
258
+ contains('Configuration error: Unrecognized key "invalid-dir-key"'),
259
+ );
260
+ await process.shouldExit(1);
261
+ });
262
+
263
+ test('succeeds with warning on invalid key when --allow-misconfigured-keys passed', () async {
264
+ await Directory('${tempDir.path}/test-skill').create();
265
+ await File('${tempDir.path}/test-skill/SKILL.md').writeAsString('''
266
+ ---
267
+ name: test-skill
268
+ description: A test skill
269
+ ---
270
+ Body''');
271
+
272
+ await File('${tempDir.path}/dart_skills_lint.yaml').writeAsString('''
273
+ dart_skills_lint:
274
+ invalid-key: value
275
+ ''');
276
+
277
+ final TestProcess process = await TestProcess.start('dart', [
278
+ p.normalize(p.absolute('bin/cli.dart')),
279
+ '-s',
280
+ 'test-skill',
281
+ '--allow-misconfigured-keys',
282
+ ], workingDirectory: tempDir.path);
283
+
284
+ final List<String> stdout = await process.stdout.rest.toList();
285
+ expect(
286
+ stdout.join('\n'),
287
+ contains('Configuration warning: Unrecognized top-level key "invalid-key"'),
288
+ );
289
+ await process.shouldExit(0);
290
+ });
291
+ });
292
+ }
@@ -0,0 +1,122 @@
1
+ import 'dart:async';
2
+ import 'dart:io';
3
+ import 'package:dart_skills_lint/dart_skills_lint.dart';
4
+ import 'package:logging/logging.dart';
5
+ import 'package:test/test.dart';
6
+
7
+ class CustomRule extends SkillRule {
8
+ @override
9
+ final String name = 'custom-rule';
10
+
11
+ @override
12
+ final AnalysisSeverity severity = AnalysisSeverity.error;
13
+
14
+ @override
15
+ Future<List<ValidationError>> validate(SkillContext context) async {
16
+ final errors = <ValidationError>[];
17
+ if (context.rawContent.contains('TRIGGER_ERROR')) {
18
+ errors.add(
19
+ ValidationError(
20
+ ruleId: name,
21
+ severity: severity,
22
+ file: 'SKILL.md',
23
+ message: 'Custom rule triggered',
24
+ ),
25
+ );
26
+ }
27
+ return errors;
28
+ }
29
+ }
30
+
31
+ class MismatchRule extends SkillRule {
32
+ @override
33
+ final String name = 'mismatch-rule';
34
+
35
+ @override
36
+ final AnalysisSeverity severity = AnalysisSeverity.warning;
37
+
38
+ @override
39
+ Future<List<ValidationError>> validate(SkillContext context) async {
40
+ return [
41
+ ValidationError(
42
+ ruleId: name,
43
+ severity: AnalysisSeverity.error, // Mismatch!
44
+ file: 'SKILL.md',
45
+ message: 'Triggered',
46
+ ),
47
+ ];
48
+ }
49
+ }
50
+
51
+ void main() {
52
+ group('Custom Rules', () {
53
+ late Directory tempDir;
54
+
55
+ setUp(() async {
56
+ tempDir = await Directory.systemTemp.createTemp('custom_rule_test.');
57
+ });
58
+
59
+ tearDown(() async {
60
+ if (tempDir.existsSync()) {
61
+ await tempDir.delete(recursive: true);
62
+ }
63
+ });
64
+
65
+ test('Validator runs custom rule', () async {
66
+ final Directory skillDir = await Directory('${tempDir.path}/skill-name').create();
67
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
68
+ ---
69
+ name: skill-name
70
+ description: A test skill
71
+ ---
72
+ TRIGGER_ERROR''');
73
+
74
+ final validator = Validator(customRules: [CustomRule()]);
75
+ final ValidationResult result = await validator.validate(skillDir);
76
+
77
+ expect(result.isValid, isFalse);
78
+ expect(result.errors, contains(contains('Custom rule triggered')));
79
+ });
80
+
81
+ test('Validator logs warning on severity mismatch', () async {
82
+ final Directory skillDir = await Directory('${tempDir.path}/skill-name-3').create();
83
+ await File('${skillDir.path}/SKILL.md').writeAsString('''
84
+ ---
85
+ name: skill-name-3
86
+ description: A test skill
87
+ ---
88
+ Body''');
89
+
90
+ final validator = Validator(customRules: [MismatchRule()]);
91
+
92
+ final logs = <String>[];
93
+ final StreamSubscription<LogRecord> subscription = Logger('dart_skills_lint').onRecord.listen(
94
+ (record) {
95
+ logs.add(record.message);
96
+ },
97
+ );
98
+
99
+ try {
100
+ await validator.validate(skillDir);
101
+ } finally {
102
+ await subscription.cancel();
103
+ }
104
+
105
+ expect(
106
+ logs,
107
+ contains(
108
+ contains(
109
+ 'Rule "mismatch-rule" used severity AnalysisSeverity.error instead of defined AnalysisSeverity.warning',
110
+ ),
111
+ ),
112
+ );
113
+ });
114
+
115
+ test('Validator throws ArgumentError on duplicate rule names', () {
116
+ final rule1 = CustomRule();
117
+ final rule2 = CustomRule(); // Same name 'custom-rule'
118
+
119
+ expect(() => Validator(customRules: [rule1, rule2]), throwsArgumentError);
120
+ });
121
+ });
122
+ }
@@ -0,0 +1,163 @@
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:dart_skills_lint/src/models/analysis_severity.dart';
9
+ import 'package:dart_skills_lint/src/validator.dart';
10
+ import 'package:path/path.dart' as p;
11
+ import 'package:test/test.dart';
12
+
13
+ class MockInaccessibleFile implements File {
14
+ MockInaccessibleFile(this._path);
15
+ final String _path;
16
+
17
+ @override
18
+ String get path => _path;
19
+
20
+ @override
21
+ bool existsSync() => true;
22
+
23
+ @override
24
+ Future<String> readAsString({Encoding encoding = utf8}) async {
25
+ throw FileSystemException('File is inaccessible', _path);
26
+ }
27
+
28
+ @override
29
+ dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
30
+ }
31
+
32
+ base class TestIOOverrides extends IOOverrides {
33
+ TestIOOverrides(this.targetPath);
34
+ final String targetPath;
35
+
36
+ @override
37
+ File createFile(String path) {
38
+ if (path == targetPath) {
39
+ return MockInaccessibleFile(path);
40
+ }
41
+ return super.createFile(path);
42
+ }
43
+ }
44
+
45
+ void main() {
46
+ group('Directory Structure Validation', () {
47
+ late Directory tempDir;
48
+
49
+ setUp(() async {
50
+ tempDir = await Directory.systemTemp.createTemp('skill_test.');
51
+ });
52
+
53
+ tearDown(() async {
54
+ if (tempDir.existsSync()) {
55
+ await tempDir.delete(recursive: true);
56
+ }
57
+ });
58
+
59
+ test('fails if directory does not exist', () async {
60
+ final nonExistentDir = Directory('path/to/nothing');
61
+ final validator = Validator();
62
+ final ValidationResult result = await validator.validate(nonExistentDir);
63
+
64
+ expect(result.isValid, isFalse);
65
+ expect(result.errors, contains(contains('Directory does not exist')));
66
+ });
67
+
68
+ test('fails if path is a file', () async {
69
+ final file = File('${tempDir.path}/some_file');
70
+ await file.create();
71
+ final validator = Validator();
72
+ final ValidationResult result = await validator.validate(Directory(file.path));
73
+
74
+ expect(result.isValid, isFalse);
75
+ expect(result.errors, contains(contains('is not a directory')));
76
+ });
77
+
78
+ test('fails if SKILL.md is missing', () async {
79
+ final validator = Validator();
80
+ final ValidationResult result = await validator.validate(tempDir);
81
+
82
+ expect(result.isValid, isFalse);
83
+ expect(result.errors, contains(contains('SKILL.md is missing')));
84
+ });
85
+
86
+ test('fails if SKILL.md cannot be read', () async {
87
+ final skillDir = Directory(p.join(tempDir.path, 'test-skill-inaccessible'));
88
+ await skillDir.create();
89
+ final String filePath = p.join(skillDir.path, 'SKILL.md');
90
+
91
+ final overrides = TestIOOverrides(filePath);
92
+ await IOOverrides.runWithIOOverrides(() async {
93
+ try {
94
+ final validator = Validator();
95
+ final ValidationResult validationResult = await validator.validate(skillDir);
96
+
97
+ // ignore: avoid_print
98
+ print(
99
+ 'DEBUG errors: ${validationResult.validationErrors.map((e) => "${e.ruleId}: ${e.message}").toList()}',
100
+ );
101
+ expect(validationResult.isValid, isFalse);
102
+ expect(
103
+ validationResult.validationErrors.any(
104
+ (e) => e.ruleId == Validator.skillFileInaccessible,
105
+ ),
106
+ isTrue,
107
+ );
108
+ } catch (e, s) {
109
+ fail('Unexpected exception during validation: $e\n$s');
110
+ }
111
+ }, overrides);
112
+ });
113
+
114
+ test('obeys skill-file-inaccessible severity override', () async {
115
+ final skillDir = Directory(p.join(tempDir.path, 'test-skill-override'));
116
+ await skillDir.create();
117
+ final String filePath = p.join(skillDir.path, 'SKILL.md');
118
+
119
+ final overrides = TestIOOverrides(filePath);
120
+ await IOOverrides.runWithIOOverrides(() async {
121
+ try {
122
+ final validator = Validator(
123
+ ruleOverrides: {Validator.skillFileInaccessible: AnalysisSeverity.warning},
124
+ );
125
+ final ValidationResult validationResult = await validator.validate(skillDir);
126
+
127
+ // ignore: avoid_print
128
+ print(
129
+ 'DEBUG errors (override): ${validationResult.validationErrors.map((e) => "${e.ruleId}: ${e.message}").toList()}',
130
+ );
131
+ expect(validationResult.isValid, isTrue);
132
+ expect(
133
+ validationResult.validationErrors.any(
134
+ (e) =>
135
+ e.ruleId == Validator.skillFileInaccessible &&
136
+ e.severity == AnalysisSeverity.warning,
137
+ ),
138
+ isTrue,
139
+ );
140
+ } catch (e, s) {
141
+ fail('Unexpected exception during validation: $e\n$s');
142
+ }
143
+ }, overrides);
144
+ });
145
+
146
+ test('passes if directory exists and contains SKILL.md', () async {
147
+ final skillDir = Directory(p.join(tempDir.path, 'test-skill'));
148
+ await skillDir.create();
149
+ await File(p.join(skillDir.path, 'SKILL.md')).writeAsString('''
150
+ ---
151
+ name: test-skill
152
+ description: A test skill
153
+ ---
154
+ Body''');
155
+
156
+ final validator = Validator();
157
+ final ValidationResult result = await validator.validate(skillDir);
158
+
159
+ expect(result.isValid, isTrue, reason: result.errors.isEmpty ? '' : result.errors.first);
160
+ expect(result.errors, isEmpty);
161
+ });
162
+ });
163
+ }