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.
- 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 +176 -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,30 @@
|
|
|
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
|
+
/// Instructions for authoring Skills.
|
|
6
|
+
const String skillInstructions = '''
|
|
7
|
+
# Role
|
|
8
|
+
Act as an Expert Skill Author. Generate high-performance, well-structured Skill modules (SKILL.md) that follow the "Skill authoring best practices" guide.
|
|
9
|
+
|
|
10
|
+
# Authoring Guidelines
|
|
11
|
+
1. **Concise & Expert:** Assume the AI is highly competent. Only provide context the AI doesn't already have. Challenge each paragraph: "Does this justify its token cost?". Avoid explaining basic concepts.
|
|
12
|
+
2. **Imperative Mood:** Write all instructions and best practices using the imperative mood (e.g., "Implement the repository..." rather than "The agent should implement...").
|
|
13
|
+
3. **Single File Constraint:** Do not use external file references (e.g., [other.md](other.md)). Include all necessary content within the SKILL.md file. Use <details> tags to collapse lengthy reference material if it exceeds 500 lines.
|
|
14
|
+
4. **Naming:** Use the gerund form (verb + -ing) for the H1 title.
|
|
15
|
+
5. **Workflows & Feedback Loops:** For complex tasks, implement sequential workflows with "Task Progress" checklists that the agent can copy to track progress. Include feedback loops where the agent must "Run validator -> review errors -> fix" for quality-critical operations.
|
|
16
|
+
6. **Conditional Logic:** Use conditional workflows to guide the agent through decision points (e.g., "If creating NEW content..." vs "If EDITING existing content...").
|
|
17
|
+
7. **Examples:** When output quality depends on style or specific formatting, provide clear input/output pairs or high-fidelity implementation examples.
|
|
18
|
+
8. **Consistent Terminology:** Choose one clear term for concepts (e.g., "API endpoint", "Widget state") and use it throughout.
|
|
19
|
+
9. **Single File Constraint:** Do not use external file references (e.g., [other.md](other.md)). Include all necessary content within the SKILL.md file. Use <details> tags to collapse lengthy reference material if it exceeds 500 lines.
|
|
20
|
+
|
|
21
|
+
# Formatting Rules
|
|
22
|
+
1. **No YAML**: DO NOT include any YAML frontmatter in your response. Start immediately with the markdown content (e.g., the H1 title).
|
|
23
|
+
2. **Raw Markdown**: DO NOT wrap the entire output in a markdown code block (e.g., ```markdown ... ```). Return raw markdown text.
|
|
24
|
+
3. **Structure**: When generating or updating a skill module, follow this hierarchy:
|
|
25
|
+
- **# [Gerund Form Title]**
|
|
26
|
+
- **## Contents**: A table of contents linking to all H2 sections.
|
|
27
|
+
- **## [Domain/Topic Sections]**: Organized H2 sections covering concepts and core guidelines.
|
|
28
|
+
- **## [Workflow Sections]**: Sequential, checklist-based guides for common tasks.
|
|
29
|
+
- **## Examples** (If applicable): Concrete examples demonstrating the preferred implementation.
|
|
30
|
+
''';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: skills
|
|
2
|
+
description: A command-line application for creating and validating Agent Skills.
|
|
3
|
+
version: 0.0.1
|
|
4
|
+
resolution: workspace
|
|
5
|
+
|
|
6
|
+
environment:
|
|
7
|
+
sdk: ^3.10.8
|
|
8
|
+
|
|
9
|
+
# Add regular dependencies here.
|
|
10
|
+
dependencies:
|
|
11
|
+
args: ^2.7.0
|
|
12
|
+
file: ^7.0.1
|
|
13
|
+
glob: ^2.1.3
|
|
14
|
+
google_cloud_ai_generativelanguage_v1beta: ^0.5.2
|
|
15
|
+
googleapis_auth: ^2.0.0
|
|
16
|
+
html: ^0.15.6
|
|
17
|
+
http: ^1.6.0
|
|
18
|
+
logging: ^1.3.0
|
|
19
|
+
meta: ^1.18.1
|
|
20
|
+
path: ^1.9.1
|
|
21
|
+
platform: ^3.1.6
|
|
22
|
+
retry: ^3.1.2
|
|
23
|
+
yaml: ^3.1.2
|
|
24
|
+
|
|
25
|
+
dev_dependencies:
|
|
26
|
+
build_verify: ^3.1.0
|
|
27
|
+
coverage: ^1.15.0
|
|
28
|
+
dart_skills_lint:
|
|
29
|
+
path: ../dart_skills_lint
|
|
30
|
+
lints: ^6.0.0
|
|
31
|
+
test: ^1.25.6
|
|
32
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
|
|
2
|
+
// for details. All rights reserved. Use of this source code is governed by a
|
|
3
|
+
// BSD-style license that can be found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import 'dart:convert';
|
|
6
|
+
import 'dart:io';
|
|
7
|
+
|
|
8
|
+
import 'package:args/command_runner.dart';
|
|
9
|
+
import 'package:http/testing.dart';
|
|
10
|
+
import 'package:logging/logging.dart';
|
|
11
|
+
import 'package:path/path.dart' as p;
|
|
12
|
+
import 'package:skills/src/commands/base_skill_command.dart';
|
|
13
|
+
import 'package:skills/src/models/skill_params.dart';
|
|
14
|
+
import 'package:skills/src/services/gemini_service.dart';
|
|
15
|
+
import 'package:test/test.dart';
|
|
16
|
+
|
|
17
|
+
class _TestSkillCommand extends BaseSkillCommand {
|
|
18
|
+
_TestSkillCommand({required super.httpClient, super.environment})
|
|
19
|
+
: super(logger: Logger('_TestSkillCommand'));
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
String get name => 'test-command';
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
String get description => 'Description';
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
Future<void> runSkill(
|
|
29
|
+
SkillParams skill,
|
|
30
|
+
GeminiService gemini,
|
|
31
|
+
Directory outputDir,
|
|
32
|
+
int thinkingBudget, {
|
|
33
|
+
Directory? configDir,
|
|
34
|
+
}) async {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void main() {
|
|
38
|
+
group('BaseSkillCommand Edge Cases', () {
|
|
39
|
+
late CommandRunner<void> runner;
|
|
40
|
+
late Directory tempDir;
|
|
41
|
+
late MockClient mockClient;
|
|
42
|
+
final logs = <String>[];
|
|
43
|
+
|
|
44
|
+
setUp(() async {
|
|
45
|
+
tempDir = await Directory.systemTemp.createTemp(
|
|
46
|
+
'base_skill_commands_test',
|
|
47
|
+
);
|
|
48
|
+
mockClient = MockClient((request) async => throw UnimplementedError());
|
|
49
|
+
runner = CommandRunner<void>('skills', 'Test runner')
|
|
50
|
+
..addCommand(_TestSkillCommand(httpClient: mockClient));
|
|
51
|
+
|
|
52
|
+
Logger.root.level = Level.INFO;
|
|
53
|
+
Logger.root.onRecord.listen((record) => logs.add(record.message));
|
|
54
|
+
logs.clear();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
tearDown(() async {
|
|
58
|
+
await tempDir.delete(recursive: true);
|
|
59
|
+
logs.clear();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('logs severe error when configuration file not found', () async {
|
|
63
|
+
final path = p.join(tempDir.path, 'missing.yaml');
|
|
64
|
+
await IOOverrides.runZoned(() async {
|
|
65
|
+
await runner.run(['test-command', path]);
|
|
66
|
+
}, getCurrentDirectory: () => tempDir);
|
|
67
|
+
|
|
68
|
+
expect(logs, contains('Configuration file not found: $path'));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('logs warning when no skills match the --skill filter', () async {
|
|
72
|
+
final configFile = File(p.join(tempDir.path, 'config.yaml'));
|
|
73
|
+
await configFile.writeAsString(
|
|
74
|
+
jsonEncode(<Map<String, dynamic>>[
|
|
75
|
+
{
|
|
76
|
+
'name': 'existent-skill',
|
|
77
|
+
'description': 'desc',
|
|
78
|
+
'resources': <String>[],
|
|
79
|
+
},
|
|
80
|
+
]),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
await IOOverrides.runZoned(() async {
|
|
84
|
+
await runner.run([
|
|
85
|
+
'test-command',
|
|
86
|
+
configFile.path,
|
|
87
|
+
'--skill',
|
|
88
|
+
'non-existent',
|
|
89
|
+
]);
|
|
90
|
+
}, getCurrentDirectory: () => tempDir);
|
|
91
|
+
|
|
92
|
+
expect(logs, contains('No skill found with name: non-existent'));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('logs warning when configuration file contains no skills', () async {
|
|
96
|
+
final configFile = File(p.join(tempDir.path, 'empty.yaml'));
|
|
97
|
+
await configFile.writeAsString('[]');
|
|
98
|
+
|
|
99
|
+
await IOOverrides.runZoned(() async {
|
|
100
|
+
await runner.run(['test-command', configFile.path]);
|
|
101
|
+
}, getCurrentDirectory: () => tempDir);
|
|
102
|
+
|
|
103
|
+
expect(logs, contains('No skills found in configuration file.'));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('logs severe error when GEMINI_API_KEY is not set', () async {
|
|
107
|
+
final configFile = File(p.join(tempDir.path, 'config.yaml'));
|
|
108
|
+
await configFile.writeAsString(
|
|
109
|
+
jsonEncode(<Map<String, dynamic>>[
|
|
110
|
+
{
|
|
111
|
+
'name': 'existent-skill',
|
|
112
|
+
'description': 'desc',
|
|
113
|
+
'resources': <String>[],
|
|
114
|
+
},
|
|
115
|
+
]),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Override environment parameter to simulate missing api key
|
|
119
|
+
runner = CommandRunner(
|
|
120
|
+
'skills',
|
|
121
|
+
'Test runner',
|
|
122
|
+
)..addCommand(_TestSkillCommand(httpClient: mockClient, environment: {}));
|
|
123
|
+
|
|
124
|
+
await IOOverrides.runZoned(() async {
|
|
125
|
+
await runner.run(['test-command', configFile.path]);
|
|
126
|
+
}, getCurrentDirectory: () => tempDir);
|
|
127
|
+
|
|
128
|
+
expect(logs, contains('GEMINI_API_KEY environment variable not set.'));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
|
|
2
|
+
// for details. All rights reserved. Use of this source code is governed by a
|
|
3
|
+
// BSD-style license that can be found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import 'dart:convert';
|
|
6
|
+
import 'dart:io';
|
|
7
|
+
|
|
8
|
+
import 'package:args/command_runner.dart';
|
|
9
|
+
import 'package:http/http.dart' as http;
|
|
10
|
+
import 'package:http/testing.dart';
|
|
11
|
+
import 'package:logging/logging.dart';
|
|
12
|
+
import 'package:path/path.dart' as p;
|
|
13
|
+
import 'package:skills/src/commands/validate_skill_command.dart';
|
|
14
|
+
import 'package:test/test.dart';
|
|
15
|
+
|
|
16
|
+
void main() {
|
|
17
|
+
group('ValidateSkillCommand Input Validation', () {
|
|
18
|
+
late CommandRunner<void> runner;
|
|
19
|
+
late Directory tempDir;
|
|
20
|
+
late MockClient mockClient;
|
|
21
|
+
final logs = <String>[];
|
|
22
|
+
|
|
23
|
+
setUp(() async {
|
|
24
|
+
tempDir = await Directory.systemTemp.createTemp(
|
|
25
|
+
'validate_skills_input_test',
|
|
26
|
+
);
|
|
27
|
+
mockClient = MockClient((request) async => http.Response('', 200));
|
|
28
|
+
|
|
29
|
+
Logger.root.level = Level.INFO;
|
|
30
|
+
Logger.root.onRecord.listen((record) {
|
|
31
|
+
logs.add(record.message);
|
|
32
|
+
});
|
|
33
|
+
logs.clear();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
tearDown(() async {
|
|
37
|
+
await tempDir.delete(recursive: true);
|
|
38
|
+
logs.clear();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
Future<void> runValidation(String filename, String content) async {
|
|
42
|
+
final configFile = File(p.join(tempDir.path, filename));
|
|
43
|
+
await configFile.writeAsString(content);
|
|
44
|
+
|
|
45
|
+
runner = CommandRunner<void>('skills', 'Test runner')
|
|
46
|
+
..addCommand(
|
|
47
|
+
ValidateSkillCommand(
|
|
48
|
+
environment: {'GEMINI_API_KEY': 'test-key'},
|
|
49
|
+
outputDir: tempDir,
|
|
50
|
+
httpClient: mockClient,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await IOOverrides.runZoned(() async {
|
|
55
|
+
await runner.run(['validate-skill', configFile.path]);
|
|
56
|
+
}, getCurrentDirectory: () => tempDir);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test('passes when configuration is perfectly valid', () async {
|
|
60
|
+
final content = jsonEncode([
|
|
61
|
+
{
|
|
62
|
+
'name': 'flutter-test-skill',
|
|
63
|
+
'description': 'A Flutter test skill',
|
|
64
|
+
'resources': ['https://example.com/doc'],
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
await runValidation('flutter_skills.yaml', content);
|
|
69
|
+
expect(logs, isNot(contains('Configuration validation failed.')));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('fails when root structure is not a list', () async {
|
|
73
|
+
final content = jsonEncode({'name': 'flutter-test-skill'});
|
|
74
|
+
|
|
75
|
+
await runValidation('flutter_skills.yaml', content);
|
|
76
|
+
expect(
|
|
77
|
+
logs,
|
|
78
|
+
contains('Invalid configuration: Root must be a YAML list.'),
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('fails when configuration list is empty', () async {
|
|
83
|
+
final content = jsonEncode([]);
|
|
84
|
+
|
|
85
|
+
await runValidation('flutter_skills.yaml', content);
|
|
86
|
+
expect(logs, contains('Configuration list must not be empty.'));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('fails when item is not a map', () async {
|
|
90
|
+
final content = jsonEncode(['just-a-string-not-a-map']);
|
|
91
|
+
|
|
92
|
+
await runValidation('flutter_skills.yaml', content);
|
|
93
|
+
expect(logs, contains('Item 0 is not a Map.'));
|
|
94
|
+
expect(logs, contains('Configuration validation failed.'));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('fails when fields are missing', () async {
|
|
98
|
+
final content = jsonEncode([
|
|
99
|
+
{'name': 'flutter-skill'},
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
await runValidation('flutter_skills.yaml', content);
|
|
103
|
+
expect(
|
|
104
|
+
logs,
|
|
105
|
+
contains(
|
|
106
|
+
'Skill "flutter-skill" is missing required field "description".',
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
expect(
|
|
110
|
+
logs,
|
|
111
|
+
contains(
|
|
112
|
+
'Skill "flutter-skill" is missing required field "resources".',
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
expect(logs, contains('Configuration validation failed.'));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('fails when skill name is not kabob-case', () async {
|
|
119
|
+
final content = jsonEncode([
|
|
120
|
+
{
|
|
121
|
+
'name': 'flutter_skill_invalid',
|
|
122
|
+
'description': 'A Flutter skill description',
|
|
123
|
+
'resources': ['https://example.com'],
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
await runValidation('flutter_skills.yaml', content);
|
|
128
|
+
expect(
|
|
129
|
+
logs,
|
|
130
|
+
contains(
|
|
131
|
+
'Skill name "flutter_skill_invalid" must be kabob-case (e.g. abc-def).',
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
expect(logs, contains('Configuration validation failed.'));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test(
|
|
138
|
+
'fails when flutter_skills.yaml name or description conventions are violated',
|
|
139
|
+
() async {
|
|
140
|
+
final content = jsonEncode([
|
|
141
|
+
{
|
|
142
|
+
'name': 'dart-skill',
|
|
143
|
+
'description': 'A skill description with no framework keyword',
|
|
144
|
+
'resources': ['https://example.com'],
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
await runValidation('flutter_skills.yaml', content);
|
|
149
|
+
expect(
|
|
150
|
+
logs,
|
|
151
|
+
contains(
|
|
152
|
+
'Skill name "dart-skill" in flutter_skills.yaml must start with "flutter-".',
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
expect(logs, contains('Configuration validation failed.'));
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
test(
|
|
160
|
+
'fails when dart_skills.yaml name or description conventions are violated',
|
|
161
|
+
() async {
|
|
162
|
+
final content = jsonEncode([
|
|
163
|
+
{
|
|
164
|
+
'name': 'flutter-skill',
|
|
165
|
+
'description': 'A skill description with no language keyword',
|
|
166
|
+
'resources': ['https://example.com'],
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
await runValidation('dart_skills.yaml', content);
|
|
171
|
+
expect(
|
|
172
|
+
logs,
|
|
173
|
+
contains(
|
|
174
|
+
'Skill name "flutter-skill" in dart_skills.yaml must start with "dart-".',
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
expect(logs, contains('Configuration validation failed.'));
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
test('fails when resources list is empty', () async {
|
|
182
|
+
final content = jsonEncode([
|
|
183
|
+
{
|
|
184
|
+
'name': 'flutter-skill',
|
|
185
|
+
'description': 'A Flutter skill description',
|
|
186
|
+
'resources': <dynamic>[],
|
|
187
|
+
},
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
await runValidation('flutter_skills.yaml', content);
|
|
191
|
+
expect(
|
|
192
|
+
logs,
|
|
193
|
+
contains('Skill "flutter-skill" field "resources" must not be empty.'),
|
|
194
|
+
);
|
|
195
|
+
expect(logs, contains('Configuration validation failed.'));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('fails when resource URL uses insecure HTTP protocol', () async {
|
|
199
|
+
final content = jsonEncode([
|
|
200
|
+
{
|
|
201
|
+
'name': 'flutter-skill',
|
|
202
|
+
'description': 'A Flutter skill description',
|
|
203
|
+
'resources': ['http://example.com/insecure'],
|
|
204
|
+
},
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
await runValidation('flutter_skills.yaml', content);
|
|
208
|
+
expect(
|
|
209
|
+
logs,
|
|
210
|
+
contains(
|
|
211
|
+
'Skill "flutter-skill" resource URL "http://example.com/insecure" must use secure HTTPS.',
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
expect(logs, contains('Configuration validation failed.'));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('fails when fields have invalid types', () async {
|
|
218
|
+
final content1 = jsonEncode([
|
|
219
|
+
{
|
|
220
|
+
'name': 12345, // int, should be string
|
|
221
|
+
'description': 'A Flutter skill description',
|
|
222
|
+
'resources': ['https://example.com'],
|
|
223
|
+
},
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
await runValidation('flutter_skills.yaml', content1);
|
|
227
|
+
expect(logs, contains('Item 0 field "name" must be a string.'));
|
|
228
|
+
|
|
229
|
+
logs.clear();
|
|
230
|
+
final content2 = jsonEncode([
|
|
231
|
+
{
|
|
232
|
+
'name': 'flutter-skill',
|
|
233
|
+
'description': true, // bool, should be string
|
|
234
|
+
'resources': ['https://example.com'],
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
await runValidation('flutter_skills.yaml', content2);
|
|
239
|
+
expect(
|
|
240
|
+
logs,
|
|
241
|
+
contains('Skill "flutter-skill" field "description" must be a string.'),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
logs.clear();
|
|
245
|
+
final content3 = jsonEncode([
|
|
246
|
+
{
|
|
247
|
+
'name': 'flutter-skill',
|
|
248
|
+
'description': 'A Flutter skill description',
|
|
249
|
+
'resources': ['https://example.com'],
|
|
250
|
+
'instructions': 999, // int, should be string
|
|
251
|
+
},
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
await runValidation('flutter_skills.yaml', content3);
|
|
255
|
+
expect(
|
|
256
|
+
logs,
|
|
257
|
+
contains(
|
|
258
|
+
'Skill "flutter-skill" field "instructions" must be a string.',
|
|
259
|
+
),
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import 'package:dart_skills_lint/dart_skills_lint.dart';
|
|
2
|
+
|
|
3
|
+
class LastModifiedRule extends SkillRule {
|
|
4
|
+
static const _metadataKey = 'metadata';
|
|
5
|
+
static const _lastModifiedKey = 'last_modified';
|
|
6
|
+
|
|
7
|
+
@override
|
|
8
|
+
final String name = 'generator:last-modified';
|
|
9
|
+
|
|
10
|
+
@override
|
|
11
|
+
final AnalysisSeverity severity = AnalysisSeverity.error;
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
Future<List<ValidationError>> validate(SkillContext context) async {
|
|
15
|
+
final errors = <ValidationError>[];
|
|
16
|
+
final yaml = context.parsedYaml;
|
|
17
|
+
if (yaml == null) return errors;
|
|
18
|
+
|
|
19
|
+
final Object? metadata = yaml[_metadataKey];
|
|
20
|
+
if (metadata is! Map || !metadata.containsKey(_lastModifiedKey)) {
|
|
21
|
+
errors.add(
|
|
22
|
+
ValidationError(
|
|
23
|
+
ruleId: name,
|
|
24
|
+
severity: severity,
|
|
25
|
+
file: 'SKILL.md',
|
|
26
|
+
message: 'Missing field: $_metadataKey.$_lastModifiedKey',
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return errors;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
|
|
2
|
+
// for details. All rights reserved. Use of this source code is governed by a
|
|
3
|
+
// BSD-style license that can be found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import 'dart:convert';
|
|
6
|
+
import 'dart:io';
|
|
7
|
+
|
|
8
|
+
import 'package:args/command_runner.dart';
|
|
9
|
+
import 'package:http/http.dart' as http;
|
|
10
|
+
import 'package:http/testing.dart';
|
|
11
|
+
import 'package:logging/logging.dart';
|
|
12
|
+
import 'package:path/path.dart' as p;
|
|
13
|
+
import 'package:skills/src/commands/generate_skill_command.dart';
|
|
14
|
+
import 'package:test/test.dart';
|
|
15
|
+
|
|
16
|
+
void main() {
|
|
17
|
+
group('GenerateSkillsCommand Retry Logic', () {
|
|
18
|
+
late CommandRunner<void> runner;
|
|
19
|
+
late Directory tempDir;
|
|
20
|
+
late File inputFile;
|
|
21
|
+
|
|
22
|
+
setUp(() async {
|
|
23
|
+
tempDir = await Directory.systemTemp.createTemp('skills_retry_test');
|
|
24
|
+
inputFile = File(p.join(tempDir.path, 'input.yaml'));
|
|
25
|
+
runner = CommandRunner<void>('skills', 'Test runner');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
tearDown(() async {
|
|
29
|
+
await tempDir.delete(recursive: true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('retries failed LLM calls up to 3 times', () async {
|
|
33
|
+
const url = 'https://example.com/retry';
|
|
34
|
+
inputFile.writeAsStringSync(
|
|
35
|
+
jsonEncode([
|
|
36
|
+
{
|
|
37
|
+
'name': 'retry_skill',
|
|
38
|
+
'description': 'Retry Description',
|
|
39
|
+
'resources': [url],
|
|
40
|
+
},
|
|
41
|
+
]),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
var attemptCount = 0;
|
|
45
|
+
final logs = <String>[];
|
|
46
|
+
final sub = Logger.root.onRecord.listen((record) {
|
|
47
|
+
logs.add(record.message);
|
|
48
|
+
});
|
|
49
|
+
addTearDown(sub.cancel);
|
|
50
|
+
|
|
51
|
+
final mockClient = MockClient((request) async {
|
|
52
|
+
if (request.url.toString() == url) {
|
|
53
|
+
return http.Response('<html>Content</html>', 200);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (request.url.toString().contains('generativelanguage')) {
|
|
57
|
+
attemptCount++;
|
|
58
|
+
if (attemptCount < 3) {
|
|
59
|
+
throw Exception('Simulated Network Error');
|
|
60
|
+
}
|
|
61
|
+
return http.Response(
|
|
62
|
+
jsonEncode({
|
|
63
|
+
'candidates': [
|
|
64
|
+
{
|
|
65
|
+
'content': {
|
|
66
|
+
'parts': [
|
|
67
|
+
{'text': '---\nname: skill\n---\nContent'},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
}),
|
|
73
|
+
200,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return http.Response('Not Found', 404);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
final command = GenerateSkillCommand(
|
|
80
|
+
environment: {'GEMINI_API_KEY': 'test-key'},
|
|
81
|
+
httpClient: mockClient,
|
|
82
|
+
outputDir: tempDir,
|
|
83
|
+
);
|
|
84
|
+
runner.addCommand(command);
|
|
85
|
+
|
|
86
|
+
await runner.run(['generate-skill', inputFile.path]);
|
|
87
|
+
|
|
88
|
+
expect(
|
|
89
|
+
attemptCount,
|
|
90
|
+
3,
|
|
91
|
+
reason: 'Should attempt 3 times (1 initial + 2 retries)',
|
|
92
|
+
);
|
|
93
|
+
expect(
|
|
94
|
+
logs,
|
|
95
|
+
contains(contains('Retrying Gemini generation')),
|
|
96
|
+
reason: 'Should log retry warnings',
|
|
97
|
+
);
|
|
98
|
+
expect(
|
|
99
|
+
logs,
|
|
100
|
+
contains(contains('Generated')),
|
|
101
|
+
reason: 'Should eventually succeed',
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|