Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/skills_tool.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ on:
pull_request:
paths:
Comment thread
kevmoo marked this conversation as resolved.
Outdated
- '.github/workflows/skills_tool.yaml'
- 'README.md'
- 'pubspec.yaml'
- 'skills.sh.json'
- 'skills/**'
- 'tool/generator/**'
push:
branches: [ main ]
paths:
Comment thread
kevmoo marked this conversation as resolved.
Outdated
- '.github/workflows/skills_tool.yaml'
- 'README.md'
- 'pubspec.yaml'
- 'skills.sh.json'
- 'skills/**'
- 'tool/generator/**'
schedule:
Expand Down
73 changes: 73 additions & 0 deletions skills.sh.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"$schema": "https://skills.sh/schemas/skills.sh.json",
"notGrouped": "bottom",
Comment thread
kevmoo marked this conversation as resolved.
Outdated
"groups": [
{
"title": "UI & Layout",
"description": "Responsive Flutter layouts, constraint debugging, and app localization.",
"skills": [
"flutter-build-responsive-layout",
"flutter-fix-layout-issues",
"flutter-setup-localization"
]
},
{
"title": "Architecture & Data",
"description": "Layered app architecture, declarative routing, HTTP networking, and JSON serialization.",
"skills": [
"flutter-apply-architecture-best-practices",
"flutter-setup-declarative-routing",
"flutter-use-http-package",
"flutter-implement-json-serialization"
]
},
{
"title": "Testing & Previews",
"description": "Widget tests, Flutter Driver integration tests, and interactive widget previews.",
"skills": [
"flutter-add-widget-test",
"flutter-add-integration-test",
"flutter-add-widget-preview"
]
},
{
"title": "Dart Language",
"description": "Modern Dart syntax, primary constructors, pattern matching, and API documentation.",
"skills": [
"dart-use-pattern-matching",
Comment thread
kevmoo marked this conversation as resolved.
Outdated
"dart-use-primary-constructors",
"dart-write-documentation",
"dart-use-doc-examples"
]
},
{
"title": "Dart Testing",
"description": "Unit test authoring, mock generation, code coverage, and package:checks migrations.",
"skills": [
"dart-add-unit-test",
"dart-generate-test-mocks",
"dart-collect-coverage",
"dart-migrate-to-checks-package"
]
},
{
"title": "Dart Analysis",
"description": "Static analysis, automated lint fixes, runtime error debugging, and dependency resolution.",
"skills": [
"dart-run-static-analysis",
"dart-fix-runtime-errors",
"dart-resolve-package-conflicts"
]
},
{
"title": "Dart Interop",
"description": "Command-line app architecture, cross-platform paths, FFI bindings, and native assets.",
"skills": [
"dart-build-cli-app",
"dart-use-path-package",
"dart-use-ffigen",
"dart-setup-ffi-assets"
]
}
]
}
73 changes: 73 additions & 0 deletions tool/generator/test/lint_skills_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';
import 'dart:io';

import 'package:logging/logging.dart';
Expand All @@ -10,6 +11,78 @@ import 'package:skills_lint/skills_lint.dart';
import 'package:test/test.dart';

void main() {
test('Validate skills.sh.json', () {
Comment thread
kevmoo marked this conversation as resolved.
Outdated
final currentPath = Directory.current.path;
final repoRoot = currentPath.endsWith(p.join('tool', 'generator'))
? Directory.current.parent.parent
: Directory.current;
final skillsDir = Directory(p.join(repoRoot.path, 'skills'));
expect(skillsDir.existsSync(), isTrue);

final actualSkills = skillsDir
.listSync()
.whereType<Directory>()
.map((d) => p.basename(d.path))
.where((name) => !name.startsWith('.'))
.toSet();
expect(actualSkills, isNotEmpty);

final skillsShFile = File(p.join(repoRoot.path, 'skills.sh.json'));
expect(skillsShFile.existsSync(), isTrue);

final skillsShJson =
jsonDecode(skillsShFile.readAsStringSync()) as Map<String, Object?>;
expect(
skillsShJson[r'$schema'],
'https://skills.sh/schemas/skills.sh.json',
Comment thread
kevmoo marked this conversation as resolved.
Outdated
);
expect(skillsShJson['notGrouped'], 'bottom');

final groups = (skillsShJson['groups'] as List<Object?>)
.cast<Map<String, Object?>>();
expect(groups, isNotEmpty);

final groupedSkills = <String>{};
for (final group in groups) {
final title = group['title'];
expect(
title,
isA<String>().having(
(s) =>
s.trim().isNotEmpty && s.trim().split(RegExp(r'\s+')).length <= 3,
'1-3 words',
isTrue,
),
);
expect(
group['description'],
isA<String>().having((s) => s.isNotEmpty, 'non-empty', isTrue),
);
final skills = (group['skills'] as List<Object?>).cast<String>();
expect(skills, isNotEmpty);
for (final skill in skills) {
expect(
groupedSkills.add(skill),
isTrue,
reason: 'Duplicate skill "$skill" in skills.sh.json',
);
}
}

expect(groupedSkills, equals(actualSkills));
Comment thread
kevmoo marked this conversation as resolved.
Outdated

final readmeFile = File(p.join(repoRoot.path, 'README.md'));
expect(readmeFile.existsSync(), isTrue);
final readmeMatches = RegExp(
r'\| \[([^\]]+)\]\(skills/\1/SKILL\.md\) \|',
).allMatches(readmeFile.readAsStringSync());
final readmeSkills = readmeMatches.map((m) => m.group(1)!).toSet();
final actualFlutterSkills = actualSkills
.where((s) => s.startsWith('flutter-'))
.toSet();
expect(readmeSkills, equals(actualFlutterSkills));
});

test('Run skills linter', () async {
final lintErrors = <String>[];
Logger.root.level = Level.ALL;
Expand Down
Loading