Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,114 +11,116 @@ Use this skill when you need to add a new validation rule to the `dart_skills_li

## 🛠️ Step-by-Step Implementation

### 1. Define the Rule in `lib/src/rules.dart`

To create a new rule, add a top-level `CheckType` instance.
### 1. Create the Rule Class
Create a new file in `lib/src/rules/` extending `SkillRule`.

```dart
// lib/src/rules.dart

/// Template instance for checking if the description has a trailing period.
final descriptionTrailingPeriodCheck = CheckType(
name: 'description-trailing-period',
defaultSeverity: AnalysisSeverity.error, // or warning/disabled
);
```
// lib/src/rules/my_new_rule.dart

### 2. Expose the CLI Flag in `lib/src/entry_point.dart`
import '../models/analysis_severity.dart';
import '../models/skill_context.dart';
import '../models/skill_rule.dart';
import '../models/validation_error.dart';

Modify `runApp` to include the toggleable flag for your new rule.

#### 📋 Register Parser Option
Add `.addFlag` in `runApp` (around line 60-90):

```dart
// lib/src/entry_point.dart
class MyNewRule extends SkillRule {
MyNewRule({super.severity});

parser
..addFlag(descriptionTrailingPeriodCheck.name,
negatable: true,
defaultsTo: true,
help: 'Check if the description ends with a period.');
```

#### 🎚️ Resolve Flag Logic or Override Severities
Find where severities are evaluated (around line 140-170) and bind your flag state:

```dart
if (results.wasParsed(descriptionTrailingPeriodCheck.name)) {
descriptionTrailingPeriodCheck.severity = (results[descriptionTrailingPeriodCheck.name] as bool)
? descriptionTrailingPeriodCheck.defaultSeverity
: AnalysisSeverity.disabled;
@override
Future<List<ValidationError>> validate(SkillContext context) async {
final errors = <ValidationError>[];
// Add validation logic here using context.rawContent or context.directory
return errors;
}
}
```

Add your rule to the `checkTypes` set if you want it loaded by default configuration overrides.
### 2. Register the Rule in `lib/src/rule_registry.dart`

### 3. Implement Validation in `lib/src/validator.dart`

Write the specific logic inside `Validator` checking the schema.
Add a new `CheckType` instance to `RuleRegistry.allChecks` list. This automatically exposes it as a CLI flag.

```dart
// lib/src/validator.dart

void _validateDescriptionPeriod(String description, List<ValidationError> validationErrors) {
if (description.isNotEmpty && !description.endsWith('.')) {
validationErrors.add(ValidationError(
ruleId: _getRule(descriptionTrailingPeriodCheck).name,
file: _skillFileName,
message: 'Description must end with a period.',
severity: _getRule(descriptionTrailingPeriodCheck).severity,
));
}
}
// lib/src/rule_registry.dart in allChecks list

const CheckType(
name: MyNewRule.ruleName,
defaultSeverity: MyNewRule.defaultSeverity,
help: 'Description of what the rule does for CLI help.',
),
```

Invoke your sub-routine inside `_parseMetadataFields`:
Then, add a case to `RuleRegistry.createRule` to instantiate your rule:

```dart
final description = yaml[_descriptionField]?.toString() ?? '';
if (description.isNotEmpty) {
_validateDescriptionPeriod(description, validationErrors);
}
// lib/src/rule_registry.dart in createRule method

static SkillRule? createRule(String name, AnalysisSeverity severity) {
switch (name) {
// ... other rules
case MyNewRule.ruleName:
return MyNewRule(severity: severity);
default:
return null;
}
}
```

### 3. Handle Disabled by Default Rules (If applicable)
If the rule is disabled by default (`defaultSeverity: AnalysisSeverity.disabled`), passing the flag `--check-my-new-rule` will automatically enable it with `AnalysisSeverity.error` severity (handled in `entry_point.dart`).

---

## 🧪 Testing the New Rule

You must write automated tests verifying your rule triggers when it should and skips when it shouldn't.

### Creating a Test Suite Case
Add matching suite files in `test/` (e.g., `test/field_constraints_test.dart` or `test/metadata_validation_test.dart`).
### Preferred Approach: In-Memory Unit Tests
Instead of writing files to disk, test the rule directly using a mock `SkillContext`. This is faster and avoids I/O dependencies.

```dart
// test/field_constraints_test.dart

test('triggers error when description does not end with period', () async {
final tempDir = createTempSkillDir(
name: 'test-skill',
description: 'This description does not end with a period',
);

final validator = Validator(rules: {descriptionTrailingPeriodCheck});
final result = await validator.validate(tempDir);

expect(result.validationErrors.any((e) => e.ruleId == 'description-trailing-period'), isTrue);
});
test('skips error when description ends with period', () async {
final tempDir = createTempSkillDir(
name: 'test-skill',
description: 'This description ends with a period.',
);

final validator = Validator(rules: {descriptionTrailingPeriodCheck});
final result = await validator.validate(tempDir);

expect(result.validationErrors.any((e) => e.ruleId == 'description-trailing-period'), isFalse);
});
// test/my_new_rule_test.dart

import 'dart:io';
import 'package:dart_skills_lint/src/models/analysis_severity.dart';
import 'package:dart_skills_lint/src/models/skill_context.dart';
import 'package:dart_skills_lint/src/models/validation_error.dart';
import 'package:dart_skills_lint/src/rules/my_new_rule.dart';
import 'package:test/test.dart';

void main() {
group('MyNewRule', () {
test('flags invalid content', () async {
final rule = MyNewRule(severity: AnalysisSeverity.warning);
final context = SkillContext(
directory: Directory('dummy'),
rawContent: 'Invalid content',
);

final List<ValidationError> errors = await rule.validate(context);

expect(errors, isNotEmpty);
expect(errors.first.message, contains('Expected error message'));
});

test('passes valid content', () async {
final rule = MyNewRule(severity: AnalysisSeverity.warning);
final context = SkillContext(
directory: Directory('dummy'),
rawContent: 'Valid content',
);

final List<ValidationError> errors = await rule.validate(context);

expect(errors, isEmpty);
});
});
}
```

### Integration Tests
If the rule interacts with CLI flags or configuration files, add a test in `test/cli_integration_test.dart` using `TestProcess`.
> [!IMPORTANT]
> When writing integration tests that use config files and `TestProcess`, ensure that paths in the config file and paths passed to the CLI match in style (both relative or both absolute) to avoid issues with path matching in `entry_point.dart`.

---

## 📚 Documentation Updates
Expand All @@ -127,20 +129,18 @@ When a new rule is introduced, verify that you synchronize sibling markdown file

1. **`README.md`:**
* Add your flag under the **Usage** and **Flags** sections so users know it exists.
* Add descriptive lines under **Specification Validation**.
2. **`documentation/knowledge/SPECIFICATION.md`:**
* If the rule implements standard specifications traits, add constraints parameters under Section 5.1 (Validation parameters).
* Document the formal constraint in the specification if it defines a standard for skill files.

---

## 🚦 Checklist Before Submitting PR

- [ ] Rule defined in `rules.dart`.
- [ ] Flag registered in `entry_point.dart`.
- [ ] Logic implemented in `validator.dart`.
- [ ] Toggle logic tests added in `test/`.
- [ ] Rule class created in `lib/src/rules/`.
- [ ] Rule registered in `lib/src/rule_registry.dart`.
- [ ] Unit tests added in `test/` using in-memory `SkillContext`.
- [ ] Usage listed in `README.md`.
- [ ] Schema documented in `documentation/knowledge/SPECIFICATION.md`.
- [ ] Run `dart analyze` to ensure no issues.
- [ ] Run `dart test` to ensure tests passing.
- [ ] Schema documented in `documentation/knowledge/SPECIFICATION.md` (if applicable).
- [ ] Run `dart format .` to format code.
- [ ] Run `dart analyze --fatal-infos` to ensure no issues.
- [ ] Run `dart test` to ensure tests passing.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ or identifiers that cannot be broken.

## Related Skills

- **[`dart-modern-features`](../dart-modern-features/SKILL.md)**: For idiomatic
- **[dart-modern-features]**: For idiomatic
usage of modern Dart features like Pattern Matching (useful for deep JSON
extraction), Records, and Switch Expressions.

[dart-modern-features]: https://github.com/kevmoo/dash_skills/blob/main/.agent/skills/dart-modern-features/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ Future<void> main(List<String> arguments) async {
}
```

### Cross-Platform Compatibility (Windows Support)
When writing CLI applications and tests, ensure compatibility with Windows:
- **Paths**: Never hardcode path separators like `/`. Use `package:path`'s
`p.join` or `p.normalize` to construct paths portably.
- **File Permissions**: When testing file permission errors, remember that
`chmod` is not available on Windows. Use `icacls` on Windows or appropriate
mock libraries. Never skip tests on Windows simply because of permission
commands if a Windows equivalent exists.

## 3. Recommended Packages

Use these community-standard packages owned by the [Dart team](https://dart.dev)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Use this skill when:

## Core Matchers

### 1. Collections (`hasLength`, `contains`, `isEmpty`)
### 1. Collections (`hasLength`, `contains`, `isEmpty`, `unorderedEquals`, `containsPair`)

- **`hasLength(n)`**:
- Prefer `expect(list, hasLength(n))` over `expect(list.length, n)`.
Expand All @@ -28,9 +28,16 @@ Use this skill when:

- **`contains(item)`**:
- Verify existence without manual iteration.
- Prefer over `expect(list.contains(item), true)`.

- **`unorderedEquals(items)`**:
- Verify contents regardless of order.
- Prefer over `expect(list, containsAll(items))`.

- **`containsPair(key, value)`**:
- Verify a map contains a specific key-value pair.
- Prefer over checking `expect(map[key], value)` or
`expect(map.containsKey(key), true)`.

### 2. Type Checks (`isA<T>` and `TypeMatcher<T>`)

Expand Down Expand Up @@ -99,8 +106,11 @@ expect(sideEffectState, equals('done')); // Race condition!

## Related Skills

- **[`dart-test-fundamentals`](../dart-test-fundamentals/SKILL.md)**: Core
- **[dart-test-fundamentals]**: Core
concepts for structuring tests, lifecycles, and configuration.
- **[`dart-checks-migration`](../dart-checks-migration/SKILL.md)**: Use this
- **[dart-checks-migration]**: Use this
skill if you are migrating tests from `package:matcher` to modern
`package:checks`.

[dart-test-fundamentals]: https://github.com/kevmoo/dash_skills/blob/main/.agent/skills/dart-test-fundamentals/SKILL.md
[dart-checks-migration]: https://github.com/kevmoo/dash_skills/blob/main/.agent/skills/dart-checks-migration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ LogLevel currentLevel = .info;

## Related Skills

- **[`dart-best-practices`](../dart-best-practices/SKILL.md)**: General code
- **[dart-best-practices]**: General code
style and foundational Dart idioms that predate or complement the modern
syntax features.

[dart-best-practices]: https://github.com/kevmoo/dash_skills/blob/main/.agent/skills/dart-best-practices/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,11 @@ timeouts:
`dart-test-fundamentals` is the core skill for structuring and configuring
tests. For writing assertions within those tests, refer to:

- **[`dart-matcher-best-practices`](../dart-matcher-best-practices/SKILL.md)**:
- **[dart-matcher-best-practices]**:
Use this if the project sticks with the traditional
`package:matcher` (`expect` calls).
- **[`dart-checks-migration`](../dart-checks-migration/SKILL.md)**: Use this
- **[dart-checks-migration]**: Use this
if the project is migrating to the modern `package:checks` (`check` calls).

[dart-matcher-best-practices]: https://github.com/kevmoo/dash_skills/blob/main/.agent/skills/dart-matcher-best-practices/SKILL.md
[dart-checks-migration]: https://github.com/kevmoo/dash_skills/blob/main/.agent/skills/dart-checks-migration/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"skills": {}
}
}
8 changes: 4 additions & 4 deletions tool/dart_skills_lint/.agents/skills/ignore.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"skills": {
"dart-matcher-best-practices": [
"dart-modern-features": [
{
"rule_id": "check-relative-paths",
"rule_id": "check-trailing-whitespace",
"file_name": "SKILL.md"
}
],
"dart-test-fundamentals": [
"dart-matcher-best-practices": [
{
"rule_id": "check-relative-paths",
"rule_id": "check-trailing-whitespace",
"file_name": "SKILL.md"
}
]
Expand Down
4 changes: 4 additions & 0 deletions tool/dart_skills_lint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ If no directory is specified, it automatically checks `.claude/skills` and `.age
- `-w`, `--print-warnings`: Enable printing of warning messages.
- `--fast-fail`: Halt execution immediately on the error.
- `--ignore-config`: Ignore the YAML configuration file entirely.
- `--[no-]check-trailing-whitespace`: Enable/disable checking for trailing whitespace. (Disabled by default).

### 2. As a Command Line Tool with a YAML Configuration File
You can configure the linter using a configuration file (defaulting to `dart_skills_lint.yaml` in the current directory).
Expand Down Expand Up @@ -190,6 +191,9 @@ The linter checks against the criteria defined in `documentation/knowledge/SPECI
- **Description (`description`)**: Max 1024 characters.
- **Compatibility (`compatibility`)**: Max 500 characters.

### 4. Content Constraints
- **Trailing Whitespace**: Lines in `SKILL.md` should not have trailing whitespace. Exactly 2 spaces at the end of a line are allowed to support Markdown hard line breaks, per the [CommonMark Spec](https://spec.commonmark.org/0.30/#hard-line-breaks).

## Contributing

Contributions are welcome! Please ensure that any PRs pass the linter themselves and align with the `documentation/knowledge/SPECIFICATION.md`.
Expand Down
1 change: 1 addition & 0 deletions tool/dart_skills_lint/dart_skills_lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ dart_skills_lint:
check-absolute-paths: error
directories:
- path: ".agents/skills"
check-trailing-whitespace: error
ignore_file: ".agents/skills/ignore.json"
- path: "../../skills"
ignore_file: ".agents/skills/flutter_skills_ignore.json"
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ Validation ensures that a skill directory and its `SKILL.md` file adhere to the
- **Compatibility (`compatibility`)**:
- Length: Maximum 500 characters.

#### 4.1.4 Content Constraints
- **Trailing Whitespace**: Lines in `SKILL.md` should not have trailing whitespace. Exactly 2 spaces at the end of a line are allowed to support Markdown hard line breaks, per the [CommonMark Spec](https://spec.commonmark.org/0.30/#hard-line-breaks).

### 5.2 Scripts & Tools
- Scripts in the `scripts/` directory should be self-documenting and provide clear error messages.

Expand Down
Loading
Loading