mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-10-19 06:10:25 +08:00
Port the MCP Inspector's skills infrastructure for #4858 Part 3: - scripts/verify-skills.mjs, verify-skills-cli.mjs, skill-eval.mjs and scripts/lib/{skill-manifest,claude-cli,npm-scripts,win-shell-args}.mjs, with all their *.test.mjs files - root npm scripts verify:skills, verify:skills:cli, skills:eval and test:scripts; yaml as a root devDependency - docs/skill-authoring.md, with paths and provenance adapted - a root-guards job in typescript.yml running verify:skills and verify:skills:cli on every push and pull request - .claude/skills/ (kept by a .gitkeep until the first skill lands) - un-ignore scripts/lib/, which the Python `lib/` pattern swallowed Adapted to this repo: checkWiring reads typescript.yml, and carries two temporary allowances (validate -> verify:format-coverage, removed by #4864; local:gate -> verify:skills:cli, removed by #4871) that excuse a link only while its pending script is absent and fail as stale once it exists. The "no skills found" failure is kept, with a temporary EMPTY_SKILLS_BOOTSTRAP allowance that fails as stale once a skill exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
145 lines
4.9 KiB
JavaScript
145 lines
4.9 KiB
JavaScript
// Table-driven tests for the shared npm-script reachability helpers. Each case
|
|
// pins a rule the inspector#1799 review took a round to get right — the comment names it.
|
|
// Run via `npm run test:scripts` (node:test; the root has no vitest harness).
|
|
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
reachableScripts,
|
|
rootReachesScript,
|
|
rootRunsClientValidate,
|
|
tokenize,
|
|
} from "./npm-scripts.mjs";
|
|
|
|
test("tokenize: quoting (r17)", () => {
|
|
const cases = [
|
|
[
|
|
"tsc --noEmit -p tsconfig.json",
|
|
["tsc", "--noEmit", "-p", "tsconfig.json"],
|
|
],
|
|
[`tsc -p "tsconfig.test.json"`, ["tsc", "-p", "tsconfig.test.json"]],
|
|
[`tsc -p 'tsconfig.test.json'`, ["tsc", "-p", "tsconfig.test.json"]],
|
|
[
|
|
`prettier --check "core/**/*.ts"`,
|
|
["prettier", "--check", "core/**/*.ts"],
|
|
],
|
|
[" spaced out ", ["spaced", "out"]],
|
|
["", []],
|
|
];
|
|
for (const [input, expected] of cases)
|
|
assert.deepEqual(tokenize(input), expected, input);
|
|
});
|
|
|
|
test("reachableScripts: follows `npm run` refs and pre/post hooks (r10)", () => {
|
|
const scripts = {
|
|
validate: "npm run lint && npm run build",
|
|
lint: "eslint .",
|
|
build: "tsc",
|
|
prebuild: "echo pre", // implicit hook npm runs around `build`
|
|
postbuild: "echo post",
|
|
unrelated: "noop",
|
|
};
|
|
const reached = reachableScripts(scripts, "validate");
|
|
assert.ok(reached.has("validate"));
|
|
assert.ok(reached.has("lint"));
|
|
assert.ok(reached.has("build"));
|
|
assert.ok(reached.has("prebuild"), "pre<name> hook");
|
|
assert.ok(reached.has("postbuild"), "post<name> hook");
|
|
assert.ok(!reached.has("unrelated"));
|
|
});
|
|
|
|
test("reachableScripts: follows every npm spelling of `run` (inspector#2341)", () => {
|
|
// `run-script` is the canonical command; `run`, `rum` and `urn` are its
|
|
// aliases. A chain written in any of them reaches the same scripts, so a
|
|
// guard that only followed `npm run` would let `npm run-script validate:web`
|
|
// reintroduce what it exists to forbid (Copilot).
|
|
const scripts = {
|
|
gate: "npm run-script a && npm rum b && npm urn c && npm run d",
|
|
a: "noop",
|
|
b: "noop",
|
|
c: "noop",
|
|
d: "noop",
|
|
e: "noop",
|
|
};
|
|
const reached = reachableScripts(scripts, "gate");
|
|
for (const name of ["a", "b", "c", "d"])
|
|
assert.ok(reached.has(name), `npm spelling reaching ${name}`);
|
|
assert.ok(!reached.has("e"));
|
|
});
|
|
|
|
test("reachableScripts: a `prevalidate`-hosted typecheck is reachable (r10)", () => {
|
|
const scripts = {
|
|
validate: "npm run build && npm run test",
|
|
prevalidate: "npm run typecheck",
|
|
typecheck: "tsc --noEmit",
|
|
build: "tsc",
|
|
test: "vitest run",
|
|
};
|
|
assert.ok(reachableScripts(scripts, "validate").has("typecheck"));
|
|
});
|
|
|
|
test("rootRunsClientValidate: forms that count vs. don't", () => {
|
|
// `cmd` lives in `vt`, reached from `validate` via `npm run vt`.
|
|
const runs = (cmd) =>
|
|
rootRunsClientValidate({ validate: "npm run vt", vt: cmd }, "clients/tui");
|
|
// Counts:
|
|
assert.ok(runs("cd clients/tui && npm run validate"), "plain cd");
|
|
assert.ok(runs("cd ./clients/tui && npm run validate"), "leading ./ (r15)");
|
|
assert.ok(runs(`cd "clients/tui" && npm run validate`), "quoted dir (r17)");
|
|
assert.ok(
|
|
runs(`cd clients/tui && npm run "validate"`),
|
|
"quoted script name (r18)",
|
|
);
|
|
assert.ok(
|
|
runs("cd clients/tui && npm run build && npm run validate"),
|
|
"extra step",
|
|
);
|
|
assert.ok(runs("npm --prefix clients/tui run validate"), "--prefix (r19)");
|
|
// Does NOT count:
|
|
assert.ok(
|
|
!runs("cd clients/tui-next && npm run validate"),
|
|
"prefix-sibling must not match the shorter name (r16)",
|
|
);
|
|
assert.ok(
|
|
!runs("cd clients/tui && npm run validate:fast"),
|
|
"`run validate:fast` is a different script (r20)",
|
|
);
|
|
assert.ok(!runs("cd clients/tui && npm run build"), "no validate");
|
|
});
|
|
|
|
test("rootRunsClientValidate: only counts a reachable script (r5)", () => {
|
|
// A cd-validate call in a script nothing reachable from `validate` runs must
|
|
// NOT count — that's the whole reason the reachability restriction exists.
|
|
assert.ok(
|
|
!rootRunsClientValidate(
|
|
{
|
|
validate: "npm run something-else",
|
|
orphan: "cd clients/tui && npm run validate",
|
|
},
|
|
"clients/tui",
|
|
),
|
|
"orphan script isn't reachable from validate",
|
|
);
|
|
// Reached via one hop of indirection counts.
|
|
assert.ok(
|
|
rootRunsClientValidate(
|
|
{
|
|
validate: "npm run validate:tui",
|
|
"validate:tui": "cd clients/tui && npm run validate",
|
|
},
|
|
"clients/tui",
|
|
),
|
|
"reached via validate:tui",
|
|
);
|
|
});
|
|
|
|
test("rootReachesScript: sibling-guard vouching", () => {
|
|
const scripts = {
|
|
validate: "npm run verify:format-coverage && npm run validate:web",
|
|
"validate:web": "cd clients/web && npm run validate",
|
|
};
|
|
assert.ok(rootReachesScript(scripts, "verify:format-coverage"));
|
|
assert.ok(rootReachesScript(scripts, "validate:web"));
|
|
assert.ok(!rootReachesScript(scripts, "verify:typecheck-coverage"));
|
|
});
|