|
3 | 3 | // Exits with code 1 if any violations are found. |
4 | 4 | // |
5 | 5 | // Usage: |
6 | | -// tsx scripts/validate-skills.ts # validate all skills |
7 | | -// tsx scripts/validate-skills.ts apex-class # validate specific skill dirs |
| 6 | +// npm run validate:skills # validate all skills |
| 7 | +// npm run validate:skills -- --changed --base=origin/main # validate only skills changed vs base |
8 | 8 |
|
| 9 | +import { execSync } from "child_process" |
9 | 10 | import fs from "fs" |
10 | 11 | import path from "path" |
| 12 | +import { parseArgs } from "util" |
11 | 13 |
|
12 | 14 | const SKILLS_DIR = path.join(__dirname, "..", "skills") |
13 | 15 |
|
@@ -136,6 +138,23 @@ const CONTENT_CHECKS: ContentCheck[] = [ |
136 | 138 | }, |
137 | 139 | ] |
138 | 140 |
|
| 141 | +// --------------------------------------------------------------------------- |
| 142 | +// Changed-skills detection |
| 143 | +// --------------------------------------------------------------------------- |
| 144 | + |
| 145 | +function getChangedSkillDirs(base: string): string[] { |
| 146 | + const output = execSync(`git diff --name-only ${base}...HEAD`, { encoding: "utf8" }) |
| 147 | + return [ |
| 148 | + ...new Set( |
| 149 | + output |
| 150 | + .split("\n") |
| 151 | + .filter((f) => f.startsWith("skills/")) |
| 152 | + .map((f) => f.split("/")[1]) |
| 153 | + .filter(Boolean) |
| 154 | + ), |
| 155 | + ] |
| 156 | +} |
| 157 | + |
139 | 158 | // --------------------------------------------------------------------------- |
140 | 159 | // Helpers |
141 | 160 | // --------------------------------------------------------------------------- |
@@ -193,10 +212,28 @@ function validateSkill(dirName: string, dirPath: string): string[] { |
193 | 212 | } |
194 | 213 |
|
195 | 214 | function main(): void { |
196 | | - // If skill dir names are passed as arguments, validate only those. |
197 | | - // Otherwise validate all entries in skills/. |
198 | | - const targets = process.argv.slice(2) |
199 | | - const entries = targets.length > 0 ? targets : fs.readdirSync(SKILLS_DIR) |
| 215 | + const { values } = parseArgs({ |
| 216 | + args: process.argv.slice(2), |
| 217 | + options: { |
| 218 | + /** Validate only skill dirs touched in this branch vs the given base ref. */ |
| 219 | + changed: { type: "boolean", default: false }, |
| 220 | + /** Base ref for --changed (e.g. origin/main). Defaults to origin/HEAD. */ |
| 221 | + base: { type: "string", default: "origin/HEAD" }, |
| 222 | + }, |
| 223 | + }) |
| 224 | + |
| 225 | + let entries: string[] |
| 226 | + |
| 227 | + if (values.changed) { |
| 228 | + entries = getChangedSkillDirs(values.base!) |
| 229 | + if (entries.length === 0) { |
| 230 | + console.log("No skill directories changed — nothing to validate.") |
| 231 | + return |
| 232 | + } |
| 233 | + console.log(`Validating ${entries.length} changed skill(s): ${entries.join(", ")}`) |
| 234 | + } else { |
| 235 | + entries = fs.readdirSync(SKILLS_DIR) |
| 236 | + } |
200 | 237 |
|
201 | 238 | const allErrors: string[] = [] |
202 | 239 | let passed = 0 |
|
0 commit comments