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.
- package/bin.js +1 -1
- package/package.json +1 -1
- package/skills/.agents/skills/flutter-add-integration-test/SKILL.md +165 -0
- package/skills/.agents/skills/flutter-add-widget-preview/SKILL.md +147 -0
- package/skills/.agents/skills/flutter-add-widget-test/SKILL.md +156 -0
- package/skills/.agents/skills/flutter-apply-architecture-best-practices/SKILL.md +164 -0
- package/skills/.agents/skills/flutter-build-responsive-layout/SKILL.md +141 -0
- package/skills/.agents/skills/flutter-fix-layout-issues/SKILL.md +132 -0
- package/skills/.agents/skills/flutter-implement-json-serialization/SKILL.md +155 -0
- package/skills/.agents/skills/flutter-setup-declarative-routing/SKILL.md +257 -0
- package/skills/.agents/skills/flutter-setup-localization/SKILL.md +212 -0
- package/skills/.agents/skills/flutter-use-http-package/SKILL.md +177 -0
- package/skills/VERSION.md +186 -62
- package/skills/design-planning/ai-coding-rules/SKILL.md +5 -13
- package/skills/design-planning/design-to-code/SKILL.md +5 -14
- package/skills/design-planning/enterprise-spec/SKILL.md +5 -13
- package/skills/design-planning/flutter-av/SKILL.md +5 -16
- package/skills/design-planning/flutter-map/SKILL.md +5 -14
- package/skills/design-planning/function-sdd/SKILL.md +5 -13
- package/skills/design-planning/global-overlay-stack-standard/SKILL.md +73 -0
- package/skills/design-planning/ui-motion-interaction-standard/SKILL.md +69 -0
- package/skills/design-planning/ui-sdd-specialized/SKILL.md +5 -14
- package/skills/development-execution/flutter-errors/SKILL.md +5 -15
- package/skills/flutter-skills/.github/dependabot.yaml +15 -0
- package/skills/flutter-skills/.github/workflows/dart_skills_lint_workflow.yaml +68 -0
- package/skills/flutter-skills/.github/workflows/skills_tool.yaml +51 -0
- package/skills/flutter-skills/CODE_OF_CONDUCT.md +3 -0
- package/skills/flutter-skills/CONTRIBUTING.md +36 -0
- package/skills/flutter-skills/LICENSE +26 -0
- package/skills/flutter-skills/README.md +50 -0
- package/skills/flutter-skills/pubspec.yaml +9 -0
- package/skills/flutter-skills/resources/flutter_skills.yaml +434 -0
- package/skills/flutter-skills/skills/flutter-add-integration-test/SKILL.md +163 -0
- package/skills/flutter-skills/skills/flutter-add-widget-preview/SKILL.md +145 -0
- package/skills/flutter-skills/skills/flutter-add-widget-test/SKILL.md +154 -0
- package/skills/flutter-skills/skills/flutter-apply-architecture-best-practices/SKILL.md +162 -0
- package/skills/flutter-skills/skills/flutter-build-responsive-layout/SKILL.md +139 -0
- package/skills/flutter-skills/skills/flutter-fix-layout-issues/SKILL.md +130 -0
- package/skills/flutter-skills/skills/flutter-implement-json-serialization/SKILL.md +153 -0
- package/skills/flutter-skills/skills/flutter-setup-declarative-routing/SKILL.md +255 -0
- package/skills/flutter-skills/skills/flutter-setup-localization/SKILL.md +210 -0
- package/skills/flutter-skills/skills/flutter-use-http-package/SKILL.md +175 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/add-dart-lint-validation-rule/SKILL.md +196 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-best-practices/SKILL.md +65 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-checks-migration/SKILL.md +158 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-cli-app-best-practices/SKILL.md +168 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-doc-validation/SKILL.md +87 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-long-lines/SKILL.md +101 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-matcher-best-practices/SKILL.md +136 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-modern-features/SKILL.md +266 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-package-maintenance/SKILL.md +92 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/SKILL.md +92 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/lib/src/calculator.dart +7 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.yaml +8 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/test/calculator_test.dart +11 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/interpret_coverage.dart +95 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/pubspec.yaml +6 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/test/interpret_coverage_test.dart +93 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/dart-test-fundamentals/SKILL.md +173 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/definition-of-done/SKILL.md +27 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/flutter_skills_ignore.json +3 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/grill-me/SKILL.md +10 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/ignore.json +3 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/test-driven-development/SKILL.md +371 -0
- package/skills/flutter-skills/tool/dart_skills_lint/.agents/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/skills/flutter-skills/tool/dart_skills_lint/AUTHORS +7 -0
- package/skills/flutter-skills/tool/dart_skills_lint/CHANGELOG.md +12 -0
- package/skills/flutter-skills/tool/dart_skills_lint/CONTRIBUTING.md +51 -0
- package/skills/flutter-skills/tool/dart_skills_lint/LICENSE +27 -0
- package/skills/flutter-skills/tool/dart_skills_lint/README.md +203 -0
- package/skills/flutter-skills/tool/dart_skills_lint/analysis_options.yaml +296 -0
- package/skills/flutter-skills/tool/dart_skills_lint/bench/README.md +23 -0
- package/skills/flutter-skills/tool/dart_skills_lint/bench/baseline_throughput.dart +230 -0
- package/skills/flutter-skills/tool/dart_skills_lint/bin/cli.dart +10 -0
- package/skills/flutter-skills/tool/dart_skills_lint/dart_skills_lint.yaml +14 -0
- package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/PRODUCTION_READYNESS.md +48 -0
- package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/completion_migration_plan.md +99 -0
- package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/legacy_patterns_report.md +110 -0
- package/skills/flutter-skills/tool/dart_skills_lint/documentation/feature_design_docs/pub_vs_skill_report.md +56 -0
- package/skills/flutter-skills/tool/dart_skills_lint/documentation/knowledge/SPECIFICATION.md +79 -0
- package/skills/flutter-skills/tool/dart_skills_lint/documentation/knowledge/architecture_overview.md +64 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/dart_skills_lint.dart +11 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/config_parser.dart +156 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/entry_point.dart +354 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/fixable_rule.dart +20 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/analysis_severity.dart +15 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/check_type.dart +17 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/ignore_entry.dart +34 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/ignore_entry.g.dart +19 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skill_context.dart +27 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skill_rule.dart +27 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skills_ignores.dart +26 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/skills_ignores.g.dart +24 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/models/validation_error.dart +31 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rule_registry.dart +79 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/absolute_paths_rule.dart +74 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/description_length_rule.dart +49 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/disallowed_field_rule.dart +61 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/name_format_rule.dart +167 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/relative_paths_rule.dart +72 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/trailing_whitespace_rule.dart +93 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/rules/valid_yaml_metadata_rule.dart +74 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/skills_ignores_storage.dart +36 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/validation_session.dart +559 -0
- package/skills/flutter-skills/tool/dart_skills_lint/lib/src/validator.dart +238 -0
- package/skills/flutter-skills/tool/dart_skills_lint/pubspec.yaml +28 -0
- package/skills/flutter-skills/tool/dart_skills_lint/skills/README.md +10 -0
- package/skills/flutter-skills/tool/dart_skills_lint/skills/dart-skills-lint-validation/SKILL.md +195 -0
- package/skills/flutter-skills/tool/dart_skills_lint/skills-lock.json +75 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/absolute_paths_test.dart +167 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/cli_integration_test.dart +683 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/config_file_test.dart +292 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/custom_rule_test.dart +122 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/directory_structure_test.dart +163 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/field_constraints_test.dart +178 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/fixer_test.dart +172 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/ignore_models_test.dart +63 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/metadata_validation_test.dart +116 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/relative_path_flag_test.dart +70 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/relative_paths_test.dart +172 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/resolve_rules_test.dart +82 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/rule_naming_test.dart +29 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/skills_ignores_storage_test.dart +89 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/test_utils.dart +19 -0
- package/skills/flutter-skills/tool/dart_skills_lint/test/trailing_whitespace_test.dart +152 -0
- package/skills/flutter-skills/tool/generator/README.md +150 -0
- package/skills/flutter-skills/tool/generator/analysis_options.yaml +143 -0
- package/skills/flutter-skills/tool/generator/bin/skills.dart +73 -0
- package/skills/flutter-skills/tool/generator/lib/src/commands/base_skill_command.dart +87 -0
- package/skills/flutter-skills/tool/generator/lib/src/commands/base_yaml_command.dart +83 -0
- package/skills/flutter-skills/tool/generator/lib/src/commands/generate_skill_command.dart +92 -0
- package/skills/flutter-skills/tool/generator/lib/src/commands/update_readme_command.dart +150 -0
- package/skills/flutter-skills/tool/generator/lib/src/commands/update_skill_command.dart +97 -0
- package/skills/flutter-skills/tool/generator/lib/src/commands/validate_skill_command.dart +284 -0
- package/skills/flutter-skills/tool/generator/lib/src/models/skill_params.dart +41 -0
- package/skills/flutter-skills/tool/generator/lib/src/services/gemini_service.dart +310 -0
- package/skills/flutter-skills/tool/generator/lib/src/services/markdown_converter.dart +226 -0
- package/skills/flutter-skills/tool/generator/lib/src/services/prompts.dart +72 -0
- package/skills/flutter-skills/tool/generator/lib/src/services/resource_fetcher_service.dart +84 -0
- package/skills/flutter-skills/tool/generator/lib/src/services/skill_instructions.dart +30 -0
- package/skills/flutter-skills/tool/generator/pubspec.yaml +32 -0
- package/skills/flutter-skills/tool/generator/test/commands/base_skill_command_test.dart +131 -0
- package/skills/flutter-skills/tool/generator/test/commands/validate_skills_input_test.dart +263 -0
- package/skills/flutter-skills/tool/generator/test/custom_skill_rules/last_modified_rule.dart +32 -0
- package/skills/flutter-skills/tool/generator/test/generate_skills_retry_test.dart +105 -0
- package/skills/flutter-skills/tool/generator/test/generate_skills_test.dart +519 -0
- package/skills/flutter-skills/tool/generator/test/lint_skills_test.dart +34 -0
- package/skills/flutter-skills/tool/generator/test/markdown_converter_test.dart +103 -0
- package/skills/flutter-skills/tool/generator/test/markdown_table_test.dart +131 -0
- package/skills/flutter-skills/tool/generator/test/models/skill_params_test.dart +37 -0
- package/skills/flutter-skills/tool/generator/test/services/gemini_service_test.dart +291 -0
- package/skills/flutter-skills/tool/generator/test/services/markdown_converter_test.dart +156 -0
- package/skills/flutter-skills/tool/generator/test/services/resource_fetcher_service_test.dart +188 -0
- package/skills/flutter-skills/tool/generator/test/update_skills_test.dart +241 -0
- package/skills/flutter-skills/tool/generator/test/validate_skills_test.dart +728 -0
- package/skills/quality-assurance/bdd-acceptance/SKILL.md +5 -14
- package/skills/quality-assurance/flutter-test/SKILL.md +5 -16
- package/skills/rules/project_rules.md +538 -127
- package/skills/special-tools/env-check/SKILL.md +5 -13
- package/skills/special-tools/ios-full-auto-debug/SKILL.md +5 -15
- package/skills/writing-skills/SKILL.md +654 -0
- package/skills/writing-skills/anthropic-best-practices.md +1149 -0
- package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/writing-skills/persuasion-principles.md +187 -0
- package/skills/writing-skills/render-graphs.js +168 -0
- package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/skills/checklist.md +0 -154
- package/skills/rules/user_rules.md +0 -263
- 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
- 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
- package/skills//346/212/200/350/203/275/344/275/277/347/224/250/346/214/207/345/215/227.md +0 -309
- 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
|
+
}
|