diff --git a/skills.sh.json b/skills.sh.json index 4b0e943..bfe4e89 100644 --- a/skills.sh.json +++ b/skills.sh.json @@ -1,6 +1,6 @@ { - "$schema": "https://skills.sh/schemas/skills.sh.json", - "groups": [ + "$schema": "https://skills.sh/schemas/skills.sh.schema.json", + "groupings": [ { "title": "UI & Layout", "description": "Responsive Flutter layouts, constraint debugging, and app localization.", diff --git a/tool/generator/README.md b/tool/generator/README.md index d637f3e..ff3f022 100644 --- a/tool/generator/README.md +++ b/tool/generator/README.md @@ -144,8 +144,8 @@ The default configuration file is located at `resources/flutter_skills.yaml`. It When adding, renaming, or deleting a `flutter-*` skill in `skills/`: 1. **Update `README.md`**: Run `dart run skills update-readme` (or update the `Available Skills` table in the root `README.md`) so the table matches the `flutter-*` directories in `skills/`. -2. **Update `skills.sh.json`**: Add, rename, or remove the skill entry under `groups` in the root `skills.sh.json` file ([skills.sh documentation](https://www.skills.sh/docs/customize)). - * Only `flutter-*` skills belong in `groups` (synced `dart-*` skills are canonical in `dart-lang/skills`). +2. **Update `skills.sh.json`**: Add, rename, or remove the skill entry under `groupings` in the root `skills.sh.json` file ([skills.sh documentation](https://www.skills.sh/docs/customize)). + * Only `flutter-*` skills belong in `groupings` (synced `dart-*` skills are canonical in `dart-lang/skills`). * Because `skills.sh` retains historical skill slugs from install telemetry after a skill is renamed or deleted in Git, `"notGrouped": "bottom"` at the end of `skills.sh.json` automatically demotes any retired skill slugs and ungrouped `dart-*` copies into an `Other skills` section at the bottom of the page. 3. **Verify**: Run `dart test` in `tool/generator` (`test/lint_skills_test.dart` verifies that `skills.sh.json` and `README.md` match the `flutter-*` directories in `skills/`). diff --git a/tool/generator/test/lint_skills_test.dart b/tool/generator/test/lint_skills_test.dart index 6c29d08..fa426fa 100644 --- a/tool/generator/test/lint_skills_test.dart +++ b/tool/generator/test/lint_skills_test.dart @@ -11,7 +11,7 @@ import 'package:skills_lint/skills_lint.dart'; import 'package:test/test.dart'; const String _expectedSkillsShSchema = - 'https://skills.sh/schemas/skills.sh.json'; + 'https://skills.sh/schemas/skills.sh.schema.json'; const int _maxGroupTitleLength = 30; void _validateGroupTitle(Object? title) { @@ -80,11 +80,19 @@ void main() { jsonDecode(skillsShFile.readAsStringSync()) as Map; expect(skillsShJson[r'$schema'], _expectedSkillsShSchema); + // The skills.sh schema sets additionalProperties: false, so unknown keys + // (such as a misspelled "groups") make skills.sh ignore the file. + expect( + skillsShJson.keys, + everyElement(isIn([r'$schema', 'groupings', 'notGrouped'])), + ); + // Setting notGrouped to "bottom" instructs skills.sh to render any skill - // not listed in "groups" (including previously deleted/renamed skill slugs - // retained in skills.sh install telemetry and synced dart-* copies) under + // not listed in "groupings" (including previously deleted/renamed skill + // slugs retained in skills.sh install telemetry and synced dart-* copies) + // under // its built-in "Other skills" heading at the bottom of the repository page - // (the schema only accepts "top", "bottom", or "hidden", and does not + // (the schema only accepts "top" or "bottom", and does not // support a custom label). expect(skillsShJson['notGrouped'], 'bottom'); expect(skillsShJson.keys.last, 'notGrouped'); @@ -92,11 +100,15 @@ void main() { // 3. Validate each group's title, description, and unique skill // identifiers, and ensure that all grouped skills exist as flutter-* // directories in skills/ (without typos or missing skills). - final groups = (skillsShJson['groups'] as List) + final groups = (skillsShJson['groupings'] as List) .cast>(); expect(groups, isNotEmpty); for (final group in groups) { + expect( + group.keys, + everyElement(isIn(['title', 'description', 'skills'])), + ); _validateGroupTitle(group['title']); _validateGroupDescription(group['description']); }