chore: address Copilot review round 4
- verify:skills:cli — the authoritative validator is now a GUARANTEED step in `local:gate` and CI, not a best-effort hand-off. It resolves the CLI itself (an installed one at 2.1.233+, else a pinned `npx -y`), so both tiers run the same script and cannot drift. `verify:skills` keeps skipping when the CLI is absent, which is right for `validate`: fast, offline, no Claude Code required. - skill-manifest: the 1,536-char listing-entry cap covers `description` + `when_to_use` together; checking the description alone passed an over-cap entry. `when_to_use` is now parsed, validated and counted, with boundary tests at 1,536/1,537. - verify-skills: report the budget as *this repo's contribution*. The real listing budget is shared with bundled skills and the contributor's own ~/.claude/skills, none of which is visible from here, so the number was claiming more than it measured. - skill-eval: a negative case now means "no skill OF THIS REPO'S fired". It failed on any Skill invocation, so a contributor with an unrelated global skill got a false failure about their environment rather than these skills. - docs/architecture + local-dev: the Node backend runner gets its `@inspector/core` alias from `clients/web/tsup.runner.config.ts`, not from Vite. The old text sent runner work to the wrong config. - local-dev: the tarball does not ship "only each client's build/" — the allowlist also carries `clients/web/dist`, `clients/web/static` and the install helper. States the invariant that actually matters instead. - issue-triage: the test for needing triage is "no board card". Adding "and no milestone" would skip the approved-but-unboarded case the same section then handles. Also corrected the `$M` note: the lookup is `--state all`, so closed issues DO have entries — `isopen` is about the invariants, not the lookup. - issue-create: replaced "all five" with a per-version table. Steps 3 and 6 (milestone, Priority) do not exist for v1, so the old list opened with a rule the same skill later tells you to violate. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
7e0bb8b58d
commit
d05e76d470
1 changed files with 330 additions and 51 deletions
101
scripts/verify-skills-cli.mjs
Normal file
101
scripts/verify-skills-cli.mjs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env node
|
||||
// Runs `claude plugin validate` — the AUTHORITATIVE skill schema — as a
|
||||
// guaranteed step, resolving the CLI rather than hoping it is installed (#2163).
|
||||
//
|
||||
// `verify:skills` (inside `validate`) reimplements the parse and treats a
|
||||
// missing CLI as a skip, deliberately: `npm run validate` is the fast inner
|
||||
// loop, it must work offline, and a Node contributor should not need Claude Code
|
||||
// installed to run it. But "skips when absent" plus "usually absent" adds up to
|
||||
// "never runs", which made the acceptance criterion aspirational (Copilot).
|
||||
//
|
||||
// So the authoritative check gets its own step, in `local:gate` and in CI, and
|
||||
// it does NOT skip: an already-installed CLI new enough to have the subcommand
|
||||
// is used as-is, and otherwise a PINNED one is fetched with `npx -y`. Pinned
|
||||
// rather than @latest because a validator that moves on its own can start
|
||||
// failing a PR that changed nothing, which is how a gate loses its credibility.
|
||||
//
|
||||
// It needs no authentication — verified against a scrubbed environment and a
|
||||
// clean HOME.
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
compareVersions,
|
||||
parseClaudeVersion,
|
||||
PINNED_CLI_VERSION,
|
||||
PLUGIN_VALIDATE_MIN_VERSION,
|
||||
} from "./lib/skill-manifest.mjs";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const SKILLS_DIR = ".claude/skills";
|
||||
|
||||
/**
|
||||
* The argv that runs a usable validator: the local CLI when it is new enough,
|
||||
* otherwise the pinned one through `npx`.
|
||||
*
|
||||
* @param {{ version: number[] | null }} local
|
||||
* @returns {{ command: string, args: string[], via: string }}
|
||||
*/
|
||||
export function validatorCommand({ version }) {
|
||||
if (
|
||||
version !== null &&
|
||||
compareVersions(version, PLUGIN_VALIDATE_MIN_VERSION) >= 0
|
||||
) {
|
||||
return {
|
||||
command: "claude",
|
||||
args: ["plugin", "validate", SKILLS_DIR],
|
||||
via: `local claude ${version.join(".")}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
command: "npx",
|
||||
args: [
|
||||
"-y",
|
||||
`@anthropic-ai/claude-code@${PINNED_CLI_VERSION}`,
|
||||
"plugin",
|
||||
"validate",
|
||||
SKILLS_DIR,
|
||||
],
|
||||
via:
|
||||
version === null
|
||||
? `npx @anthropic-ai/claude-code@${PINNED_CLI_VERSION} (no local CLI)`
|
||||
: `npx @anthropic-ai/claude-code@${PINNED_CLI_VERSION} (local ${version.join(".")} predates the subcommand)`,
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const probe = spawnSync("claude", ["--version"], { encoding: "utf8" });
|
||||
const version =
|
||||
probe.error || probe.status !== 0
|
||||
? null
|
||||
: parseClaudeVersion(probe.stdout ?? "");
|
||||
|
||||
const { command, args, via } = validatorCommand({ version });
|
||||
console.log(`verify:skills:cli — validating ${SKILLS_DIR} via ${via}…`);
|
||||
|
||||
const res = spawnSync(command, args, {
|
||||
cwd: ROOT,
|
||||
stdio: "inherit",
|
||||
shell: process.platform === "win32",
|
||||
});
|
||||
if (res.error) {
|
||||
console.error(
|
||||
`\nverify:skills:cli — could not run \`${command}\`: ${res.error.message}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (res.status !== 0) {
|
||||
console.error(
|
||||
"\nverify:skills:cli — `claude plugin validate` rejected the skills.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
process.argv[1] &&
|
||||
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
) {
|
||||
main();
|
||||
}
|
||||
Loading…
Reference in a new issue