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,559 @@
|
|
|
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:logging/logging.dart';
|
|
9
|
+
import 'package:meta/meta.dart';
|
|
10
|
+
import 'package:path/path.dart' as p;
|
|
11
|
+
|
|
12
|
+
import 'config_parser.dart';
|
|
13
|
+
import 'fixable_rule.dart';
|
|
14
|
+
import 'models/analysis_severity.dart';
|
|
15
|
+
import 'models/ignore_entry.dart';
|
|
16
|
+
import 'models/skill_context.dart';
|
|
17
|
+
import 'models/skill_rule.dart';
|
|
18
|
+
import 'models/skills_ignores.dart';
|
|
19
|
+
import 'models/validation_error.dart';
|
|
20
|
+
import 'skills_ignores_storage.dart';
|
|
21
|
+
import 'validator.dart';
|
|
22
|
+
|
|
23
|
+
final _log = Logger('dart_skills_lint');
|
|
24
|
+
|
|
25
|
+
/// Default filename for the per-run ignore baseline file.
|
|
26
|
+
///
|
|
27
|
+
/// Referenced both by production code (the `--generate-baseline` help text in
|
|
28
|
+
/// the CLI) and by tests, so this is intentionally not `@visibleForTesting`.
|
|
29
|
+
const defaultIgnoreFileName = 'dart_skills_lint_ignore.json';
|
|
30
|
+
|
|
31
|
+
@visibleForTesting
|
|
32
|
+
const skillIsValidMsg = ' Skill is valid.';
|
|
33
|
+
@visibleForTesting
|
|
34
|
+
const skillIsInvalidMsg = ' Skill is invalid:';
|
|
35
|
+
@visibleForTesting
|
|
36
|
+
const warningsMsg = 'Warnings:';
|
|
37
|
+
|
|
38
|
+
@visibleForTesting
|
|
39
|
+
const evaluatingDirMsg = 'Evaluating directory:';
|
|
40
|
+
|
|
41
|
+
@visibleForTesting
|
|
42
|
+
const directoryErrorMsg = 'Directory error:';
|
|
43
|
+
|
|
44
|
+
/// Per-invocation state and orchestration for skill validation.
|
|
45
|
+
///
|
|
46
|
+
/// One session is constructed per CLI invocation (or embedded call). The
|
|
47
|
+
/// caller invokes [processIndividualSkill] for each `--skill` path and
|
|
48
|
+
/// [processSkillRoot] for each `--skills-directory` path, then optionally
|
|
49
|
+
/// [reportNoSkillsValidated] to emit the "no skills found" diagnostics.
|
|
50
|
+
/// Failure state is exposed via [anyFailed] and [anySkillsValidated].
|
|
51
|
+
class ValidationSession {
|
|
52
|
+
ValidationSession({
|
|
53
|
+
required this.config,
|
|
54
|
+
required this.resolvedRules,
|
|
55
|
+
required this.ignoreFileOverride,
|
|
56
|
+
required this.customRules,
|
|
57
|
+
required this.printWarnings,
|
|
58
|
+
required this.fastFail,
|
|
59
|
+
required this.quiet,
|
|
60
|
+
required this.generateBaseline,
|
|
61
|
+
required this.fix,
|
|
62
|
+
required this.fixApply,
|
|
63
|
+
}) : _normalizedDirectoryConfigs = [
|
|
64
|
+
for (final dc in config.directoryConfigs)
|
|
65
|
+
(normalizedPath: p.normalize(dc.path), config: dc),
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
final Configuration config;
|
|
69
|
+
final Map<String, AnalysisSeverity> resolvedRules;
|
|
70
|
+
final String? ignoreFileOverride;
|
|
71
|
+
final List<SkillRule> customRules;
|
|
72
|
+
final bool printWarnings;
|
|
73
|
+
final bool fastFail;
|
|
74
|
+
final bool quiet;
|
|
75
|
+
final bool generateBaseline;
|
|
76
|
+
final bool fix;
|
|
77
|
+
final bool fixApply;
|
|
78
|
+
|
|
79
|
+
/// [config.directoryConfigs] with each `path` pre-normalized once.
|
|
80
|
+
///
|
|
81
|
+
/// `config` is static for the lifetime of a session, so we pay the
|
|
82
|
+
/// `p.normalize` cost up front instead of once per skill in
|
|
83
|
+
/// [_resolveRulesForPath] and [_resolveIgnoreFile].
|
|
84
|
+
final List<({String normalizedPath, DirectoryConfig config})> _normalizedDirectoryConfigs;
|
|
85
|
+
|
|
86
|
+
bool _anyFailed = false;
|
|
87
|
+
bool _anySkillsValidated = false;
|
|
88
|
+
|
|
89
|
+
bool get anyFailed => _anyFailed;
|
|
90
|
+
bool get anySkillsValidated => _anySkillsValidated;
|
|
91
|
+
|
|
92
|
+
/// Validates a single skill directory passed via `--skill` / `-s`.
|
|
93
|
+
///
|
|
94
|
+
/// Returns `true` if the caller should continue iterating, `false` to
|
|
95
|
+
/// stop. Only a real validation failure under [fastFail] returns `false`;
|
|
96
|
+
/// a missing directory contributes to [anyFailed] but still allows the
|
|
97
|
+
/// caller to continue.
|
|
98
|
+
Future<bool> processIndividualSkill(String skillPath) async {
|
|
99
|
+
final String normalizedSkillPath = p.normalize(_expandPath(skillPath));
|
|
100
|
+
if (!quiet) {
|
|
101
|
+
_log.info('$evaluatingDirMsg $normalizedSkillPath');
|
|
102
|
+
}
|
|
103
|
+
final skillDir = Directory(normalizedSkillPath);
|
|
104
|
+
|
|
105
|
+
if (!skillDir.existsSync()) {
|
|
106
|
+
_log.severe('Specified skill directory does not exist: $normalizedSkillPath');
|
|
107
|
+
_anyFailed = true;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
final Map<String, AnalysisSeverity> localRules = _resolveRulesForPath(normalizedSkillPath);
|
|
112
|
+
final String? localIgnoreFile = _resolveIgnoreFile(normalizedSkillPath);
|
|
113
|
+
final validator = Validator(ruleOverrides: localRules, customRules: customRules);
|
|
114
|
+
|
|
115
|
+
final ({SkillsIgnores ignores, String ignorePath}) loaded = await _loadIgnores(
|
|
116
|
+
localIgnoreFile,
|
|
117
|
+
skillDir.parent,
|
|
118
|
+
);
|
|
119
|
+
final SkillsIgnores ignores = loaded.ignores;
|
|
120
|
+
final String skillName = p.basename(skillDir.path);
|
|
121
|
+
final List<IgnoreEntry> skillIgnores = ignores.skills[skillName] ?? [];
|
|
122
|
+
|
|
123
|
+
_anySkillsValidated = true;
|
|
124
|
+
final ValidationResult finalResult = await _runValidationWorkflow(
|
|
125
|
+
skillDir: skillDir,
|
|
126
|
+
validator: validator,
|
|
127
|
+
ignores: ignores,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (generateBaseline) {
|
|
131
|
+
await _saveBaseline(loaded.ignorePath, ignores);
|
|
132
|
+
} else {
|
|
133
|
+
final String fullPath = p.absolute(skillDir.path);
|
|
134
|
+
for (final ignore in skillIgnores) {
|
|
135
|
+
if (!ignore.used) {
|
|
136
|
+
_log.info(
|
|
137
|
+
"Stale ignore entry found for rule '${ignore.ruleId}' in skill "
|
|
138
|
+
"'$skillName' at '$fullPath'. Consider removing it.",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!finalResult.isValid) {
|
|
145
|
+
_anyFailed = true;
|
|
146
|
+
if (fastFail) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Validates every skill directory under a root passed via
|
|
154
|
+
/// `--skills-directory` / `-d`.
|
|
155
|
+
///
|
|
156
|
+
/// Returns `true` if the caller should continue iterating, `false` to
|
|
157
|
+
/// stop. Missing-root and listing-failure errors contribute to [anyFailed]
|
|
158
|
+
/// but allow the caller to continue. After a successful iteration, returns
|
|
159
|
+
/// `false` if [fastFail] is set and any failure has accumulated across the
|
|
160
|
+
/// run so far.
|
|
161
|
+
Future<bool> processSkillRoot(String rootPath) async {
|
|
162
|
+
final String normalizedRootPath = p.normalize(_expandPath(rootPath));
|
|
163
|
+
if (!quiet) {
|
|
164
|
+
_log.info('$evaluatingDirMsg $normalizedRootPath');
|
|
165
|
+
}
|
|
166
|
+
final rootDir = Directory(normalizedRootPath);
|
|
167
|
+
|
|
168
|
+
if (!rootDir.existsSync()) {
|
|
169
|
+
_log.severe('Specified root directory does not exist: $normalizedRootPath');
|
|
170
|
+
_anyFailed = true;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
final Map<String, AnalysisSeverity> localRules = _resolveRulesForPath(normalizedRootPath);
|
|
175
|
+
final String? localIgnoreFile = _resolveIgnoreFile(normalizedRootPath);
|
|
176
|
+
final validator = Validator(ruleOverrides: localRules, customRules: customRules);
|
|
177
|
+
|
|
178
|
+
List<FileSystemEntity> entities;
|
|
179
|
+
try {
|
|
180
|
+
entities = await rootDir.list().toList();
|
|
181
|
+
} catch (_) {
|
|
182
|
+
_log.severe(' $directoryErrorMsg');
|
|
183
|
+
_log.severe(' - Failed to list children of: $normalizedRootPath');
|
|
184
|
+
_anyFailed = true;
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
entities.sort((a, b) => a.path.compareTo(b.path));
|
|
188
|
+
|
|
189
|
+
final ({SkillsIgnores ignores, String ignorePath}) loaded = await _loadIgnores(
|
|
190
|
+
localIgnoreFile,
|
|
191
|
+
rootDir,
|
|
192
|
+
);
|
|
193
|
+
final SkillsIgnores ignores = loaded.ignores;
|
|
194
|
+
|
|
195
|
+
for (final entity in entities) {
|
|
196
|
+
if (entity is! Directory) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (p.basename(entity.path).startsWith('.')) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_anySkillsValidated = true;
|
|
204
|
+
final ValidationResult finalResult = await _runValidationWorkflow(
|
|
205
|
+
skillDir: entity,
|
|
206
|
+
validator: validator,
|
|
207
|
+
ignores: ignores,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (!finalResult.isValid) {
|
|
211
|
+
_anyFailed = true;
|
|
212
|
+
if (fastFail) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (generateBaseline) {
|
|
219
|
+
await _saveBaseline(loaded.ignorePath, ignores);
|
|
220
|
+
} else {
|
|
221
|
+
for (final MapEntry<String, List<IgnoreEntry>> entry in ignores.skills.entries) {
|
|
222
|
+
final String skillName = entry.key;
|
|
223
|
+
for (final IgnoreEntry ignore in entry.value) {
|
|
224
|
+
if (!ignore.used) {
|
|
225
|
+
final String fullPath = p.absolute(p.join(rootDir.path, skillName));
|
|
226
|
+
_log.info(
|
|
227
|
+
"Stale ignore entry found for rule '${ignore.ruleId}' in skill "
|
|
228
|
+
"'$skillName' at '$fullPath'. Consider removing it.",
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return !(_anyFailed && fastFail);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// If no skills were validated across the whole run, emit appropriate
|
|
239
|
+
/// diagnostics and mark the session as failed.
|
|
240
|
+
void reportNoSkillsValidated(List<String> rootPaths) {
|
|
241
|
+
if (_anySkillsValidated) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
var foundSingleSkillPassedToD = false;
|
|
246
|
+
for (final rootPath in rootPaths) {
|
|
247
|
+
final String expandedRootPath = _expandPath(rootPath);
|
|
248
|
+
final skillMdFile = File(p.join(expandedRootPath, SkillContext.skillFileName));
|
|
249
|
+
if (skillMdFile.existsSync()) {
|
|
250
|
+
_log.severe(
|
|
251
|
+
'Directory "$expandedRootPath" appears to be an individual skill. '
|
|
252
|
+
'Use --skill / -s instead of -d / --skills-directory.',
|
|
253
|
+
);
|
|
254
|
+
foundSingleSkillPassedToD = true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (!foundSingleSkillPassedToD) {
|
|
258
|
+
_log.severe('No skills found to validate in the specified directories.');
|
|
259
|
+
}
|
|
260
|
+
_anyFailed = true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
Map<String, AnalysisSeverity> _resolveRulesForPath(String normalizedPath) {
|
|
264
|
+
final localRules = Map<String, AnalysisSeverity>.from(resolvedRules);
|
|
265
|
+
for (final ({String normalizedPath, DirectoryConfig config}) entry
|
|
266
|
+
in _normalizedDirectoryConfigs) {
|
|
267
|
+
if (normalizedPath.startsWith(entry.normalizedPath)) {
|
|
268
|
+
localRules.addAll(entry.config.rules);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return localRules;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
String? _resolveIgnoreFile(String normalizedPath) {
|
|
276
|
+
if (ignoreFileOverride != null) {
|
|
277
|
+
return ignoreFileOverride;
|
|
278
|
+
}
|
|
279
|
+
for (final ({String normalizedPath, DirectoryConfig config}) entry
|
|
280
|
+
in _normalizedDirectoryConfigs) {
|
|
281
|
+
if (normalizedPath.startsWith(entry.normalizedPath)) {
|
|
282
|
+
return entry.config.ignoreFile;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/// Loads the ignore JSON for a root, returning both the parsed
|
|
289
|
+
/// [SkillsIgnores] and the resolved on-disk path it came from (or where it
|
|
290
|
+
/// would be written).
|
|
291
|
+
///
|
|
292
|
+
/// Returning the [SkillsIgnores] object (not just `.skills`) lets callers
|
|
293
|
+
/// mutate it in memory across all skills in a root and then save it once,
|
|
294
|
+
/// instead of doing a load+save round-trip per skill.
|
|
295
|
+
Future<({SkillsIgnores ignores, String ignorePath})> _loadIgnores(
|
|
296
|
+
String? localIgnoreFile,
|
|
297
|
+
Directory rootDir,
|
|
298
|
+
) async {
|
|
299
|
+
final String ignorePath = localIgnoreFile != null
|
|
300
|
+
? p.normalize(_expandPath(localIgnoreFile))
|
|
301
|
+
: p.join(rootDir.path, defaultIgnoreFileName);
|
|
302
|
+
|
|
303
|
+
final file = File(ignorePath);
|
|
304
|
+
|
|
305
|
+
if (file.existsSync()) {
|
|
306
|
+
final storage = SkillsIgnoresStorage();
|
|
307
|
+
final SkillsIgnores ignores = await storage.load(ignorePath);
|
|
308
|
+
return (ignores: ignores, ignorePath: ignorePath);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// If a custom ignore file was specified but not found, create an empty one
|
|
312
|
+
// so the user can start adding ignores to it.
|
|
313
|
+
if (localIgnoreFile != null) {
|
|
314
|
+
_log.warning('File not found generating-baseline');
|
|
315
|
+
try {
|
|
316
|
+
await file.writeAsString(jsonEncode({SkillsIgnores.skillsKey: <String, dynamic>{}}));
|
|
317
|
+
} catch (_) {
|
|
318
|
+
// Ignore write errors, we will just return empty ignores.
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return (ignores: SkillsIgnores(skills: {}), ignorePath: ignorePath);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
void _applyIgnores(ValidationResult result, List<IgnoreEntry> ignores) {
|
|
326
|
+
// Pre-normalize ignore filenames once so the inner loop below is a
|
|
327
|
+
// straight string comparison instead of repeated path normalization.
|
|
328
|
+
final List<({IgnoreEntry entry, String normalizedFileName})> preNormalizedIgnores = [
|
|
329
|
+
for (final ignore in ignores)
|
|
330
|
+
(entry: ignore, normalizedFileName: p.normalize(ignore.fileName)),
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
for (final ValidationError error in result.validationErrors) {
|
|
334
|
+
if (error.isIgnored) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
final String normalizedErrorFile = p.normalize(error.file);
|
|
338
|
+
for (final pair in preNormalizedIgnores) {
|
|
339
|
+
final IgnoreEntry ignore = pair.entry;
|
|
340
|
+
if (ignore.ruleId == error.ruleId && pair.normalizedFileName == normalizedErrorFile) {
|
|
341
|
+
error.isIgnored = true;
|
|
342
|
+
ignore.used = true;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/// Validates [skillDir], applies fixes if requested, and (when
|
|
350
|
+
/// [generateBaseline] is set) updates [ignores] in memory with any new
|
|
351
|
+
/// baseline entries for this skill. The caller is responsible for
|
|
352
|
+
/// persisting [ignores] to disk once after all skills are processed —
|
|
353
|
+
/// see [_saveBaseline].
|
|
354
|
+
Future<ValidationResult> _runValidationWorkflow({
|
|
355
|
+
required Directory skillDir,
|
|
356
|
+
required Validator validator,
|
|
357
|
+
required SkillsIgnores ignores,
|
|
358
|
+
}) async {
|
|
359
|
+
final String skillName = p.basename(skillDir.path);
|
|
360
|
+
final List<IgnoreEntry> skillIgnores = ignores.skills[skillName] ?? [];
|
|
361
|
+
|
|
362
|
+
final ValidationResult result = await _validateSingleSkill(
|
|
363
|
+
skillDir: skillDir,
|
|
364
|
+
validator: validator,
|
|
365
|
+
skillIgnores: skillIgnores,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
final ValidationResult finalResult = await _applyFixesIfNeeded(
|
|
369
|
+
skillDir: skillDir,
|
|
370
|
+
result: result,
|
|
371
|
+
validator: validator,
|
|
372
|
+
skillIgnores: skillIgnores,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
if (generateBaseline) {
|
|
376
|
+
_updateBaselineForSkill(ignores, finalResult, skillName);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return finalResult;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
Future<ValidationResult> _validateSingleSkill({
|
|
383
|
+
required Directory skillDir,
|
|
384
|
+
required Validator validator,
|
|
385
|
+
required List<IgnoreEntry> skillIgnores,
|
|
386
|
+
}) async {
|
|
387
|
+
final String skillName = p.basename(skillDir.path);
|
|
388
|
+
if (!quiet) {
|
|
389
|
+
_log.info('--- Validating skill: $skillName ---');
|
|
390
|
+
}
|
|
391
|
+
final ValidationResult result = await validator.validate(skillDir);
|
|
392
|
+
_applyIgnores(result, skillIgnores);
|
|
393
|
+
_printValidationResult(result);
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
Future<ValidationResult> _applyFixesIfNeeded({
|
|
398
|
+
required Directory skillDir,
|
|
399
|
+
required ValidationResult result,
|
|
400
|
+
required Validator validator,
|
|
401
|
+
required List<IgnoreEntry> skillIgnores,
|
|
402
|
+
}) async {
|
|
403
|
+
if (!fix && !fixApply) {
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
final SkillContext? context = result.context;
|
|
408
|
+
if (context == null) {
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
final String skillName = p.basename(skillDir.path);
|
|
413
|
+
final skillMdFile = File(p.join(skillDir.path, SkillContext.skillFileName));
|
|
414
|
+
if (!skillMdFile.existsSync()) {
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
String currentContent = context.rawContent;
|
|
419
|
+
final originalContent = currentContent;
|
|
420
|
+
var modified = false;
|
|
421
|
+
|
|
422
|
+
for (final SkillRule rule in validator.rules) {
|
|
423
|
+
if (rule is FixableRule) {
|
|
424
|
+
final bool hasErrors = result.validationErrors.any(
|
|
425
|
+
(e) => e.ruleId == rule.name && !e.isIgnored,
|
|
426
|
+
);
|
|
427
|
+
if (hasErrors) {
|
|
428
|
+
try {
|
|
429
|
+
final String newContent = await rule.fix(
|
|
430
|
+
SkillContext.skillFileName,
|
|
431
|
+
currentContent,
|
|
432
|
+
context.directory,
|
|
433
|
+
);
|
|
434
|
+
if (newContent != currentContent) {
|
|
435
|
+
currentContent = newContent;
|
|
436
|
+
modified = true;
|
|
437
|
+
}
|
|
438
|
+
} catch (e) {
|
|
439
|
+
_log.severe(" Failed to apply fix for rule '${rule.name}': $e");
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (modified) {
|
|
446
|
+
if (fixApply) {
|
|
447
|
+
await skillMdFile.writeAsString(currentContent);
|
|
448
|
+
if (!quiet) {
|
|
449
|
+
_log.info(' Applied fixes for $skillName');
|
|
450
|
+
}
|
|
451
|
+
final ValidationResult newResult = await validator.validate(skillDir);
|
|
452
|
+
_applyIgnores(newResult, skillIgnores);
|
|
453
|
+
return newResult;
|
|
454
|
+
} else if (fix) {
|
|
455
|
+
if (!quiet) {
|
|
456
|
+
_log.info(' [Dry Run] Proposed changes for $skillName (SKILL.md):');
|
|
457
|
+
_printDiff(originalContent, currentContent);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return result;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/// Prints a simple line-by-line diff between [original] and [modified].
|
|
466
|
+
///
|
|
467
|
+
/// **Limitation**: This naive diff algorithm does not handle line additions
|
|
468
|
+
/// or removals well, as it compares lines at the same index. It is
|
|
469
|
+
/// sufficient for current fixers that only modify existing lines, but
|
|
470
|
+
/// should be replaced with a more robust diffing solution (e.g.,
|
|
471
|
+
/// `package:diff`) if future fixers add or remove lines.
|
|
472
|
+
void _printDiff(String original, String modified) {
|
|
473
|
+
final List<String> origLines = original.split('\n');
|
|
474
|
+
final List<String> modLines = modified.split('\n');
|
|
475
|
+
final int maxLines = origLines.length > modLines.length ? origLines.length : modLines.length;
|
|
476
|
+
for (var i = 0; i < maxLines; i++) {
|
|
477
|
+
final String orig = i < origLines.length ? origLines[i] : '';
|
|
478
|
+
final String mod = i < modLines.length ? modLines[i] : '';
|
|
479
|
+
if (orig != mod) {
|
|
480
|
+
if (orig.isNotEmpty) {
|
|
481
|
+
_log.info('- Line ${i + 1}: $orig');
|
|
482
|
+
}
|
|
483
|
+
if (mod.isNotEmpty) {
|
|
484
|
+
_log.info('+ Line ${i + 1}: $mod');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/// Mutates [ignores] in place to add baseline entries for any non-ignored
|
|
491
|
+
/// errors in [result] under the [skillName] key. Pure in-memory operation
|
|
492
|
+
/// — pair with [_saveBaseline] to persist changes.
|
|
493
|
+
void _updateBaselineForSkill(SkillsIgnores ignores, ValidationResult result, String skillName) {
|
|
494
|
+
final List<IgnoreEntry> currentSkillIgnores = ignores.skills[skillName] ?? [];
|
|
495
|
+
final currentSkillSeen = <String>{};
|
|
496
|
+
for (final ignore in currentSkillIgnores) {
|
|
497
|
+
currentSkillSeen.add('${ignore.ruleId}:${ignore.fileName}');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
for (final ValidationError error in result.validationErrors) {
|
|
501
|
+
if (!error.isIgnored) {
|
|
502
|
+
final key = '${error.ruleId}:${error.file}';
|
|
503
|
+
if (currentSkillSeen.contains(key)) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
currentSkillSeen.add(key);
|
|
507
|
+
|
|
508
|
+
currentSkillIgnores.add(IgnoreEntry(ruleId: error.ruleId, fileName: error.file));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (currentSkillIgnores.isNotEmpty) {
|
|
513
|
+
ignores.skills[skillName] = currentSkillIgnores;
|
|
514
|
+
} else {
|
|
515
|
+
ignores.skills.remove(skillName);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/// Writes [ignores] to [ignorePath]. Write failures are logged at warning
|
|
520
|
+
/// level and otherwise swallowed so a single I/O error does not abort the
|
|
521
|
+
/// rest of the run.
|
|
522
|
+
Future<void> _saveBaseline(String ignorePath, SkillsIgnores ignores) async {
|
|
523
|
+
try {
|
|
524
|
+
await SkillsIgnoresStorage().save(ignorePath, ignores);
|
|
525
|
+
} catch (e) {
|
|
526
|
+
_log.warning('Failed to generate baseline file at $ignorePath: $e');
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
void _printValidationResult(ValidationResult result) {
|
|
531
|
+
if (result.isValid) {
|
|
532
|
+
if (!quiet) {
|
|
533
|
+
_log.info(' $skillIsValidMsg');
|
|
534
|
+
}
|
|
535
|
+
} else {
|
|
536
|
+
_log.severe(' $skillIsInvalidMsg');
|
|
537
|
+
for (final String error in result.errors) {
|
|
538
|
+
_log.severe(' - $error');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (printWarnings && result.warnings.isNotEmpty) {
|
|
543
|
+
_log.warning(' $warningsMsg');
|
|
544
|
+
for (final String warning in result.warnings) {
|
|
545
|
+
_log.warning(' - $warning');
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
String _expandPath(String path) {
|
|
551
|
+
if (path.startsWith('~/')) {
|
|
552
|
+
final String? homeDir = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
|
|
553
|
+
if (homeDir != null) {
|
|
554
|
+
return p.join(homeDir, path.substring(2));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return path;
|
|
558
|
+
}
|
|
559
|
+
}
|