@kevinrabun/judges 3.113.0 → 3.114.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/README.md +9 -0
- package/agents/accessibility.judge.md +37 -0
- package/agents/agent-instructions.judge.md +37 -0
- package/agents/ai-code-safety.judge.md +48 -0
- package/agents/api-contract.judge.md +30 -0
- package/agents/api-design.judge.md +39 -0
- package/agents/authentication.judge.md +37 -0
- package/agents/backwards-compatibility.judge.md +37 -0
- package/agents/caching.judge.md +37 -0
- package/agents/ci-cd.judge.md +37 -0
- package/agents/cloud-readiness.judge.md +37 -0
- package/agents/code-structure.judge.md +48 -0
- package/agents/compliance.judge.md +40 -0
- package/agents/concurrency.judge.md +39 -0
- package/agents/configuration-management.judge.md +37 -0
- package/agents/cost-effectiveness.judge.md +40 -0
- package/agents/cybersecurity.judge.md +36 -0
- package/agents/data-security.judge.md +34 -0
- package/agents/data-sovereignty.judge.md +58 -0
- package/agents/database.judge.md +41 -0
- package/agents/dependency-health.judge.md +39 -0
- package/agents/documentation.judge.md +39 -0
- package/agents/error-handling.judge.md +37 -0
- package/agents/ethics-bias.judge.md +39 -0
- package/agents/false-positive-review.judge.md +73 -0
- package/agents/framework-safety.judge.md +40 -0
- package/agents/hallucination-detection.judge.md +33 -0
- package/agents/iac-security.judge.md +38 -0
- package/agents/intent-alignment.judge.md +31 -0
- package/agents/internationalization.judge.md +42 -0
- package/agents/logging-privacy.judge.md +37 -0
- package/agents/logic-review.judge.md +34 -0
- package/agents/maintainability.judge.md +37 -0
- package/agents/model-fingerprint.judge.md +31 -0
- package/agents/multi-turn-coherence.judge.md +29 -0
- package/agents/observability.judge.md +37 -0
- package/agents/over-engineering.judge.md +48 -0
- package/agents/performance.judge.md +44 -0
- package/agents/portability.judge.md +37 -0
- package/agents/rate-limiting.judge.md +37 -0
- package/agents/reliability.judge.md +39 -0
- package/agents/scalability.judge.md +41 -0
- package/agents/security.judge.md +31 -0
- package/agents/software-practices.judge.md +44 -0
- package/agents/testing.judge.md +39 -0
- package/agents/ux.judge.md +37 -0
- package/dist/api.d.ts +9 -1
- package/dist/api.js +9 -1
- package/dist/commands/fix.d.ts +10 -0
- package/dist/commands/fix.js +52 -0
- package/dist/commands/llm-benchmark.d.ts +13 -4
- package/dist/commands/llm-benchmark.js +39 -8
- package/dist/commands/review.d.ts +51 -1
- package/dist/commands/review.js +213 -7
- package/dist/evaluators/index.js +61 -35
- package/dist/github-app.d.ts +35 -0
- package/dist/github-app.js +125 -4
- package/dist/judges/index.d.ts +23 -61
- package/dist/judges/index.js +49 -63
- package/dist/patches/apply.d.ts +15 -0
- package/dist/patches/apply.js +37 -0
- package/dist/tools/prompts.d.ts +2 -2
- package/dist/tools/prompts.js +21 -10
- package/docs/skills.md +7 -0
- package/package.json +15 -3
- package/scripts/generate-agents-from-judges.ts +111 -0
- package/scripts/generate-skills-docs.ts +26 -0
- package/scripts/validate-agents.ts +104 -0
- package/server.json +2 -2
- package/skills/ai-code-review.skill.md +57 -0
- package/skills/release-gate.skill.md +27 -0
- package/skills/security-review.skill.md +32 -0
- package/src/agent-loader.ts +324 -0
- package/src/skill-loader.ts +199 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Loader — reads `.skill.md` files and converts them into skill
|
|
3
|
+
* definitions that orchestrate sets of judges/agents. A skill represents a
|
|
4
|
+
* reusable review workflow (e.g., AI code review, security gate, release gate).
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
7
|
+
import { join, resolve, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import type { JudgeDefinition, TribunalVerdict } from "./types.js";
|
|
10
|
+
import { evaluateWithTribunal } from "./evaluators/index.js";
|
|
11
|
+
import { defaultRegistry } from "./judge-registry.js";
|
|
12
|
+
import { loadAgentJudges } from "./judges/index.js";
|
|
13
|
+
|
|
14
|
+
export interface SkillFrontmatter {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
agents: string[];
|
|
19
|
+
tags?: string[];
|
|
20
|
+
priority?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ParsedSkill {
|
|
24
|
+
frontmatter: SkillFrontmatter;
|
|
25
|
+
body: string; // orchestrator instructions
|
|
26
|
+
sourcePath: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type SkillMeta = Record<string, unknown>;
|
|
30
|
+
|
|
31
|
+
export function parseSkillFrontmatter(raw: string): { meta: SkillMeta; body: string } {
|
|
32
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
33
|
+
if (!match) {
|
|
34
|
+
return { meta: {}, body: raw };
|
|
35
|
+
}
|
|
36
|
+
const yamlBlock = match[1];
|
|
37
|
+
const body = match[2].trim();
|
|
38
|
+
const meta: SkillMeta = {};
|
|
39
|
+
const lines = yamlBlock.split(/\r?\n/);
|
|
40
|
+
let i = 0;
|
|
41
|
+
while (i < lines.length) {
|
|
42
|
+
const line = lines[i];
|
|
43
|
+
if (!line.trim() || line.trim().startsWith("#")) {
|
|
44
|
+
i++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const kv = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)$/);
|
|
48
|
+
if (!kv) {
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const key = kv[1];
|
|
53
|
+
let value: unknown = kv[2].trim();
|
|
54
|
+
|
|
55
|
+
// Multi-line array (YAML list)
|
|
56
|
+
if (!value || value === "|") {
|
|
57
|
+
// Peek ahead for indented or dash-prefixed lines
|
|
58
|
+
const items: string[] = [];
|
|
59
|
+
i++;
|
|
60
|
+
while (i < lines.length) {
|
|
61
|
+
const next = lines[i];
|
|
62
|
+
if (!next.trim()) {
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (/^\s*-\s+/.test(next)) {
|
|
67
|
+
items.push(next.replace(/^\s*-\s+/, "").trim());
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (/^\s{2,}\S/.test(next)) {
|
|
72
|
+
items.push(next.trim());
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
break; // end of list
|
|
77
|
+
}
|
|
78
|
+
if (items.length > 0) {
|
|
79
|
+
meta[key] = items;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// fall through if no items captured
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof value === "string" && ((value.startsWith("[") && value.endsWith("]")) || value.includes(","))) {
|
|
86
|
+
// simple array parsing: split on comma
|
|
87
|
+
const normalized = (value as string)
|
|
88
|
+
.replace(/^\s*\[/, "")
|
|
89
|
+
.replace(/\]\s*$/, "")
|
|
90
|
+
.split(/\s*,\s*/)
|
|
91
|
+
.filter(Boolean);
|
|
92
|
+
value = normalized;
|
|
93
|
+
} else if (
|
|
94
|
+
typeof value === "string" &&
|
|
95
|
+
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
96
|
+
) {
|
|
97
|
+
value = (value as string).slice(1, -1);
|
|
98
|
+
}
|
|
99
|
+
meta[key] = value;
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
102
|
+
return { meta, body };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const REQUIRED_FIELDS: (keyof SkillFrontmatter)[] = ["id", "name", "description", "agents"];
|
|
106
|
+
|
|
107
|
+
export function validateSkillFrontmatter(meta: SkillMeta, sourcePath: string): SkillFrontmatter {
|
|
108
|
+
for (const field of REQUIRED_FIELDS) {
|
|
109
|
+
if (!meta[field] || (Array.isArray(meta[field]) && meta[field].length === 0)) {
|
|
110
|
+
throw new Error(`Skill file ${sourcePath} is missing required field: "${field}"`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
id: String(meta.id),
|
|
115
|
+
name: String(meta.name),
|
|
116
|
+
description: String(meta.description),
|
|
117
|
+
agents: Array.isArray(meta.agents)
|
|
118
|
+
? (meta.agents as string[])
|
|
119
|
+
: String(meta.agents ?? "")
|
|
120
|
+
.split(/\s*,\s*/)
|
|
121
|
+
.filter(Boolean),
|
|
122
|
+
tags: Array.isArray(meta.tags)
|
|
123
|
+
? (meta.tags as string[])
|
|
124
|
+
: meta.tags
|
|
125
|
+
? String(meta.tags)
|
|
126
|
+
.split(/\s*,\s*/)
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
: undefined,
|
|
129
|
+
priority: meta.priority ? Number(meta.priority) : 10,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function parseSkillFile(filePath: string): ParsedSkill {
|
|
134
|
+
const absPath = resolve(filePath);
|
|
135
|
+
const raw = readFileSync(absPath, "utf-8");
|
|
136
|
+
const { meta, body } = parseSkillFrontmatter(raw);
|
|
137
|
+
const frontmatter = validateSkillFrontmatter(meta, absPath);
|
|
138
|
+
return { frontmatter, body, sourcePath: absPath };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function loadSkillDirectory(dirPath: string): ParsedSkill[] {
|
|
142
|
+
const absDir = resolve(dirPath);
|
|
143
|
+
if (!existsSync(absDir)) return [];
|
|
144
|
+
return readdirSync(absDir)
|
|
145
|
+
.filter((f) => f.endsWith(".skill.md"))
|
|
146
|
+
.map((f) => parseSkillFile(join(absDir, f)))
|
|
147
|
+
.sort((a, b) => (a.frontmatter.priority ?? 10) - (b.frontmatter.priority ?? 10));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** List skills with metadata for display (id, name, description). */
|
|
151
|
+
export function listSkills(
|
|
152
|
+
dirPath: string,
|
|
153
|
+
): Array<Pick<SkillFrontmatter, "id" | "name" | "description" | "tags" | "agents">> {
|
|
154
|
+
return loadSkillDirectory(dirPath).map((s) => ({
|
|
155
|
+
id: s.frontmatter.id,
|
|
156
|
+
name: s.frontmatter.name,
|
|
157
|
+
description: s.frontmatter.description,
|
|
158
|
+
tags: s.frontmatter.tags,
|
|
159
|
+
agents: s.frontmatter.agents,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Run a skill by ID. Loads any missing agent judges, then evaluates code using
|
|
165
|
+
* only the judges referenced by the skill. Returns a tribunal verdict.
|
|
166
|
+
*/
|
|
167
|
+
export async function runSkill(
|
|
168
|
+
skillId: string,
|
|
169
|
+
code: string,
|
|
170
|
+
language: string,
|
|
171
|
+
opts?: { skillsDir?: string; context?: unknown },
|
|
172
|
+
): Promise<TribunalVerdict> {
|
|
173
|
+
const skillsDir = opts?.skillsDir ?? resolve(dirname(fileURLToPath(import.meta.url)), "..", "skills");
|
|
174
|
+
const skills = loadSkillDirectory(skillsDir);
|
|
175
|
+
const skill = skills.find((s) => s.frontmatter.id === skillId);
|
|
176
|
+
if (!skill) throw new Error(`Skill not found: ${skillId}`);
|
|
177
|
+
|
|
178
|
+
// Load agent judges referenced by the skill
|
|
179
|
+
loadAgentJudges();
|
|
180
|
+
|
|
181
|
+
const judges: JudgeDefinition[] = [];
|
|
182
|
+
for (const id of skill.frontmatter.agents) {
|
|
183
|
+
const judge = defaultRegistry.getJudge(id);
|
|
184
|
+
if (!judge) {
|
|
185
|
+
throw new Error(`Judge referenced by skill not found in registry: ${id}`);
|
|
186
|
+
}
|
|
187
|
+
judges.push(judge);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const allJudgeIds = defaultRegistry.getJudges().map((j) => j.id);
|
|
191
|
+
const enabled = new Set(skill.frontmatter.agents);
|
|
192
|
+
const disabled = allJudgeIds.filter((id) => !enabled.has(id));
|
|
193
|
+
|
|
194
|
+
return evaluateWithTribunal(code, language, `skill:${skill.frontmatter.id}`, {
|
|
195
|
+
config: {
|
|
196
|
+
disabledJudges: disabled,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|