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,188 @@
|
|
|
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' as io;
|
|
6
|
+
|
|
7
|
+
import 'package:http/http.dart' as http;
|
|
8
|
+
import 'package:http/testing.dart';
|
|
9
|
+
import 'package:logging/logging.dart';
|
|
10
|
+
import 'package:path/path.dart' as p;
|
|
11
|
+
|
|
12
|
+
import 'package:skills/src/services/resource_fetcher_service.dart';
|
|
13
|
+
import 'package:test/test.dart';
|
|
14
|
+
|
|
15
|
+
void main() {
|
|
16
|
+
group('ResourceFetcherService', () {
|
|
17
|
+
late Logger logger;
|
|
18
|
+
|
|
19
|
+
setUp(() {
|
|
20
|
+
logger = Logger('test');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('fetches and converts content successfully on 200 OK', () async {
|
|
24
|
+
final client = MockClient((request) async {
|
|
25
|
+
if (request.url.toString() == 'https://example.com/doc1') {
|
|
26
|
+
return http.Response('<h1>Doc 1</h1>', 200);
|
|
27
|
+
} else if (request.url.toString() == 'https://example.com/doc2') {
|
|
28
|
+
return http.Response('<p>Doc 2 content</p>', 200);
|
|
29
|
+
}
|
|
30
|
+
return http.Response('Not found', 404);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
final fetcher = ResourceFetcherService(
|
|
34
|
+
httpClient: client,
|
|
35
|
+
logger: logger,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
final result = await fetcher.fetchAndConvertContent([
|
|
39
|
+
'https://example.com/doc1',
|
|
40
|
+
'https://example.com/doc2',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
expect(result, contains('Doc 1'));
|
|
44
|
+
expect(result, contains('Doc 2 content'));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('throws Exception on non-200 status code to save tokens', () async {
|
|
48
|
+
final client = MockClient((request) async {
|
|
49
|
+
return http.Response('Not found', 404);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
final fetcher = ResourceFetcherService(
|
|
53
|
+
httpClient: client,
|
|
54
|
+
logger: logger,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(
|
|
58
|
+
() => fetcher.fetchAndConvertContent(['https://example.com/missing']),
|
|
59
|
+
throwsA(
|
|
60
|
+
isA<Exception>().having(
|
|
61
|
+
(e) => e.toString(),
|
|
62
|
+
'message',
|
|
63
|
+
contains('HTTP 404'),
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('throws exception on network error to save tokens', () async {
|
|
70
|
+
final client = MockClient((request) async {
|
|
71
|
+
throw http.ClientException('Connection failed');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
final fetcher = ResourceFetcherService(
|
|
75
|
+
httpClient: client,
|
|
76
|
+
logger: logger,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(
|
|
80
|
+
() => fetcher.fetchAndConvertContent(['https://example.com/error']),
|
|
81
|
+
throwsA(isA<http.ClientException>()),
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('throws Exception for insecure http:// URL', () async {
|
|
86
|
+
final client = MockClient((request) async {
|
|
87
|
+
return http.Response('content', 200);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
final fetcher = ResourceFetcherService(
|
|
91
|
+
httpClient: client,
|
|
92
|
+
logger: logger,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(
|
|
96
|
+
() => fetcher.fetchAndConvertContent(['http://example.com/doc1']),
|
|
97
|
+
throwsA(
|
|
98
|
+
isA<Exception>().having(
|
|
99
|
+
(e) => e.toString(),
|
|
100
|
+
'message',
|
|
101
|
+
contains('Insecure HTTP URL found'),
|
|
102
|
+
),
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('fetches local file correctly relative to configDir', () async {
|
|
108
|
+
final tempDir = io.Directory.systemTemp.createTempSync('gemini_test');
|
|
109
|
+
try {
|
|
110
|
+
io.File(
|
|
111
|
+
p.join(tempDir.path, 'local_doc.md'),
|
|
112
|
+
).writeAsStringSync('# Local Doc\ncontent');
|
|
113
|
+
|
|
114
|
+
final client = MockClient((request) async {
|
|
115
|
+
return http.Response('Not found', 404);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
final fetcher = ResourceFetcherService(
|
|
119
|
+
httpClient: client,
|
|
120
|
+
logger: logger,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
final result = await fetcher.fetchAndConvertContent([
|
|
124
|
+
'local_doc.md',
|
|
125
|
+
], configDir: tempDir);
|
|
126
|
+
|
|
127
|
+
expect(result, contains('Local Doc'));
|
|
128
|
+
expect(result, contains('content'));
|
|
129
|
+
} finally {
|
|
130
|
+
tempDir.deleteSync(recursive: true);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('throws Exception for missing local file', () async {
|
|
135
|
+
final tempDir = io.Directory.systemTemp.createTempSync('gemini_test');
|
|
136
|
+
try {
|
|
137
|
+
final client = MockClient((request) async {
|
|
138
|
+
return http.Response('Not found', 404);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
final fetcher = ResourceFetcherService(
|
|
142
|
+
httpClient: client,
|
|
143
|
+
logger: logger,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(
|
|
147
|
+
() => fetcher.fetchAndConvertContent([
|
|
148
|
+
'missing.md',
|
|
149
|
+
], configDir: tempDir),
|
|
150
|
+
throwsA(
|
|
151
|
+
isA<Exception>().having(
|
|
152
|
+
(e) => e.toString(),
|
|
153
|
+
'message',
|
|
154
|
+
contains('Local resource file not found'),
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
} finally {
|
|
159
|
+
tempDir.deleteSync(recursive: true);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test(
|
|
164
|
+
'throws Exception for local file when no configDir is provided',
|
|
165
|
+
() async {
|
|
166
|
+
final client = MockClient((request) async {
|
|
167
|
+
return http.Response('Not found', 404);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
final fetcher = ResourceFetcherService(
|
|
171
|
+
httpClient: client,
|
|
172
|
+
logger: logger,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
expect(
|
|
176
|
+
() => fetcher.fetchAndConvertContent(['local_doc.md']),
|
|
177
|
+
throwsA(
|
|
178
|
+
isA<Exception>().having(
|
|
179
|
+
(e) => e.toString(),
|
|
180
|
+
'message',
|
|
181
|
+
contains('no configuration directory was provided to resolve it'),
|
|
182
|
+
),
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
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/update_skill_command.dart';
|
|
14
|
+
import 'package:test/test.dart';
|
|
15
|
+
|
|
16
|
+
void main() {
|
|
17
|
+
group('UpdateSkillCommand', () {
|
|
18
|
+
late CommandRunner<void> runner;
|
|
19
|
+
late Directory tempDir;
|
|
20
|
+
late File inputYamlFile;
|
|
21
|
+
|
|
22
|
+
setUp(() async {
|
|
23
|
+
tempDir = await Directory.systemTemp.createTemp('skills_update_test');
|
|
24
|
+
inputYamlFile = File(p.join(tempDir.path, 'dart_dev.yaml'));
|
|
25
|
+
runner = CommandRunner<void>('skills', 'Test runner');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
tearDown(() async {
|
|
29
|
+
await tempDir.delete(recursive: true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('updates skill from YAML input preserving existing content', () async {
|
|
33
|
+
final inputData = [
|
|
34
|
+
{
|
|
35
|
+
'name': 'foo',
|
|
36
|
+
'description': 'Foo description',
|
|
37
|
+
'resources': ['https://example.com/foo.html'],
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
inputYamlFile.writeAsStringSync(jsonEncode(inputData));
|
|
41
|
+
|
|
42
|
+
// Create existing skill
|
|
43
|
+
final skillDirFoo = Directory(p.join(tempDir.path, 'foo'))..createSync();
|
|
44
|
+
final skillFile = File(p.join(skillDirFoo.path, 'SKILL.md'))
|
|
45
|
+
..writeAsStringSync('# Existing Content\n');
|
|
46
|
+
|
|
47
|
+
final geminiRequests = <String>[];
|
|
48
|
+
// Mock HTTP Client
|
|
49
|
+
final mockClient = MockClient((request) async {
|
|
50
|
+
final url = request.url.toString();
|
|
51
|
+
|
|
52
|
+
// 1. Mock content fetch
|
|
53
|
+
if (url.startsWith('https://example.com')) {
|
|
54
|
+
return http.Response(
|
|
55
|
+
'<html><body><h1>New Content</h1></body></html>',
|
|
56
|
+
200,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 2. Mock Gemini API
|
|
61
|
+
if (url.contains('generativelanguage.googleapis.com')) {
|
|
62
|
+
geminiRequests.add(request.body);
|
|
63
|
+
return http.Response(
|
|
64
|
+
jsonEncode({
|
|
65
|
+
'candidates': [
|
|
66
|
+
{
|
|
67
|
+
'content': {
|
|
68
|
+
'parts': [
|
|
69
|
+
{'text': 'Updated Content with # Existing Content'},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
}),
|
|
75
|
+
200,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return http.Response('Not Found', 404);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
final command = UpdateSkillCommand(
|
|
83
|
+
environment: {'GEMINI_API_KEY': 'test-key'},
|
|
84
|
+
httpClient: mockClient,
|
|
85
|
+
outputDir: tempDir,
|
|
86
|
+
);
|
|
87
|
+
runner.addCommand(command);
|
|
88
|
+
|
|
89
|
+
// Run command
|
|
90
|
+
await runner.run(['update-skill', inputYamlFile.path]);
|
|
91
|
+
|
|
92
|
+
expect(skillFile.existsSync(), isTrue);
|
|
93
|
+
|
|
94
|
+
// Verify the updated content was written
|
|
95
|
+
final updatedText = skillFile.readAsStringSync();
|
|
96
|
+
expect(updatedText, contains('Updated Content with # Existing Content'));
|
|
97
|
+
|
|
98
|
+
// Verify source header was sent to Gemini
|
|
99
|
+
expect(geminiRequests, isNotEmpty);
|
|
100
|
+
expect(geminiRequests.first, contains('# Existing Content'));
|
|
101
|
+
expect(geminiRequests.first, contains('New Content'));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('fails gracefully when SKILL.md does not exist', () async {
|
|
105
|
+
final inputData = [
|
|
106
|
+
{
|
|
107
|
+
'name': 'missing',
|
|
108
|
+
'description': 'Description',
|
|
109
|
+
'resources': ['https://example.com/source'],
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
inputYamlFile.writeAsStringSync(jsonEncode(inputData));
|
|
113
|
+
|
|
114
|
+
final logs = <String>[];
|
|
115
|
+
final sub = Logger.root.onRecord.listen((record) {
|
|
116
|
+
logs.add(record.message);
|
|
117
|
+
});
|
|
118
|
+
addTearDown(sub.cancel);
|
|
119
|
+
|
|
120
|
+
final mockClient = MockClient((request) async {
|
|
121
|
+
return http.Response('Error', 500);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
final command = UpdateSkillCommand(
|
|
125
|
+
environment: {'GEMINI_API_KEY': 'test-key'},
|
|
126
|
+
httpClient: mockClient,
|
|
127
|
+
outputDir: tempDir,
|
|
128
|
+
);
|
|
129
|
+
runner.addCommand(command);
|
|
130
|
+
|
|
131
|
+
await runner.run(['update-skill', inputYamlFile.path]);
|
|
132
|
+
|
|
133
|
+
expect(logs, contains(contains('Skill file not found at')));
|
|
134
|
+
expect(logs, contains(contains('Cannot update an non-existent skill.')));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test(
|
|
138
|
+
'logs warning when fetchAndConvertContent returns empty string',
|
|
139
|
+
() async {
|
|
140
|
+
final inputData = [
|
|
141
|
+
{
|
|
142
|
+
'name': 'empty-fetch',
|
|
143
|
+
'description': 'Description',
|
|
144
|
+
'resources': <String>[],
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
inputYamlFile.writeAsStringSync(jsonEncode(inputData));
|
|
148
|
+
|
|
149
|
+
final skillDir = Directory(p.join(tempDir.path, 'empty-fetch'))
|
|
150
|
+
..createSync();
|
|
151
|
+
File(p.join(skillDir.path, 'SKILL.md')).writeAsStringSync('Existing');
|
|
152
|
+
|
|
153
|
+
final logs = <String>[];
|
|
154
|
+
final sub = Logger.root.onRecord.listen(
|
|
155
|
+
(record) => logs.add(record.message),
|
|
156
|
+
);
|
|
157
|
+
addTearDown(sub.cancel);
|
|
158
|
+
|
|
159
|
+
final mockClient = MockClient((request) async {
|
|
160
|
+
return http.Response('', 200); // Empty HTML body
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
final command = UpdateSkillCommand(
|
|
164
|
+
environment: {'GEMINI_API_KEY': 'test-key'},
|
|
165
|
+
httpClient: mockClient,
|
|
166
|
+
outputDir: tempDir,
|
|
167
|
+
);
|
|
168
|
+
runner.addCommand(command);
|
|
169
|
+
|
|
170
|
+
await runner.run(['update-skill', inputYamlFile.path]);
|
|
171
|
+
expect(
|
|
172
|
+
logs,
|
|
173
|
+
contains(' No content fetched for empty-fetch. Skipping.'),
|
|
174
|
+
);
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
test('dry run fetches content but skips Gemini and file writes', () async {
|
|
179
|
+
final inputData = [
|
|
180
|
+
{
|
|
181
|
+
'name': 'dry-update',
|
|
182
|
+
'description': 'Description',
|
|
183
|
+
'resources': ['https://example.com/source'],
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
inputYamlFile.writeAsStringSync(jsonEncode(inputData));
|
|
187
|
+
|
|
188
|
+
final skillDir = Directory(p.join(tempDir.path, 'dry-update'))
|
|
189
|
+
..createSync();
|
|
190
|
+
final skillFile = File(p.join(skillDir.path, 'SKILL.md'))
|
|
191
|
+
..writeAsStringSync('Existing content');
|
|
192
|
+
|
|
193
|
+
final logs = <String>[];
|
|
194
|
+
final sub = Logger.root.onRecord.listen((record) {
|
|
195
|
+
logs.add(record.message);
|
|
196
|
+
});
|
|
197
|
+
addTearDown(sub.cancel);
|
|
198
|
+
|
|
199
|
+
var geminiCalled = false;
|
|
200
|
+
final mockClient = MockClient((request) async {
|
|
201
|
+
if (request.url.toString() == 'https://example.com/source') {
|
|
202
|
+
return http.Response('<html>New Content</html>', 200);
|
|
203
|
+
}
|
|
204
|
+
if (request.url.toString().contains('generativelanguage')) {
|
|
205
|
+
geminiCalled = true;
|
|
206
|
+
return http.Response(
|
|
207
|
+
jsonEncode({
|
|
208
|
+
'candidates': [
|
|
209
|
+
{
|
|
210
|
+
'content': {
|
|
211
|
+
'parts': [
|
|
212
|
+
{'text': 'Gen'},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
}),
|
|
218
|
+
200,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return http.Response('Error', 500);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
final command = UpdateSkillCommand(
|
|
225
|
+
environment: {'GEMINI_API_KEY': 'test-key'},
|
|
226
|
+
httpClient: mockClient,
|
|
227
|
+
outputDir: tempDir,
|
|
228
|
+
);
|
|
229
|
+
runner.addCommand(command);
|
|
230
|
+
|
|
231
|
+
await runner.run(['update-skill', '--dry-run', inputYamlFile.path]);
|
|
232
|
+
|
|
233
|
+
expect(geminiCalled, isFalse);
|
|
234
|
+
expect(
|
|
235
|
+
logs,
|
|
236
|
+
contains(contains('[DRY RUN] Would update skill: dry-update')),
|
|
237
|
+
);
|
|
238
|
+
expect(skillFile.readAsStringSync(), equals('Existing content'));
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|