@uinaf/skillcheck 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/lint.js +21 -3
- package/dist/scenario.js +3 -2
- package/docs/scenarios.md +1 -0
- package/docs/usage.md +3 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { lintSkills } from "./lint.js";
|
|
3
2
|
import { encodeRunNamePart, generateRun, requiredEvalPackages, resolvePackageDir, runNameFor } from "./scenario.js";
|
|
3
|
+
import { lintSkills } from "./lint.js";
|
|
4
4
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import fs from "node:fs";
|
package/dist/lint.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FILE_BLOCK } from "./scenario.js";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
//#region src/lint.ts
|
|
@@ -61,19 +62,36 @@ function lintSkill(dir, root, errors) {
|
|
|
61
62
|
function lintSkills(root) {
|
|
62
63
|
const roots = ["skills", ...fs.globSync("cli/*/skills", { cwd: root })].map((r) => path.join(root, r));
|
|
63
64
|
const errors = [];
|
|
64
|
-
|
|
65
|
+
const skills = [];
|
|
65
66
|
for (const dir of roots) {
|
|
66
67
|
if (!fs.existsSync(dir)) continue;
|
|
67
68
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
68
69
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
69
70
|
lintSkill(path.join(dir, entry.name), root, errors);
|
|
70
|
-
|
|
71
|
+
skills.push(path.join(dir, entry.name));
|
|
71
72
|
}
|
|
72
73
|
}
|
|
74
|
+
lintTasks(skills, root, errors);
|
|
73
75
|
return {
|
|
74
76
|
errors,
|
|
75
|
-
count
|
|
77
|
+
count: skills.length
|
|
76
78
|
};
|
|
77
79
|
}
|
|
80
|
+
function lintTasks(skills, root, errors) {
|
|
81
|
+
const names = skills.map((dir) => path.basename(dir));
|
|
82
|
+
for (const dir of skills) {
|
|
83
|
+
const evals = path.join(dir, "evals");
|
|
84
|
+
if (!fs.existsSync(evals)) continue;
|
|
85
|
+
for (const scenario of fs.readdirSync(evals, { withFileTypes: true })) {
|
|
86
|
+
const file = path.join(evals, scenario.name, "task.md");
|
|
87
|
+
if (!scenario.isDirectory() || !fs.existsSync(file)) continue;
|
|
88
|
+
const text = fs.readFileSync(file, "utf8").replace(FILE_BLOCK, "");
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
91
|
+
if (new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i").test(text)) errors.push(`${path.relative(root, file)}: task names skill ${name}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
78
96
|
//#endregion
|
|
79
97
|
export { lintSkills };
|
package/dist/scenario.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
//#region src/scenario.ts
|
|
7
|
+
const FILE_BLOCK = /^=+ FILE: (.+?) =+\n([\s\S]*?)\n=+ END FILE =+$/gm;
|
|
7
8
|
const DEFAULT_CLAUDE_AGENT = "claude-opus-5";
|
|
8
9
|
function loadScenario(scenarioDir) {
|
|
9
10
|
const match = scenarioDir.match(/skills\/([^/]+)\/evals\/([^/]+)$/);
|
|
@@ -15,7 +16,7 @@ function loadScenario(scenarioDir) {
|
|
|
15
16
|
if (criteria.skill_use !== void 0 && criteria.skill_use !== "required" && criteria.skill_use !== "optional") throw new Error(`skill_use must be "required" or "optional" in ${scenarioDir}`);
|
|
16
17
|
for (const item of criteria.checklist) if (!(typeof item?.name === "string" && item.name.trim() !== "" && typeof item?.description === "string" && item.description.trim() !== "" && Number.isFinite(item?.max_score) && item.max_score > 0)) throw new Error(`invalid checklist item in ${scenarioDir}: ${JSON.stringify(item)}`);
|
|
17
18
|
const files = [];
|
|
18
|
-
const task = taskMd.replace(
|
|
19
|
+
const task = taskMd.replace(FILE_BLOCK, (_, name, content) => {
|
|
19
20
|
files.push({
|
|
20
21
|
name: name.trim(),
|
|
21
22
|
content: content + "\n"
|
|
@@ -322,4 +323,4 @@ function generateRun(scenarioDir, opts, paths) {
|
|
|
322
323
|
};
|
|
323
324
|
}
|
|
324
325
|
//#endregion
|
|
325
|
-
export { DEFAULT_CLAUDE_AGENT, buildConfig, encodeRunNamePart, generateRun, isHiddenSkill, loadScenario, materialize, privateCodexHome, requiredEvalPackages, resolvePackageDir, runNameFor, sdkNodeModulesDir, stripHiddenFlag, trialLabel };
|
|
326
|
+
export { DEFAULT_CLAUDE_AGENT, FILE_BLOCK, buildConfig, encodeRunNamePart, generateRun, isHiddenSkill, loadScenario, materialize, privateCodexHome, requiredEvalPackages, resolvePackageDir, runNameFor, sdkNodeModulesDir, stripHiddenFlag, trialLabel };
|
package/docs/scenarios.md
CHANGED
|
@@ -35,6 +35,7 @@ own examiner or embed generated dependencies.
|
|
|
35
35
|
|
|
36
36
|
Write the task the way a user would write it. Do not name the skill, describe
|
|
37
37
|
its steps, or hint at the checklist: routing is part of what is being measured.
|
|
38
|
+
`skillcheck lint` rejects a task whose prose names any skill in the root.
|
|
38
39
|
|
|
39
40
|
## criteria.json
|
|
40
41
|
|
package/docs/usage.md
CHANGED
|
@@ -20,6 +20,9 @@ Checks each `<root>/skills/<skill>/`:
|
|
|
20
20
|
- `disable-model-invocation`, when present, is the bare YAML boolean `true`.
|
|
21
21
|
A quoted `"true"` is an error
|
|
22
22
|
- Relative links in the body resolve on disk
|
|
23
|
+
- No eval `task.md` names a skill from the root, outside its inline input
|
|
24
|
+
files. A named skill gives routing away, and a no-skill control can read
|
|
25
|
+
the operator's installed copy
|
|
23
26
|
|
|
24
27
|
Code spans and fenced blocks are stripped before links are checked, so example
|
|
25
28
|
links never fail. External schemes and `#anchors` pass. Dot-directories under
|