@dzhechkov/harness-core 0.3.3 → 0.3.5
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/benchmark.d.ts +54 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +132 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +33 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +84 -0
- package/dist/registry.js.map +1 -0
- package/package.json +4 -4
- package/src/benchmark.ts +196 -0
- package/src/index.ts +4 -0
- package/src/registry.ts +109 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill benchmarking — Layer 0 deterministic checks + scoring.
|
|
3
|
+
*
|
|
4
|
+
* Runs structural validation checks against a skill directory,
|
|
5
|
+
* producing a scored report. Checks are based on BTO Layer 0
|
|
6
|
+
* (universal U1-U5 + skill-specific S1-S10).
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
/** A single benchmark check result. */
|
|
11
|
+
export interface BenchmarkCheck {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly passed: boolean;
|
|
15
|
+
readonly detail?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Score for a single skill benchmark. */
|
|
18
|
+
export interface BenchmarkScore {
|
|
19
|
+
readonly skillId: string;
|
|
20
|
+
readonly skillDir: string;
|
|
21
|
+
readonly checks: readonly BenchmarkCheck[];
|
|
22
|
+
readonly passed: number;
|
|
23
|
+
readonly total: number;
|
|
24
|
+
readonly passRate: number;
|
|
25
|
+
readonly grade: 'A' | 'B' | 'C' | 'D' | 'F';
|
|
26
|
+
}
|
|
27
|
+
/** Result of benchmarking one or more skills. */
|
|
28
|
+
export interface BenchmarkReport {
|
|
29
|
+
readonly skills: readonly BenchmarkScore[];
|
|
30
|
+
readonly totalPassed: number;
|
|
31
|
+
readonly totalChecks: number;
|
|
32
|
+
readonly overallPassRate: number;
|
|
33
|
+
}
|
|
34
|
+
/** Result of A/B comparison between two skills. */
|
|
35
|
+
export interface CompareResult {
|
|
36
|
+
readonly skillA: BenchmarkScore;
|
|
37
|
+
readonly skillB: BenchmarkScore;
|
|
38
|
+
readonly winner: string;
|
|
39
|
+
readonly deltaChecks: readonly {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly aPass: boolean;
|
|
42
|
+
readonly bPass: boolean;
|
|
43
|
+
}[];
|
|
44
|
+
}
|
|
45
|
+
/** Run Layer 0 benchmark checks on a single skill directory. */
|
|
46
|
+
export declare function benchmarkSkill(skillDir: string, skillId: string): BenchmarkScore;
|
|
47
|
+
/** Benchmark multiple skills. */
|
|
48
|
+
export declare function benchmarkSkills(skillDirs: {
|
|
49
|
+
id: string;
|
|
50
|
+
dir: string;
|
|
51
|
+
}[]): BenchmarkReport;
|
|
52
|
+
/** Compare two skills head-to-head. */
|
|
53
|
+
export declare function compareSkills(skillADir: string, skillAId: string, skillBDir: string, skillBId: string): CompareResult;
|
|
54
|
+
//# sourceMappingURL=benchmark.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"benchmark.d.ts","sourceRoot":"","sources":["../src/benchmark.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;CAC7C;AAED,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,SAAS;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAC5G;AAUD,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAsGhF;AAED,iCAAiC;AACjC,wBAAgB,eAAe,CAAC,SAAS,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,eAAe,CAUzF;AAED,uCAAuC;AACvC,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EACnC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAClC,aAAa,CAiBf"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill benchmarking — Layer 0 deterministic checks + scoring.
|
|
3
|
+
*
|
|
4
|
+
* Runs structural validation checks against a skill directory,
|
|
5
|
+
* producing a scored report. Checks are based on BTO Layer 0
|
|
6
|
+
* (universal U1-U5 + skill-specific S1-S10).
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
function gradeFromRate(rate) {
|
|
13
|
+
if (rate >= 90)
|
|
14
|
+
return 'A';
|
|
15
|
+
if (rate >= 80)
|
|
16
|
+
return 'B';
|
|
17
|
+
if (rate >= 70)
|
|
18
|
+
return 'C';
|
|
19
|
+
if (rate >= 60)
|
|
20
|
+
return 'D';
|
|
21
|
+
return 'F';
|
|
22
|
+
}
|
|
23
|
+
/** Run Layer 0 benchmark checks on a single skill directory. */
|
|
24
|
+
export function benchmarkSkill(skillDir, skillId) {
|
|
25
|
+
const checks = [];
|
|
26
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
27
|
+
// U1: SKILL.md exists
|
|
28
|
+
const skillMdExists = existsSync(skillMdPath);
|
|
29
|
+
checks.push({ id: 'U1', name: 'SKILL.md exists', passed: skillMdExists });
|
|
30
|
+
if (!skillMdExists) {
|
|
31
|
+
return {
|
|
32
|
+
skillId, skillDir, checks, passed: 0, total: checks.length,
|
|
33
|
+
passRate: 0, grade: 'F',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
37
|
+
const stat = statSync(skillMdPath);
|
|
38
|
+
// U2: Valid UTF-8 (if we can read it, it's valid)
|
|
39
|
+
checks.push({ id: 'U2', name: 'valid UTF-8', passed: true });
|
|
40
|
+
// U3: Has headings
|
|
41
|
+
const hasHeadings = /^#{1,6}\s/m.test(content);
|
|
42
|
+
checks.push({ id: 'U3', name: 'has headings', passed: hasHeadings });
|
|
43
|
+
// U4: No excessive blanks (>3 consecutive blank lines)
|
|
44
|
+
const excessiveBlanks = /\n{5,}/.test(content);
|
|
45
|
+
checks.push({ id: 'U4', name: 'no excessive blanks', passed: !excessiveBlanks });
|
|
46
|
+
// U5: Size bounds (100B - 50KB)
|
|
47
|
+
const sizeOk = stat.size >= 100 && stat.size <= 50000;
|
|
48
|
+
checks.push({
|
|
49
|
+
id: 'U5', name: 'size bounds (100B-50KB)', passed: sizeOk,
|
|
50
|
+
detail: `${stat.size} bytes`,
|
|
51
|
+
});
|
|
52
|
+
// S1: Frontmatter present
|
|
53
|
+
const hasFrontmatter = content.startsWith('---');
|
|
54
|
+
checks.push({ id: 'S1', name: 'frontmatter present', passed: hasFrontmatter });
|
|
55
|
+
// S2: name field
|
|
56
|
+
const hasName = /^name:\s/m.test(content);
|
|
57
|
+
checks.push({ id: 'S2', name: 'name field', passed: hasName });
|
|
58
|
+
// S3: description field
|
|
59
|
+
const hasDescription = /^description:\s/m.test(content);
|
|
60
|
+
checks.push({ id: 'S3', name: 'description field', passed: hasDescription });
|
|
61
|
+
// S4: trust_tier field
|
|
62
|
+
const hasTrustTier = /^trust_tier:\s/m.test(content);
|
|
63
|
+
checks.push({ id: 'S4', name: 'trust_tier field', passed: hasTrustTier });
|
|
64
|
+
// S5: Protocol/procedure section
|
|
65
|
+
const hasProtocol = /^##\s+(Protocol|Procedure|Steps|When to use)/mi.test(content);
|
|
66
|
+
checks.push({ id: 'S5', name: 'protocol/procedure section', passed: hasProtocol });
|
|
67
|
+
// S6: Output section or schema reference
|
|
68
|
+
const hasOutput = /^##\s+Output/mi.test(content) || /schema_path/m.test(content);
|
|
69
|
+
checks.push({ id: 'S6', name: 'output section or schema ref', passed: hasOutput });
|
|
70
|
+
// S7: schemas/output.json exists
|
|
71
|
+
const hasSchema = existsSync(join(skillDir, 'schemas', 'output.json'));
|
|
72
|
+
checks.push({ id: 'S7', name: 'schemas/output.json exists', passed: hasSchema });
|
|
73
|
+
// S8: scripts/validate-config.json exists
|
|
74
|
+
const hasValidator = existsSync(join(skillDir, 'scripts', 'validate-config.json'));
|
|
75
|
+
checks.push({ id: 'S8', name: 'scripts/validate-config.json exists', passed: hasValidator });
|
|
76
|
+
// S9: When to use section
|
|
77
|
+
const hasWhenToUse = /^##\s+(When to use|When To Activate|Use this skill when)/mi.test(content);
|
|
78
|
+
checks.push({ id: 'S9', name: 'when-to-use section', passed: hasWhenToUse });
|
|
79
|
+
// S10: No TODO/PLACEHOLDER markers
|
|
80
|
+
const hasTodos = /\[TODO\]|\[PLACEHOLDER\]|\[TBD\]/i.test(content);
|
|
81
|
+
checks.push({ id: 'S10', name: 'no TODO/PLACEHOLDER markers', passed: !hasTodos });
|
|
82
|
+
// S11: Evals exist
|
|
83
|
+
const hasEvals = existsSync(join(skillDir, 'evals'));
|
|
84
|
+
checks.push({ id: 'S11', name: 'evals directory exists', passed: hasEvals });
|
|
85
|
+
// S12: Content depth (>20 lines)
|
|
86
|
+
const lineCount = content.split('\n').length;
|
|
87
|
+
checks.push({
|
|
88
|
+
id: 'S12', name: 'content depth (>20 lines)', passed: lineCount > 20,
|
|
89
|
+
detail: `${lineCount} lines`,
|
|
90
|
+
});
|
|
91
|
+
// S13: Examples section
|
|
92
|
+
const hasExamples = /^##\s+(Examples|Example|Quick Start)/mi.test(content);
|
|
93
|
+
checks.push({ id: 'S13', name: 'examples section', passed: hasExamples });
|
|
94
|
+
// S14: Anti-patterns or self-check section
|
|
95
|
+
const hasAntiPatterns = /^##\s+(Anti.?[Pp]atterns|Self.?check|Self-check)/mi.test(content);
|
|
96
|
+
checks.push({ id: 'S14', name: 'anti-patterns or self-check', passed: hasAntiPatterns });
|
|
97
|
+
const passed = checks.filter((c) => c.passed).length;
|
|
98
|
+
const passRate = Math.round((passed / checks.length) * 100);
|
|
99
|
+
return {
|
|
100
|
+
skillId, skillDir, checks, passed, total: checks.length,
|
|
101
|
+
passRate, grade: gradeFromRate(passRate),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Benchmark multiple skills. */
|
|
105
|
+
export function benchmarkSkills(skillDirs) {
|
|
106
|
+
const skills = skillDirs.map((s) => benchmarkSkill(s.dir, s.id));
|
|
107
|
+
const totalPassed = skills.reduce((sum, s) => sum + s.passed, 0);
|
|
108
|
+
const totalChecks = skills.reduce((sum, s) => sum + s.total, 0);
|
|
109
|
+
return {
|
|
110
|
+
skills,
|
|
111
|
+
totalPassed,
|
|
112
|
+
totalChecks,
|
|
113
|
+
overallPassRate: totalChecks > 0 ? Math.round((totalPassed / totalChecks) * 100) : 0,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Compare two skills head-to-head. */
|
|
117
|
+
export function compareSkills(skillADir, skillAId, skillBDir, skillBId) {
|
|
118
|
+
const skillA = benchmarkSkill(skillADir, skillAId);
|
|
119
|
+
const skillB = benchmarkSkill(skillBDir, skillBId);
|
|
120
|
+
const deltaChecks = [];
|
|
121
|
+
for (const checkA of skillA.checks) {
|
|
122
|
+
const checkB = skillB.checks.find((c) => c.id === checkA.id);
|
|
123
|
+
if (checkB && checkA.passed !== checkB.passed) {
|
|
124
|
+
deltaChecks.push({ id: checkA.id, aPass: checkA.passed, bPass: checkB.passed });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const winner = skillA.passRate > skillB.passRate ? skillAId
|
|
128
|
+
: skillB.passRate > skillA.passRate ? skillBId
|
|
129
|
+
: 'tie';
|
|
130
|
+
return { skillA, skillB, winner, deltaChecks };
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=benchmark.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"benchmark.js","sourceRoot":"","sources":["../src/benchmark.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,4 +14,8 @@ export { createSkill } from './create-skill.js';
|
|
|
14
14
|
export type { CreateSkillOptions, CreateSkillResult } from './create-skill.js';
|
|
15
15
|
export { checkUpstream, loadSourcesManifest } from './sync-upstream.js';
|
|
16
16
|
export type { SyncUpstreamReport, UpstreamCheckResult, SourcesManifest } from './sync-upstream.js';
|
|
17
|
+
export { benchmarkSkill, benchmarkSkills, compareSkills } from './benchmark.js';
|
|
18
|
+
export { buildRegistry, searchRegistry, filterByCategory } from './registry.js';
|
|
19
|
+
export type { RegistryEntry, Registry } from './registry.js';
|
|
20
|
+
export type { BenchmarkCheck, BenchmarkScore, BenchmarkReport, CompareResult } from './benchmark.js';
|
|
17
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yDAAyD;AACzD,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACxE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yDAAyD;AACzD,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACxE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnG,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAChF,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -12,4 +12,6 @@ export * from './operations.js';
|
|
|
12
12
|
export * from './workflows.js';
|
|
13
13
|
export { createSkill } from './create-skill.js';
|
|
14
14
|
export { checkUpstream, loadSourcesManifest } from './sync-upstream.js';
|
|
15
|
+
export { benchmarkSkill, benchmarkSkills, compareSkills } from './benchmark.js';
|
|
16
|
+
export { buildRegistry, searchRegistry, filterByCategory } from './registry.js';
|
|
15
17
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yDAAyD;AACzD,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAE5C,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yDAAyD;AACzD,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAE5C,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill registry — indexes all skills across skill packs for search and discovery.
|
|
3
|
+
*
|
|
4
|
+
* Scans `packages/@dzhechkov/skills-*` directories, reads SKILL.md frontmatter,
|
|
5
|
+
* and builds a searchable index.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
/** A single skill in the registry. */
|
|
10
|
+
export interface RegistryEntry {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly pack: string;
|
|
13
|
+
readonly description: string;
|
|
14
|
+
readonly trustTier: number;
|
|
15
|
+
readonly hasSchema: boolean;
|
|
16
|
+
readonly hasEvals: boolean;
|
|
17
|
+
readonly lineCount: number;
|
|
18
|
+
readonly category: string;
|
|
19
|
+
}
|
|
20
|
+
/** The full registry. */
|
|
21
|
+
export interface Registry {
|
|
22
|
+
readonly entries: readonly RegistryEntry[];
|
|
23
|
+
readonly totalSkills: number;
|
|
24
|
+
readonly totalPacks: number;
|
|
25
|
+
readonly categories: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
/** Build registry from all skills-* packages. */
|
|
28
|
+
export declare function buildRegistry(monorepoRoot: string): Registry;
|
|
29
|
+
/** Search registry by query (matches id and description, case-insensitive). */
|
|
30
|
+
export declare function searchRegistry(registry: Registry, query: string): readonly RegistryEntry[];
|
|
31
|
+
/** Filter registry by category. */
|
|
32
|
+
export declare function filterByCategory(registry: Registry, category: string): readonly RegistryEntry[];
|
|
33
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,yBAAyB;AACzB,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAwBD,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,QAAQ,CAwC5D;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAK1F;AAED,mCAAmC;AACnC,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAE/F"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill registry — indexes all skills across skill packs for search and discovery.
|
|
3
|
+
*
|
|
4
|
+
* Scans `packages/@dzhechkov/skills-*` directories, reads SKILL.md frontmatter,
|
|
5
|
+
* and builds a searchable index.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
/** Extract description from SKILL.md frontmatter. */
|
|
12
|
+
function extractFrontmatter(content) {
|
|
13
|
+
const descMatch = /^description:\s*"?(.+?)"?\s*$/m.exec(content);
|
|
14
|
+
const tierMatch = /^trust_tier:\s*(\d)/m.exec(content);
|
|
15
|
+
return {
|
|
16
|
+
description: descMatch?.[1] ?? '',
|
|
17
|
+
trustTier: tierMatch ? parseInt(tierMatch[1] ?? '1', 10) : 1,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Infer category from pack name. */
|
|
21
|
+
function categoryFromPack(pack) {
|
|
22
|
+
if (pack.includes('devops'))
|
|
23
|
+
return 'devops';
|
|
24
|
+
if (pack.includes('web3'))
|
|
25
|
+
return 'web3';
|
|
26
|
+
if (pack.includes('mcp'))
|
|
27
|
+
return 'mcp';
|
|
28
|
+
if (pack.includes('qe'))
|
|
29
|
+
return 'qe';
|
|
30
|
+
if (pack.includes('meta'))
|
|
31
|
+
return 'meta';
|
|
32
|
+
if (pack.includes('bto'))
|
|
33
|
+
return 'bto';
|
|
34
|
+
if (pack.includes('health'))
|
|
35
|
+
return 'health';
|
|
36
|
+
return 'other';
|
|
37
|
+
}
|
|
38
|
+
/** Build registry from all skills-* packages. */
|
|
39
|
+
export function buildRegistry(monorepoRoot) {
|
|
40
|
+
const baseDir = join(monorepoRoot, 'packages', '@dzhechkov');
|
|
41
|
+
if (!existsSync(baseDir))
|
|
42
|
+
return { entries: [], totalSkills: 0, totalPacks: 0, categories: [] };
|
|
43
|
+
const entries = [];
|
|
44
|
+
const packs = readdirSync(baseDir, { withFileTypes: true })
|
|
45
|
+
.filter((e) => e.isDirectory() && e.name.startsWith('skills-'));
|
|
46
|
+
for (const pack of packs) {
|
|
47
|
+
const packDir = join(baseDir, pack.name);
|
|
48
|
+
const skillDirs = readdirSync(packDir, { withFileTypes: true })
|
|
49
|
+
.filter((e) => e.isDirectory() && existsSync(join(packDir, e.name, 'SKILL.md')));
|
|
50
|
+
for (const skill of skillDirs) {
|
|
51
|
+
const skillMdPath = join(packDir, skill.name, 'SKILL.md');
|
|
52
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
53
|
+
const { description, trustTier } = extractFrontmatter(content);
|
|
54
|
+
entries.push({
|
|
55
|
+
id: skill.name,
|
|
56
|
+
pack: pack.name,
|
|
57
|
+
description,
|
|
58
|
+
trustTier,
|
|
59
|
+
hasSchema: existsSync(join(packDir, skill.name, 'schemas', 'output.json')),
|
|
60
|
+
hasEvals: existsSync(join(packDir, skill.name, 'evals')),
|
|
61
|
+
lineCount: content.split('\n').length,
|
|
62
|
+
category: categoryFromPack(pack.name),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
entries.sort((a, b) => a.id.localeCompare(b.id));
|
|
67
|
+
const categories = [...new Set(entries.map((e) => e.category))].sort();
|
|
68
|
+
return {
|
|
69
|
+
entries,
|
|
70
|
+
totalSkills: entries.length,
|
|
71
|
+
totalPacks: packs.length,
|
|
72
|
+
categories,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Search registry by query (matches id and description, case-insensitive). */
|
|
76
|
+
export function searchRegistry(registry, query) {
|
|
77
|
+
const q = query.toLowerCase();
|
|
78
|
+
return registry.entries.filter((e) => e.id.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || e.category.includes(q));
|
|
79
|
+
}
|
|
80
|
+
/** Filter registry by category. */
|
|
81
|
+
export function filterByCategory(registry, category) {
|
|
82
|
+
return registry.entries.filter((e) => e.category === category);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Shared harness logic - skill loading, additive apply, and the init/sync/verify/doctor operations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"yaml": "^2.0.0",
|
|
28
28
|
"@dzhechkov/adapter-claude": "0.2.0",
|
|
29
|
-
"@dzhechkov/adapter-openclaude": "0.1.0",
|
|
30
|
-
"@dzhechkov/adapter-codex": "0.2.0",
|
|
31
|
-
"@dzhechkov/adapter-opencode": "0.2.0",
|
|
32
29
|
"@dzhechkov/adapter-hermes": "0.2.0",
|
|
30
|
+
"@dzhechkov/adapter-opencode": "0.2.0",
|
|
31
|
+
"@dzhechkov/adapter-codex": "0.2.0",
|
|
32
|
+
"@dzhechkov/adapter-openclaude": "0.1.0",
|
|
33
33
|
"@dzhechkov/core": "0.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
package/src/benchmark.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill benchmarking — Layer 0 deterministic checks + scoring.
|
|
3
|
+
*
|
|
4
|
+
* Runs structural validation checks against a skill directory,
|
|
5
|
+
* producing a scored report. Checks are based on BTO Layer 0
|
|
6
|
+
* (universal U1-U5 + skill-specific S1-S10).
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
/** A single benchmark check result. */
|
|
15
|
+
export interface BenchmarkCheck {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly passed: boolean;
|
|
19
|
+
readonly detail?: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Score for a single skill benchmark. */
|
|
23
|
+
export interface BenchmarkScore {
|
|
24
|
+
readonly skillId: string;
|
|
25
|
+
readonly skillDir: string;
|
|
26
|
+
readonly checks: readonly BenchmarkCheck[];
|
|
27
|
+
readonly passed: number;
|
|
28
|
+
readonly total: number;
|
|
29
|
+
readonly passRate: number;
|
|
30
|
+
readonly grade: 'A' | 'B' | 'C' | 'D' | 'F';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Result of benchmarking one or more skills. */
|
|
34
|
+
export interface BenchmarkReport {
|
|
35
|
+
readonly skills: readonly BenchmarkScore[];
|
|
36
|
+
readonly totalPassed: number;
|
|
37
|
+
readonly totalChecks: number;
|
|
38
|
+
readonly overallPassRate: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Result of A/B comparison between two skills. */
|
|
42
|
+
export interface CompareResult {
|
|
43
|
+
readonly skillA: BenchmarkScore;
|
|
44
|
+
readonly skillB: BenchmarkScore;
|
|
45
|
+
readonly winner: string;
|
|
46
|
+
readonly deltaChecks: readonly { readonly id: string; readonly aPass: boolean; readonly bPass: boolean }[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function gradeFromRate(rate: number): 'A' | 'B' | 'C' | 'D' | 'F' {
|
|
50
|
+
if (rate >= 90) return 'A';
|
|
51
|
+
if (rate >= 80) return 'B';
|
|
52
|
+
if (rate >= 70) return 'C';
|
|
53
|
+
if (rate >= 60) return 'D';
|
|
54
|
+
return 'F';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Run Layer 0 benchmark checks on a single skill directory. */
|
|
58
|
+
export function benchmarkSkill(skillDir: string, skillId: string): BenchmarkScore {
|
|
59
|
+
const checks: BenchmarkCheck[] = [];
|
|
60
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
61
|
+
|
|
62
|
+
// U1: SKILL.md exists
|
|
63
|
+
const skillMdExists = existsSync(skillMdPath);
|
|
64
|
+
checks.push({ id: 'U1', name: 'SKILL.md exists', passed: skillMdExists });
|
|
65
|
+
|
|
66
|
+
if (!skillMdExists) {
|
|
67
|
+
return {
|
|
68
|
+
skillId, skillDir, checks, passed: 0, total: checks.length,
|
|
69
|
+
passRate: 0, grade: 'F',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
74
|
+
const stat = statSync(skillMdPath);
|
|
75
|
+
|
|
76
|
+
// U2: Valid UTF-8 (if we can read it, it's valid)
|
|
77
|
+
checks.push({ id: 'U2', name: 'valid UTF-8', passed: true });
|
|
78
|
+
|
|
79
|
+
// U3: Has headings
|
|
80
|
+
const hasHeadings = /^#{1,6}\s/m.test(content);
|
|
81
|
+
checks.push({ id: 'U3', name: 'has headings', passed: hasHeadings });
|
|
82
|
+
|
|
83
|
+
// U4: No excessive blanks (>3 consecutive blank lines)
|
|
84
|
+
const excessiveBlanks = /\n{5,}/.test(content);
|
|
85
|
+
checks.push({ id: 'U4', name: 'no excessive blanks', passed: !excessiveBlanks });
|
|
86
|
+
|
|
87
|
+
// U5: Size bounds (100B - 50KB)
|
|
88
|
+
const sizeOk = stat.size >= 100 && stat.size <= 50000;
|
|
89
|
+
checks.push({
|
|
90
|
+
id: 'U5', name: 'size bounds (100B-50KB)', passed: sizeOk,
|
|
91
|
+
detail: `${stat.size} bytes`,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// S1: Frontmatter present
|
|
95
|
+
const hasFrontmatter = content.startsWith('---');
|
|
96
|
+
checks.push({ id: 'S1', name: 'frontmatter present', passed: hasFrontmatter });
|
|
97
|
+
|
|
98
|
+
// S2: name field
|
|
99
|
+
const hasName = /^name:\s/m.test(content);
|
|
100
|
+
checks.push({ id: 'S2', name: 'name field', passed: hasName });
|
|
101
|
+
|
|
102
|
+
// S3: description field
|
|
103
|
+
const hasDescription = /^description:\s/m.test(content);
|
|
104
|
+
checks.push({ id: 'S3', name: 'description field', passed: hasDescription });
|
|
105
|
+
|
|
106
|
+
// S4: trust_tier field
|
|
107
|
+
const hasTrustTier = /^trust_tier:\s/m.test(content);
|
|
108
|
+
checks.push({ id: 'S4', name: 'trust_tier field', passed: hasTrustTier });
|
|
109
|
+
|
|
110
|
+
// S5: Protocol/procedure section
|
|
111
|
+
const hasProtocol = /^##\s+(Protocol|Procedure|Steps|When to use)/mi.test(content);
|
|
112
|
+
checks.push({ id: 'S5', name: 'protocol/procedure section', passed: hasProtocol });
|
|
113
|
+
|
|
114
|
+
// S6: Output section or schema reference
|
|
115
|
+
const hasOutput = /^##\s+Output/mi.test(content) || /schema_path/m.test(content);
|
|
116
|
+
checks.push({ id: 'S6', name: 'output section or schema ref', passed: hasOutput });
|
|
117
|
+
|
|
118
|
+
// S7: schemas/output.json exists
|
|
119
|
+
const hasSchema = existsSync(join(skillDir, 'schemas', 'output.json'));
|
|
120
|
+
checks.push({ id: 'S7', name: 'schemas/output.json exists', passed: hasSchema });
|
|
121
|
+
|
|
122
|
+
// S8: scripts/validate-config.json exists
|
|
123
|
+
const hasValidator = existsSync(join(skillDir, 'scripts', 'validate-config.json'));
|
|
124
|
+
checks.push({ id: 'S8', name: 'scripts/validate-config.json exists', passed: hasValidator });
|
|
125
|
+
|
|
126
|
+
// S9: When to use section
|
|
127
|
+
const hasWhenToUse = /^##\s+(When to use|When To Activate|Use this skill when)/mi.test(content);
|
|
128
|
+
checks.push({ id: 'S9', name: 'when-to-use section', passed: hasWhenToUse });
|
|
129
|
+
|
|
130
|
+
// S10: No TODO/PLACEHOLDER markers
|
|
131
|
+
const hasTodos = /\[TODO\]|\[PLACEHOLDER\]|\[TBD\]/i.test(content);
|
|
132
|
+
checks.push({ id: 'S10', name: 'no TODO/PLACEHOLDER markers', passed: !hasTodos });
|
|
133
|
+
|
|
134
|
+
// S11: Evals exist
|
|
135
|
+
const hasEvals = existsSync(join(skillDir, 'evals'));
|
|
136
|
+
checks.push({ id: 'S11', name: 'evals directory exists', passed: hasEvals });
|
|
137
|
+
|
|
138
|
+
// S12: Content depth (>20 lines)
|
|
139
|
+
const lineCount = content.split('\n').length;
|
|
140
|
+
checks.push({
|
|
141
|
+
id: 'S12', name: 'content depth (>20 lines)', passed: lineCount > 20,
|
|
142
|
+
detail: `${lineCount} lines`,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// S13: Examples section
|
|
146
|
+
const hasExamples = /^##\s+(Examples|Example|Quick Start)/mi.test(content);
|
|
147
|
+
checks.push({ id: 'S13', name: 'examples section', passed: hasExamples });
|
|
148
|
+
|
|
149
|
+
// S14: Anti-patterns or self-check section
|
|
150
|
+
const hasAntiPatterns = /^##\s+(Anti.?[Pp]atterns|Self.?check|Self-check)/mi.test(content);
|
|
151
|
+
checks.push({ id: 'S14', name: 'anti-patterns or self-check', passed: hasAntiPatterns });
|
|
152
|
+
|
|
153
|
+
const passed = checks.filter((c) => c.passed).length;
|
|
154
|
+
const passRate = Math.round((passed / checks.length) * 100);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
skillId, skillDir, checks, passed, total: checks.length,
|
|
158
|
+
passRate, grade: gradeFromRate(passRate),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Benchmark multiple skills. */
|
|
163
|
+
export function benchmarkSkills(skillDirs: { id: string; dir: string }[]): BenchmarkReport {
|
|
164
|
+
const skills = skillDirs.map((s) => benchmarkSkill(s.dir, s.id));
|
|
165
|
+
const totalPassed = skills.reduce((sum, s) => sum + s.passed, 0);
|
|
166
|
+
const totalChecks = skills.reduce((sum, s) => sum + s.total, 0);
|
|
167
|
+
return {
|
|
168
|
+
skills,
|
|
169
|
+
totalPassed,
|
|
170
|
+
totalChecks,
|
|
171
|
+
overallPassRate: totalChecks > 0 ? Math.round((totalPassed / totalChecks) * 100) : 0,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Compare two skills head-to-head. */
|
|
176
|
+
export function compareSkills(
|
|
177
|
+
skillADir: string, skillAId: string,
|
|
178
|
+
skillBDir: string, skillBId: string,
|
|
179
|
+
): CompareResult {
|
|
180
|
+
const skillA = benchmarkSkill(skillADir, skillAId);
|
|
181
|
+
const skillB = benchmarkSkill(skillBDir, skillBId);
|
|
182
|
+
|
|
183
|
+
const deltaChecks: { id: string; aPass: boolean; bPass: boolean }[] = [];
|
|
184
|
+
for (const checkA of skillA.checks) {
|
|
185
|
+
const checkB = skillB.checks.find((c) => c.id === checkA.id);
|
|
186
|
+
if (checkB && checkA.passed !== checkB.passed) {
|
|
187
|
+
deltaChecks.push({ id: checkA.id, aPass: checkA.passed, bPass: checkB.passed });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const winner = skillA.passRate > skillB.passRate ? skillAId
|
|
192
|
+
: skillB.passRate > skillA.passRate ? skillBId
|
|
193
|
+
: 'tie';
|
|
194
|
+
|
|
195
|
+
return { skillA, skillB, winner, deltaChecks };
|
|
196
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,3 +16,7 @@ export { createSkill } from './create-skill.js';
|
|
|
16
16
|
export type { CreateSkillOptions, CreateSkillResult } from './create-skill.js';
|
|
17
17
|
export { checkUpstream, loadSourcesManifest } from './sync-upstream.js';
|
|
18
18
|
export type { SyncUpstreamReport, UpstreamCheckResult, SourcesManifest } from './sync-upstream.js';
|
|
19
|
+
export { benchmarkSkill, benchmarkSkills, compareSkills } from './benchmark.js';
|
|
20
|
+
export { buildRegistry, searchRegistry, filterByCategory } from './registry.js';
|
|
21
|
+
export type { RegistryEntry, Registry } from './registry.js';
|
|
22
|
+
export type { BenchmarkCheck, BenchmarkScore, BenchmarkReport, CompareResult } from './benchmark.js';
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill registry — indexes all skills across skill packs for search and discovery.
|
|
3
|
+
*
|
|
4
|
+
* Scans `packages/@dzhechkov/skills-*` directories, reads SKILL.md frontmatter,
|
|
5
|
+
* and builds a searchable index.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
/** A single skill in the registry. */
|
|
14
|
+
export interface RegistryEntry {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly pack: string;
|
|
17
|
+
readonly description: string;
|
|
18
|
+
readonly trustTier: number;
|
|
19
|
+
readonly hasSchema: boolean;
|
|
20
|
+
readonly hasEvals: boolean;
|
|
21
|
+
readonly lineCount: number;
|
|
22
|
+
readonly category: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The full registry. */
|
|
26
|
+
export interface Registry {
|
|
27
|
+
readonly entries: readonly RegistryEntry[];
|
|
28
|
+
readonly totalSkills: number;
|
|
29
|
+
readonly totalPacks: number;
|
|
30
|
+
readonly categories: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Extract description from SKILL.md frontmatter. */
|
|
34
|
+
function extractFrontmatter(content: string): { description: string; trustTier: number } {
|
|
35
|
+
const descMatch = /^description:\s*"?(.+?)"?\s*$/m.exec(content);
|
|
36
|
+
const tierMatch = /^trust_tier:\s*(\d)/m.exec(content);
|
|
37
|
+
return {
|
|
38
|
+
description: descMatch?.[1] ?? '',
|
|
39
|
+
trustTier: tierMatch ? parseInt(tierMatch[1] ?? '1', 10) : 1,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Infer category from pack name. */
|
|
44
|
+
function categoryFromPack(pack: string): string {
|
|
45
|
+
if (pack.includes('devops')) return 'devops';
|
|
46
|
+
if (pack.includes('web3')) return 'web3';
|
|
47
|
+
if (pack.includes('mcp')) return 'mcp';
|
|
48
|
+
if (pack.includes('qe')) return 'qe';
|
|
49
|
+
if (pack.includes('meta')) return 'meta';
|
|
50
|
+
if (pack.includes('bto')) return 'bto';
|
|
51
|
+
if (pack.includes('health')) return 'health';
|
|
52
|
+
return 'other';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Build registry from all skills-* packages. */
|
|
56
|
+
export function buildRegistry(monorepoRoot: string): Registry {
|
|
57
|
+
const baseDir = join(monorepoRoot, 'packages', '@dzhechkov');
|
|
58
|
+
if (!existsSync(baseDir)) return { entries: [], totalSkills: 0, totalPacks: 0, categories: [] };
|
|
59
|
+
|
|
60
|
+
const entries: RegistryEntry[] = [];
|
|
61
|
+
const packs = readdirSync(baseDir, { withFileTypes: true })
|
|
62
|
+
.filter((e) => e.isDirectory() && e.name.startsWith('skills-'));
|
|
63
|
+
|
|
64
|
+
for (const pack of packs) {
|
|
65
|
+
const packDir = join(baseDir, pack.name);
|
|
66
|
+
const skillDirs = readdirSync(packDir, { withFileTypes: true })
|
|
67
|
+
.filter((e) => e.isDirectory() && existsSync(join(packDir, e.name, 'SKILL.md')));
|
|
68
|
+
|
|
69
|
+
for (const skill of skillDirs) {
|
|
70
|
+
const skillMdPath = join(packDir, skill.name, 'SKILL.md');
|
|
71
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
72
|
+
const { description, trustTier } = extractFrontmatter(content);
|
|
73
|
+
|
|
74
|
+
entries.push({
|
|
75
|
+
id: skill.name,
|
|
76
|
+
pack: pack.name,
|
|
77
|
+
description,
|
|
78
|
+
trustTier,
|
|
79
|
+
hasSchema: existsSync(join(packDir, skill.name, 'schemas', 'output.json')),
|
|
80
|
+
hasEvals: existsSync(join(packDir, skill.name, 'evals')),
|
|
81
|
+
lineCount: content.split('\n').length,
|
|
82
|
+
category: categoryFromPack(pack.name),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
entries.sort((a, b) => a.id.localeCompare(b.id));
|
|
88
|
+
const categories = [...new Set(entries.map((e) => e.category))].sort();
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
entries,
|
|
92
|
+
totalSkills: entries.length,
|
|
93
|
+
totalPacks: packs.length,
|
|
94
|
+
categories,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Search registry by query (matches id and description, case-insensitive). */
|
|
99
|
+
export function searchRegistry(registry: Registry, query: string): readonly RegistryEntry[] {
|
|
100
|
+
const q = query.toLowerCase();
|
|
101
|
+
return registry.entries.filter(
|
|
102
|
+
(e) => e.id.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || e.category.includes(q),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Filter registry by category. */
|
|
107
|
+
export function filterByCategory(registry: Registry, category: string): readonly RegistryEntry[] {
|
|
108
|
+
return registry.entries.filter((e) => e.category === category);
|
|
109
|
+
}
|