sdd-full 4.6.1 → 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 +186 -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,74 @@
1
+ import 'dart:io';
2
+ import 'package:path/path.dart';
3
+ import '../fixable_rule.dart';
4
+ import '../models/analysis_severity.dart';
5
+ import '../models/skill_context.dart';
6
+ import '../models/skill_rule.dart';
7
+ import '../models/validation_error.dart';
8
+
9
+ /// Enforces that links in SKILL.md do not use absolute paths.
10
+ class AbsolutePathsRule extends SkillRule implements FixableRule {
11
+ AbsolutePathsRule({this.severity = defaultSeverity});
12
+
13
+ static const String ruleName = 'check-absolute-paths';
14
+ static const AnalysisSeverity defaultSeverity = AnalysisSeverity.warning;
15
+
16
+ @override
17
+ String get name => ruleName;
18
+
19
+ @override
20
+ final AnalysisSeverity severity;
21
+
22
+ static final _markdownLinkRegex = RegExp(r'\[.*?\]\((.*?)\)');
23
+ static const String _skillFileName = SkillContext.skillFileName;
24
+
25
+ @override
26
+ Future<List<ValidationError>> validate(SkillContext context) async {
27
+ final errors = <ValidationError>[];
28
+
29
+ // Extract content after YAML frontmatter
30
+ final skillStartRegex = RegExp(r'^---\s*\n(.*?)\n---\s*\n', dotAll: true);
31
+ final RegExpMatch? match = skillStartRegex.firstMatch(context.rawContent);
32
+ final String markdownContent = match != null
33
+ ? context.rawContent.substring(match.end)
34
+ : context.rawContent;
35
+
36
+ for (final RegExpMatch linkMatch in _markdownLinkRegex.allMatches(markdownContent)) {
37
+ final String path = linkMatch.group(1)!;
38
+ if (isAbsolute(path) || windows.isAbsolute(path)) {
39
+ errors.add(
40
+ ValidationError(
41
+ ruleId: name,
42
+ severity: severity,
43
+ file: _skillFileName,
44
+ message: 'Absolute filepath found in link: $path',
45
+ ),
46
+ );
47
+ }
48
+ }
49
+
50
+ return errors;
51
+ }
52
+
53
+ @override
54
+ Future<String> fix(String filePath, String currentContent, Directory directory) async {
55
+ if (filePath != SkillContext.skillFileName) {
56
+ return currentContent;
57
+ }
58
+
59
+ return currentContent.replaceAllMapped(_markdownLinkRegex, (match) {
60
+ final String path = match.group(1)!;
61
+ if (isAbsolute(path) || windows.isAbsolute(path)) {
62
+ final file = File(path);
63
+ if (file.existsSync()) {
64
+ final String relativePath = relative(path, from: directory.path);
65
+ final String posixRelativePath = relativePath.replaceAll(r'\', '/');
66
+ final String fullMatch = match.group(0)!;
67
+ final int lastParen = fullMatch.lastIndexOf('(');
68
+ return '${fullMatch.substring(0, lastParen + 1)}$posixRelativePath)';
69
+ }
70
+ }
71
+ return match.group(0)!;
72
+ });
73
+ }
74
+ }
@@ -0,0 +1,49 @@
1
+ import 'package:yaml/yaml.dart';
2
+ import '../models/analysis_severity.dart';
3
+ import '../models/skill_context.dart';
4
+ import '../models/skill_rule.dart';
5
+ import '../models/validation_error.dart';
6
+
7
+ /// Enforces that the description field is not too long.
8
+ class DescriptionLengthRule extends SkillRule {
9
+ DescriptionLengthRule({this.severity = defaultSeverity});
10
+
11
+ static const String ruleName = 'description-too-long';
12
+ static const AnalysisSeverity defaultSeverity = AnalysisSeverity.error;
13
+
14
+ @override
15
+ String get name => ruleName;
16
+
17
+ @override
18
+ final AnalysisSeverity severity;
19
+
20
+ static const maxDescriptionLength = 1024;
21
+ static const _skillFileName = 'SKILL.md';
22
+ static const _descriptionFieldUrl = 'https://agentskills.io/specification#description-field';
23
+
24
+ @override
25
+ Future<List<ValidationError>> validate(SkillContext context) async {
26
+ final errors = <ValidationError>[];
27
+
28
+ if (context.parsedYaml == null) {
29
+ return errors;
30
+ }
31
+
32
+ final YamlMap yaml = context.parsedYaml!;
33
+ final String description = yaml['description']?.toString() ?? '';
34
+
35
+ if (description.length > maxDescriptionLength) {
36
+ errors.add(
37
+ ValidationError(
38
+ ruleId: name,
39
+ severity: severity,
40
+ file: _skillFileName,
41
+ message:
42
+ 'Description field is too long. Maximum $maxDescriptionLength characters (see $_descriptionFieldUrl)',
43
+ ),
44
+ );
45
+ }
46
+
47
+ return errors;
48
+ }
49
+ }
@@ -0,0 +1,61 @@
1
+ import 'package:yaml/yaml.dart';
2
+ import '../models/analysis_severity.dart';
3
+ import '../models/skill_context.dart';
4
+ import '../models/skill_rule.dart';
5
+ import '../models/validation_error.dart';
6
+
7
+ /// Enforces that only allowed fields are present in YAML metadata.
8
+ class DisallowedFieldRule extends SkillRule {
9
+ DisallowedFieldRule({this.severity = defaultSeverity});
10
+
11
+ static const String ruleName = 'disallowed-field';
12
+ static const AnalysisSeverity defaultSeverity = AnalysisSeverity.disabled;
13
+
14
+ @override
15
+ String get name => ruleName;
16
+
17
+ @override
18
+ final AnalysisSeverity severity;
19
+
20
+ static const _allowedFields = {
21
+ 'name',
22
+ 'description',
23
+ 'license',
24
+ 'allowed-tools',
25
+ 'metadata',
26
+ 'compatibility',
27
+ 'category',
28
+ 'tags',
29
+ 'version',
30
+ 'eval_task',
31
+ };
32
+
33
+ static const _skillFileName = 'SKILL.md';
34
+ static const _metadataUrl = 'https://agentskills.io/specification#frontmatter';
35
+
36
+ @override
37
+ Future<List<ValidationError>> validate(SkillContext context) async {
38
+ final errors = <ValidationError>[];
39
+
40
+ if (context.parsedYaml == null) {
41
+ return errors;
42
+ }
43
+
44
+ final YamlMap yaml = context.parsedYaml!;
45
+ for (final Object? key in yaml.keys) {
46
+ final bool isDisallowed = key is! String || !_allowedFields.contains(key);
47
+ if (isDisallowed) {
48
+ errors.add(
49
+ ValidationError(
50
+ ruleId: name,
51
+ severity: severity,
52
+ file: _skillFileName,
53
+ message: 'Disallowed field: $key (see $_metadataUrl)',
54
+ ),
55
+ );
56
+ }
57
+ }
58
+
59
+ return errors;
60
+ }
61
+ }
@@ -0,0 +1,167 @@
1
+ import 'dart:io';
2
+ import 'package:meta/meta.dart';
3
+ import 'package:path/path.dart';
4
+ import 'package:yaml/yaml.dart';
5
+ import '../fixable_rule.dart';
6
+ import '../models/analysis_severity.dart';
7
+ import '../models/skill_context.dart';
8
+ import '../models/skill_rule.dart';
9
+ import '../models/validation_error.dart';
10
+
11
+ /// Enforces constraints on the skill name field.
12
+ class NameFormatRule extends SkillRule implements FixableRule {
13
+ NameFormatRule({this.severity = defaultSeverity});
14
+
15
+ static const String ruleName = 'invalid-skill-name';
16
+ static const AnalysisSeverity defaultSeverity = AnalysisSeverity.error;
17
+
18
+ @override
19
+ String get name => ruleName;
20
+
21
+ @override
22
+ final AnalysisSeverity severity;
23
+
24
+ static const maxNameLength = 64;
25
+ static final _validNameRegex = RegExp(r'^[a-z0-9\-]+$');
26
+ static const String _skillFileName = SkillContext.skillFileName;
27
+ static const _nameFieldUrl = 'https://agentskills.io/specification#name-field';
28
+
29
+ @override
30
+ Future<List<ValidationError>> validate(SkillContext context) async {
31
+ final errors = <ValidationError>[];
32
+
33
+ if (context.parsedYaml == null) {
34
+ return errors;
35
+ }
36
+
37
+ final YamlMap yaml = context.parsedYaml!;
38
+ final String skillName = getNameNode(yaml)?.value.toString() ?? '';
39
+
40
+ if (skillName.isEmpty) {
41
+ return errors; // Handled by required fields check
42
+ }
43
+
44
+ if (skillName != skillName.toLowerCase()) {
45
+ errors.add(
46
+ ValidationError(
47
+ ruleId: name,
48
+ severity: severity,
49
+ file: _skillFileName,
50
+ message: 'Skill name must be lowercase: $skillName (see $_nameFieldUrl)',
51
+ ),
52
+ );
53
+ }
54
+
55
+ if (skillName.length > maxNameLength) {
56
+ errors.add(
57
+ ValidationError(
58
+ ruleId: name,
59
+ severity: severity,
60
+ file: _skillFileName,
61
+ message: 'Skill name too long. Maximum $maxNameLength characters (see $_nameFieldUrl)',
62
+ ),
63
+ );
64
+ }
65
+
66
+ if (!_validNameRegex.hasMatch(skillName)) {
67
+ errors.add(
68
+ ValidationError(
69
+ ruleId: name,
70
+ severity: severity,
71
+ file: _skillFileName,
72
+ message:
73
+ 'Skill name contains invalid characters. Only lowercase letters, digits, and hyphens allowed (see $_nameFieldUrl)',
74
+ ),
75
+ );
76
+ }
77
+
78
+ if (skillName.startsWith('-') || skillName.endsWith('-')) {
79
+ errors.add(
80
+ ValidationError(
81
+ ruleId: name,
82
+ severity: severity,
83
+ file: _skillFileName,
84
+ message: 'Skill name cannot have leading or trailing hyphens (see $_nameFieldUrl)',
85
+ ),
86
+ );
87
+ }
88
+
89
+ if (skillName.contains('--')) {
90
+ errors.add(
91
+ ValidationError(
92
+ ruleId: name,
93
+ severity: severity,
94
+ file: _skillFileName,
95
+ message: 'Skill name cannot have consecutive hyphens (see $_nameFieldUrl)',
96
+ ),
97
+ );
98
+ }
99
+
100
+ final String dirName = basename(context.directory.path);
101
+ if (skillName != dirName) {
102
+ errors.add(
103
+ ValidationError(
104
+ ruleId: name,
105
+ severity: severity,
106
+ file: _skillFileName,
107
+ message:
108
+ 'Skill name ($skillName) must exactly match the parent directory name ($dirName) (see $_nameFieldUrl)',
109
+ ),
110
+ );
111
+ }
112
+
113
+ return errors;
114
+ }
115
+
116
+ @override
117
+ Future<String> fix(String filePath, String currentContent, Directory directory) async {
118
+ if (filePath != SkillContext.skillFileName) {
119
+ return currentContent;
120
+ }
121
+
122
+ final RegExpMatch? match = SkillContext.skillStartRegex.firstMatch(currentContent);
123
+ if (match == null) {
124
+ return currentContent;
125
+ }
126
+ final String yamlStr = match.group(1)!;
127
+
128
+ final Object? yamlObj;
129
+ try {
130
+ yamlObj = loadYaml(yamlStr);
131
+ } catch (e) {
132
+ return currentContent;
133
+ }
134
+
135
+ if (yamlObj is! YamlMap) {
136
+ return currentContent;
137
+ }
138
+
139
+ final YamlMap yaml = yamlObj;
140
+ final YamlNode? nameNode = getNameNode(yaml);
141
+ if (nameNode == null) {
142
+ return currentContent;
143
+ }
144
+
145
+ final String dirName = basename(directory.path);
146
+
147
+ final currentName = nameNode.value.toString();
148
+ if (currentName == dirName) {
149
+ return currentContent;
150
+ }
151
+
152
+ final int yamlOffset = currentContent.indexOf(yamlStr, match.start);
153
+
154
+ // ignore: specify_nonobvious_local_variable_types
155
+ final span = nameNode.span;
156
+ final String before = currentContent.substring(0, yamlOffset + span.start.offset);
157
+ final String after = currentContent.substring(yamlOffset + span.end.offset);
158
+
159
+ return '$before$dirName$after';
160
+ }
161
+
162
+ /// Returns the YAML node for the skill name.
163
+ @visibleForTesting
164
+ static YamlNode? getNameNode(YamlMap yaml) {
165
+ return yaml.nodes['name'];
166
+ }
167
+ }
@@ -0,0 +1,72 @@
1
+ import 'dart:io';
2
+ import 'package:path/path.dart';
3
+ import '../models/analysis_severity.dart';
4
+ import '../models/skill_context.dart';
5
+ import '../models/skill_rule.dart';
6
+ import '../models/validation_error.dart';
7
+
8
+ /// Enforces that relative links in SKILL.md point to existing files.
9
+ class RelativePathsRule extends SkillRule {
10
+ RelativePathsRule({this.severity = defaultSeverity});
11
+
12
+ static const String ruleName = 'check-relative-paths';
13
+ static const AnalysisSeverity defaultSeverity = AnalysisSeverity.disabled;
14
+
15
+ @override
16
+ String get name => ruleName;
17
+
18
+ @override
19
+ final AnalysisSeverity severity;
20
+
21
+ static final _markdownLinkRegex = RegExp(r'\[.*?\]\((.*?)\)');
22
+ static const _skillFileName = 'SKILL.md';
23
+
24
+ @override
25
+ Future<List<ValidationError>> validate(SkillContext context) async {
26
+ final errors = <ValidationError>[];
27
+
28
+ // Extract content after YAML frontmatter
29
+ final skillStartRegex = RegExp(r'^---\s*\n(.*?)\n---\s*\n', dotAll: true);
30
+ final RegExpMatch? match = skillStartRegex.firstMatch(context.rawContent);
31
+ final String markdownContent = match != null
32
+ ? context.rawContent.substring(match.end)
33
+ : context.rawContent;
34
+
35
+ for (final RegExpMatch linkMatch in _markdownLinkRegex.allMatches(markdownContent)) {
36
+ final String fullPath = linkMatch.group(1)!;
37
+ // Markdown links can have a title after the URL, separated by spaces.
38
+ // e.g. [text](url "title")
39
+ final String path = fullPath.trim().split(RegExp(r'\s+')).first;
40
+
41
+ // Skip absolute paths (handled by AbsolutePathsRule)
42
+ if (isAbsolute(path) || windows.isAbsolute(path)) {
43
+ continue;
44
+ }
45
+
46
+ var effectivePath = path;
47
+ try {
48
+ final Uri uri = Uri.parse(path);
49
+ if (uri.hasScheme || path.startsWith('#')) {
50
+ continue; // Ignore web URLs, email links, anchors, etc.
51
+ }
52
+ effectivePath = uri.path;
53
+ } catch (_) {
54
+ // If Uri parsing fails, treat it as a potential filepath.
55
+ }
56
+
57
+ final linkedFile = File(join(context.directory.path, effectivePath));
58
+ if (!linkedFile.existsSync()) {
59
+ errors.add(
60
+ ValidationError(
61
+ ruleId: name,
62
+ severity: severity,
63
+ file: _skillFileName,
64
+ message: 'Linked file does not exist: $path',
65
+ ),
66
+ );
67
+ }
68
+ }
69
+
70
+ return errors;
71
+ }
72
+ }
@@ -0,0 +1,93 @@
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
+ import 'package:meta/meta.dart';
7
+
8
+ import '../fixable_rule.dart';
9
+ import '../models/analysis_severity.dart';
10
+ import '../models/skill_context.dart';
11
+ import '../models/skill_rule.dart';
12
+ import '../models/validation_error.dart';
13
+
14
+ /// Enforces that lines in SKILL.md do not have trailing whitespace,
15
+ /// except for exactly two spaces which indicate a hard line break.
16
+ class TrailingWhitespaceRule extends SkillRule implements FixableRule {
17
+ TrailingWhitespaceRule({this.severity = defaultSeverity});
18
+
19
+ static const String ruleName = 'check-trailing-whitespace';
20
+ static const AnalysisSeverity defaultSeverity = AnalysisSeverity.disabled;
21
+ static final RegExp _whitespaceRegExp = RegExp(r'([ \t]+)$');
22
+
23
+ @override
24
+ String get name => ruleName;
25
+
26
+ @override
27
+ final AnalysisSeverity severity;
28
+
29
+ @override
30
+ Future<List<ValidationError>> validate(SkillContext context) async {
31
+ final errors = <ValidationError>[];
32
+ final List<String> lines = context.rawContent.split('\n');
33
+
34
+ for (var i = 0; i < lines.length; i++) {
35
+ final String line = lines[i];
36
+
37
+ // Remove carriage return if present (Windows line endings)
38
+ final String trimmedLine = line.endsWith('\r') ? line.substring(0, line.length - 1) : line;
39
+
40
+ final RegExpMatch? match = _whitespaceRegExp.firstMatch(trimmedLine);
41
+ if (match != null) {
42
+ final String whitespace = match.group(1)!;
43
+ String? message;
44
+
45
+ if (whitespace.contains('\t')) {
46
+ message = 'Line ${i + 1} has trailing whitespace containing tabs.';
47
+ } else {
48
+ final int spacesCount = whitespace.length;
49
+ if (spacesCount == 1 || spacesCount >= 3) {
50
+ message =
51
+ 'Line ${i + 1} has $spacesCount trailing space(s). Only exactly 2 spaces are allowed for line breaks.';
52
+ }
53
+ }
54
+
55
+ if (message != null) {
56
+ errors.add(
57
+ ValidationError(ruleId: name, severity: severity, file: 'SKILL.md', message: message),
58
+ );
59
+ }
60
+ }
61
+ }
62
+
63
+ return errors;
64
+ }
65
+
66
+ @override
67
+ Future<String> fix(String filePath, String currentContent, Directory directory) async {
68
+ if (filePath != 'SKILL.md') {
69
+ return currentContent;
70
+ }
71
+
72
+ return currentContent.split('\n').map(fixLine).join('\n');
73
+ }
74
+
75
+ @visibleForTesting
76
+ String fixLine(String line) {
77
+ final bool hasCR = line.endsWith('\r');
78
+ final String lineWithoutCR = hasCR ? line.substring(0, line.length - 1) : line;
79
+
80
+ final RegExpMatch? match = _whitespaceRegExp.firstMatch(lineWithoutCR);
81
+ if (match == null) {
82
+ return line;
83
+ }
84
+
85
+ final String whitespace = match.group(1)!;
86
+ if (whitespace == ' ') {
87
+ return line; // Keep the 2 space hard line break.
88
+ }
89
+
90
+ final String fixedLine = lineWithoutCR.replaceAll(_whitespaceRegExp, '');
91
+ return hasCR ? '$fixedLine\r' : fixedLine;
92
+ }
93
+ }
@@ -0,0 +1,74 @@
1
+ import 'package:yaml/yaml.dart';
2
+ import '../models/analysis_severity.dart';
3
+ import '../models/skill_context.dart';
4
+ import '../models/skill_rule.dart';
5
+ import '../models/validation_error.dart';
6
+
7
+ /// Enforces that SKILL.md has valid YAML frontmatter and required fields.
8
+ class ValidYamlMetadataRule extends SkillRule {
9
+ ValidYamlMetadataRule({this.severity = defaultSeverity});
10
+
11
+ static const String ruleName = 'valid-yaml-metadata';
12
+ static const AnalysisSeverity defaultSeverity = AnalysisSeverity.error;
13
+
14
+ @override
15
+ String get name => ruleName;
16
+
17
+ @override
18
+ final AnalysisSeverity severity;
19
+
20
+ static const _requiredFields = {'name', 'description'};
21
+ static const _skillFileName = 'SKILL.md';
22
+ static const _metadataUrl = 'https://agentskills.io/specification#frontmatter';
23
+ static const maxCompatibilityLength = 500;
24
+ static const _compatibilityFieldUrl = 'https://agentskills.io/specification#compatibility-field';
25
+
26
+ @override
27
+ Future<List<ValidationError>> validate(SkillContext context) async {
28
+ final errors = <ValidationError>[];
29
+
30
+ if (context.parsedYaml == null) {
31
+ errors.add(
32
+ ValidationError(
33
+ ruleId: name,
34
+ severity: severity,
35
+ file: _skillFileName,
36
+ message:
37
+ 'Invalid YAML metadata: ${context.yamlParsingError ?? 'Missing or invalid'} (see $_metadataUrl)',
38
+ ),
39
+ );
40
+ return errors;
41
+ }
42
+
43
+ final YamlMap yaml = context.parsedYaml!;
44
+ for (final String field in _requiredFields) {
45
+ if (!yaml.containsKey(field)) {
46
+ errors.add(
47
+ ValidationError(
48
+ ruleId: name,
49
+ severity: severity,
50
+ file: _skillFileName,
51
+ message: 'Missing required field: $field (see $_metadataUrl)',
52
+ ),
53
+ );
54
+ }
55
+ }
56
+
57
+ if (yaml.containsKey('compatibility')) {
58
+ final String compatibility = yaml['compatibility']?.toString() ?? '';
59
+ if (compatibility.length > maxCompatibilityLength) {
60
+ errors.add(
61
+ ValidationError(
62
+ ruleId: name,
63
+ severity: severity,
64
+ file: _skillFileName,
65
+ message:
66
+ 'Compatibility field is too long. Maximum $maxCompatibilityLength characters (see $_compatibilityFieldUrl)',
67
+ ),
68
+ );
69
+ }
70
+ }
71
+
72
+ return errors;
73
+ }
74
+ }
@@ -0,0 +1,36 @@
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 'models/skills_ignores.dart';
9
+
10
+ /// Service class for reading and writing the `SkillsIgnores` model to/from disk.
11
+ class SkillsIgnoresStorage {
12
+ /// Loads `SkillsIgnores` from the specified path.
13
+ ///
14
+ /// Returns an empty `SkillsIgnores` if the file does not exist or fails to parse.
15
+ Future<SkillsIgnores> load(String path) async {
16
+ final file = File(path);
17
+ if (!file.existsSync()) {
18
+ return SkillsIgnores(skills: {});
19
+ }
20
+
21
+ try {
22
+ final String content = await file.readAsString();
23
+ final json = jsonDecode(content) as Map<String, dynamic>;
24
+ return SkillsIgnores.fromJson(json);
25
+ } catch (_) {
26
+ return SkillsIgnores(skills: {});
27
+ }
28
+ }
29
+
30
+ /// Saves `SkillsIgnores` to the specified path.
31
+ Future<void> save(String path, SkillsIgnores ignores) async {
32
+ final file = File(path);
33
+ final String jsonString = const JsonEncoder.withIndent(' ').convert(ignores.toJson());
34
+ await file.writeAsString(jsonString);
35
+ }
36
+ }