Skip to content

Commit 6fa596f

Browse files
committed
feat(ng-dev): add ai skills validation command
Implements a new `ng-dev ai skills validate` command to validate `SKILL.md` files within the repository. The command enforces the Agent Skills specification by validating YAML frontmatter against a Zod schema, ensuring correct directory structure, and checking for required fields.
1 parent 31db864 commit 6fa596f

File tree

20 files changed

+376
-4
lines changed

20 files changed

+376
-4
lines changed

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ bazel/map-size-tracking/test/size-golden.json
2222

2323
**/test/goldens/**/*.md
2424
**/pnpm-lock.yaml
25+
26+
# AI Skills intentionally not formatted to ensure validation failures.
27+
ng-dev/ai/skills/fixtures/invalid-frontmatter-location/skills/test-skill/SKILL.md

ng-dev/ai/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ts_project(
1414
"//ng-dev:node_modules/cli-progress",
1515
"//ng-dev:node_modules/fast-glob",
1616
"//ng-dev:node_modules/yargs",
17+
"//ng-dev/ai/skills",
1718
"//ng-dev/utils",
1819
],
1920
)

ng-dev/ai/cli.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
import {Argv} from 'yargs';
99
import {MigrateModule} from './migrate.js';
1010
import {FixModule} from './fix.js';
11+
import {SkillsModule} from './skills/cli.js';
1112

1213
/** Build the parser for the AI commands. */
1314
export function buildAiParser(localYargs: Argv) {
14-
return localYargs.command(MigrateModule).command(FixModule);
15+
return localYargs.command(MigrateModule).command(FixModule).command(SkillsModule);
1516
}

ng-dev/ai/skills/BUILD.bazel

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
load("//tools:defaults.bzl", "jasmine_test", "ts_project")
2+
3+
ts_project(
4+
name = "skills",
5+
srcs = glob(
6+
["**/*.ts"],
7+
exclude = ["**/*.spec.ts"],
8+
),
9+
visibility = ["//ng-dev:__subpackages__"],
10+
deps = [
11+
"//ng-dev:node_modules/@types/node",
12+
"//ng-dev:node_modules/@types/yargs",
13+
"//ng-dev:node_modules/fast-glob",
14+
"//ng-dev:node_modules/yaml",
15+
"//ng-dev:node_modules/yargs",
16+
"//ng-dev:node_modules/zod",
17+
"//ng-dev/utils",
18+
],
19+
)
20+
21+
ts_project(
22+
name = "test_lib",
23+
testonly = True,
24+
srcs = ["validate.spec.ts"],
25+
deps = [
26+
":skills",
27+
"//ng-dev:node_modules/@types/jasmine",
28+
"//ng-dev:node_modules/@types/node",
29+
"//ng-dev/utils",
30+
"//ng-dev/utils/testing",
31+
],
32+
)
33+
34+
jasmine_test(
35+
name = "test",
36+
data = glob(["fixtures/**"]) + [
37+
":test_lib",
38+
],
39+
)

ng-dev/ai/skills/cli.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Arguments, Argv, CommandModule} from 'yargs';
10+
11+
import {validateSkills} from './validate.js';
12+
import {determineRepoBaseDirFromCwd} from '../../utils/repo-directory.js';
13+
14+
interface Options {
15+
baseDir: string;
16+
}
17+
18+
async function builder(yargs: Argv) {
19+
return yargs.option('base-dir' as 'baseDir', {
20+
type: 'string',
21+
default: determineRepoBaseDirFromCwd(),
22+
hidden: true,
23+
description: 'The base directory to look for skills in',
24+
});
25+
}
26+
27+
async function handler({baseDir}: Arguments<Options>) {
28+
process.exitCode = (await validateSkills(baseDir)).exitCode;
29+
}
30+
31+
/**
32+
* Validates all skills found in the `skills/` directory.
33+
*/
34+
export const SkillsModule: CommandModule<{}, Options> = {
35+
command: 'skills validate',
36+
describe: 'Validate agent skills in the repository',
37+
builder,
38+
handler,
39+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
name: complex-skill
3+
description: Valid description
4+
license: MIT
5+
compatibility: Any
6+
metadata:
7+
key: value
8+
allowed-tools: tool1
9+
---
10+
11+
Instruction
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!-- This skill is invalid because the frontmatter is not at the very beginning of the file -->
2+
---
3+
name: test-skill
4+
description: Valid description
5+
license: MIT
6+
---
7+
Instruction
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
name: InvalidName
3+
description: Valid description
4+
license: MIT
5+
---
6+
7+
Instruction
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
name: bad-schema
3+
description: Valid description
4+
---
5+
6+
Instruction
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Just text

0 commit comments

Comments
 (0)