Add skill-lint hook for dart_skills_lint#133
Conversation
Adds an Antigravity hook that triggers on modifications to any SKILL.md
file and runs dart_skills_lint against each affected skill in a single
pass, surfacing lint errors via the standard JSON decision protocol.
Implementation notes:
- New SkillLintHook subclasses BaseGitHook. It pre-filters scoped files
by exact 'SKILL.md' basename and maps each match to its parent skill
directory before chunking. executeCommand invokes
`dart run --directory=<repo>/tool/dart_skills_lint bin/cli.dart -s ...`
with one `-s` per skill directory.
- BaseGitHook gains two additive extension points:
* a protected `repoRoot` field, set in `run()` before any subclass
hooks are called, so subclasses can resolve repo-relative paths;
* a protected `transformScopedFiles` virtual method (default
identity) called between scope filtering and chunking, so
directory-oriented hooks can rewrite the file list without
duplicating run() orchestration.
- Wired into .agents/hooks.json at all four existing layers (repo
root, tool/, tool/dart_hooks/, tool/dart_skills_lint/) using the
same relative-path scoping as dart-format and dart-analyze. Timeout
120s.
Tests: 5 unit tests for SkillLintHook (filter, dedup, success, failure,
ignored .md files) and 2 integration tests against a real temp git
repo (resolved-symlink repoRoot so the path matches `git rev-parse
--show-toplevel` on macOS). All existing BaseGitHook tests continue
to pass with the additive extension points.
There was a problem hiding this comment.
Code Review
This pull request introduces a new SkillLintHook designed to run dart_skills_lint on directories where SKILL.md files have been modified. To support this, the BaseGitHook class was updated with a repoRoot property and a transformScopedFiles method for customizing the file list before processing. Review feedback suggests removing the final modifier from repoRoot to avoid potential initialization errors, improving the chunking logic to account for argument overhead in subclasses, and considering case-insensitive matching for SKILL.md filenames.
BaseGitHook chunked by entry length plus a fixed +1 for the space separator. Subclasses that prepend per-entry flags (SkillLintHook adds '-s ' for every directory) undercounted by 3 characters per entry, which could push large chunks over the maxCharsPerChunk safety margin. Adds a protected 'perEntryArgOverhead' (default 0) that subclasses override to declare their flag overhead. SkillLintHook returns 3.
|
Addressing the gemini-code-assist review:
|
| /// Defaults to 0 (no extra overhead). Override to return the number of | ||
| /// additional characters per entry, including any flag and its separator. | ||
| @protected | ||
| int get perEntryArgOverhead => 0; |
There was a problem hiding this comment.
I dont think this is needed especaialy if it is set to zero. Lets find a real world case before adding this complexity.
| final List<String> transformedFiles = transformScopedFiles(scopedFiles); | ||
| if (transformedFiles.isEmpty) { | ||
| await logToFile('No files to process after transform.'); | ||
| printStdout(jsonEncode({'decision': 'stop'})); |
There was a problem hiding this comment.
Lets extract printStdout(jsonEncode({'decision': 'stop'})); to a method that we can call on base_git_hook.dart when there is no more work to do.
| // Add 1 for the space separator between arguments | ||
| final int fileLen = file.length + 1; | ||
| for (final file in transformedFiles) { | ||
| // Add 1 for the space separator between arguments, plus any |
There was a problem hiding this comment.
This will also go away after https://github.com/flutter/skills/pull/133/changes#r3229542020 is removed.
| static const String _lintBinRelativePath = 'bin/cli.dart'; | ||
|
|
||
| @override | ||
| List<String> get allowedExtensions => ['.md']; |
There was a problem hiding this comment.
where is this used?
| /// exactly `SKILL.md`, then runs the linter once with each skill's containing | ||
| /// directory passed as a `-s` argument. A single pass; the agent is | ||
| /// responsible for fixing reported errors. | ||
| class SkillLintHook extends BaseGitHook { |
There was a problem hiding this comment.
How does this hook interact with a packages dart_skills_lint.yaml especially its ignore.json ?
| } | ||
| } | ||
|
|
||
| await git(['init']); |
There was a problem hiding this comment.
I think there is already a helper to setup a bunch of these commands.
| // 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'; |
There was a problem hiding this comment.
This file looks like it has several places were we could reduce code duplication.
|
This comment was authored by an agent. Addressed your review comments in 5bcbc42:
|
5bcbc42 to
9db3a5e
Compare
# Conflicts: # tool/dart_hooks/lib/src/base_git_hook.dart
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request adds a skill-lint hook to validate SKILL.md files using the dart_skills_lint tool. It refactors BaseGitHook to support file transformations and includes new tests for executable bits and integration. Reviewers recommended using the --directory flag in SkillLintHook for better dependency resolution and adjusting the chunking logic in BaseGitHook to account for per-file flags added by subclasses.
This pr is the result of me playing with gastown. It still needs review from me.
Summary
SKILL.mdfile and runsdart_skills_lintagainst each affected skill directory in a single pass, surfacing lint errors via the standard JSON decision protocol.SkillLintHooksubclassesBaseGitHook. It pre-filters scoped files by exactSKILL.mdbasename and maps each match to its parent skill directory before chunking, then invokesdart run --directory=<repo>/tool/dart_skills_lint bin/cli.dart -s <dir1> -s <dir2> ...once per chunk.BaseGitHookgains two additive extension points so directory-oriented hooks don't have to duplicaterun()orchestration:repoRootfield set inrun()before any subclass hooks fire, for resolving repo-relative paths;transformScopedFilesvirtual method (default identity) called between scope filtering and chunking..agents/hooks.jsonat all four existing layers (repo root,tool/,tool/dart_hooks/,tool/dart_skills_lint/) using the same relative-path scoping asdart-formatanddart-analyze. Timeout 120s.Test plan
SkillLintHook(filter, dedup, success, failure, ignored non-SKILL.md files) and 2 integration tests against a real temp git repo. All pass.BaseGitHookunit tests pass unchanged — the new extension points are additive.dart analyzeclean acrosstool/dart_hooks.Note for reviewers
The new integration test calls
tempDir.resolveSymbolicLinksSync()before assigning torepoRoot, because on macOSDirectory.systemTempreturns/var/folders/...whilegit rev-parse --show-toplevelreturns/private/var/folders/..., and the scope filter is a string-prefix comparison. The existingagent_dart_analyze_integration_test.dartandagent_dart_format_integration_test.dartdon't apply this fix; a separate follow-up issue can address them.