@shahimat/harness 0.1.0 → 0.3.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 +11 -1
- package/dist/checks/context-index.js +34 -18
- package/dist/cli.js +7 -0
- package/dist/commands/expect.d.ts +3 -0
- package/dist/commands/expect.js +303 -0
- package/dist/commands/types.d.ts +2 -0
- package/dist/expect/batch.d.ts +44 -0
- package/dist/expect/batch.js +157 -0
- package/dist/expect/evidence.d.ts +5 -0
- package/dist/expect/evidence.js +21 -0
- package/dist/expect/index.d.ts +4 -0
- package/dist/expect/index.js +4 -0
- package/dist/expect/judge.d.ts +24 -0
- package/dist/expect/judge.js +50 -0
- package/dist/expect/levels.d.ts +25 -0
- package/dist/expect/levels.js +43 -0
- package/dist/expect/lint.d.ts +15 -0
- package/dist/expect/lint.js +38 -0
- package/dist/expect/repo.d.ts +60 -0
- package/dist/expect/repo.js +139 -0
- package/dist/expect/schema.d.ts +13 -0
- package/dist/expect/schema.js +47 -0
- package/dist/expect/verdicts.d.ts +35 -0
- package/dist/expect/verdicts.js +23 -0
- package/dist/prompt-index.d.ts +2 -0
- package/dist/prompt-index.js +57 -2
- package/package.json +14 -1
package/dist/prompt-index.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { stringify } from 'yaml';
|
|
4
|
+
import { parse, stringify } from 'yaml';
|
|
5
5
|
import { normalizeEol, parseFrontmatter } from './frontmatter.js';
|
|
6
6
|
export const INDEX_PATH = 'prompts/index.yml';
|
|
7
7
|
const SOURCE_DIRS = ['prompts', 'patterns'];
|
|
8
8
|
const HEADER = '# generated by harness index build — do not edit manually';
|
|
9
9
|
const SKILLS_DIR = 'skills';
|
|
10
10
|
const AGENTS_DIR = 'agents';
|
|
11
|
+
const PRESETS_DIR = 'presets';
|
|
11
12
|
const GROUPS = [
|
|
12
13
|
{ kind: 'contract', label: 'contracts' },
|
|
13
14
|
{ kind: 'pattern', label: 'patterns' },
|
|
14
15
|
{ kind: 'module', label: 'modules' },
|
|
15
16
|
{ kind: 'skill', label: 'skills' },
|
|
16
17
|
{ kind: 'agent', label: 'agents' },
|
|
18
|
+
{ kind: 'preset', label: 'presets' },
|
|
17
19
|
{ kind: 'prompt', label: 'prompts' },
|
|
18
20
|
];
|
|
19
21
|
/** Поля `feature` во frontmatter; `asks` читается из самого модуля, в индекс не идёт. */
|
|
@@ -128,6 +130,9 @@ export async function collectEntries(root) {
|
|
|
128
130
|
const title = oneLine(fm?.['title'], 'title', file);
|
|
129
131
|
if (title !== undefined)
|
|
130
132
|
entry.title = title;
|
|
133
|
+
const deprecated = oneLine(fm?.['deprecated'], 'deprecated', file);
|
|
134
|
+
if (deprecated !== undefined)
|
|
135
|
+
entry.deprecated = deprecated;
|
|
131
136
|
const feature = parseFeature(fm?.['feature'], kind, file);
|
|
132
137
|
if (feature !== undefined)
|
|
133
138
|
entry.feature = feature;
|
|
@@ -135,6 +140,7 @@ export async function collectEntries(root) {
|
|
|
135
140
|
}
|
|
136
141
|
entries.push(...(await collectSkills(root)), ...(await collectAgents(root)));
|
|
137
142
|
checkFeatureRefs(entries);
|
|
143
|
+
entries.push(...(await collectPresets(root, entries)));
|
|
138
144
|
return { entries, skipped };
|
|
139
145
|
}
|
|
140
146
|
async function listNames(abs) {
|
|
@@ -191,6 +197,53 @@ async function collectSkills(root) {
|
|
|
191
197
|
}
|
|
192
198
|
return entries;
|
|
193
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Пресет — `presets/<key>.yml`: готовый набор возможностей для типа
|
|
202
|
+
* проекта. Модули пресета должны быть в индексе и применимы к его контракту.
|
|
203
|
+
*/
|
|
204
|
+
async function collectPresets(root, known) {
|
|
205
|
+
const entries = [];
|
|
206
|
+
for (const entry of await listNames(path.join(root, PRESETS_DIR))) {
|
|
207
|
+
if (!entry.endsWith('.yml'))
|
|
208
|
+
continue;
|
|
209
|
+
const file = `${PRESETS_DIR}/${entry}`;
|
|
210
|
+
const key = entry.slice(0, -4);
|
|
211
|
+
const content = await readFile(path.join(root, file), 'utf8');
|
|
212
|
+
let data;
|
|
213
|
+
try {
|
|
214
|
+
data = parse(content);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
throw new Error(`${file}: невалидный YAML: ${error.message.split('\n')[0]}`, { cause: error });
|
|
218
|
+
}
|
|
219
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data))
|
|
220
|
+
throw new Error(`${file}: корень должен быть объектом`);
|
|
221
|
+
const preset = data;
|
|
222
|
+
if (preset['key'] !== key)
|
|
223
|
+
throw new Error(`${file}: key должно совпадать с именем файла «${key}»`);
|
|
224
|
+
const title = oneLine(preset['title'], 'title', file);
|
|
225
|
+
if (title === undefined)
|
|
226
|
+
throw new Error(`${file}: нет title`);
|
|
227
|
+
const contract = preset['contract'];
|
|
228
|
+
if (!known.some((e) => e.kind === 'contract' && e.key === contract)) {
|
|
229
|
+
throw new Error(`${file}: контракта «${String(contract)}» нет в индексе`);
|
|
230
|
+
}
|
|
231
|
+
const modules = preset['modules'];
|
|
232
|
+
if (!Array.isArray(modules) || modules.length === 0)
|
|
233
|
+
throw new Error(`${file}: modules — непустой список`);
|
|
234
|
+
for (const item of modules) {
|
|
235
|
+
const moduleKey = typeof item === 'object' && item !== null ? item['key'] : undefined;
|
|
236
|
+
const module = known.find((e) => e.kind === 'module' && e.key === moduleKey);
|
|
237
|
+
if (module === undefined)
|
|
238
|
+
throw new Error(`${file}: модуля «${String(moduleKey)}» нет в индексе`);
|
|
239
|
+
if (!module.applicableTo.includes(String(contract))) {
|
|
240
|
+
throw new Error(`${file}: модуль «${module.key}» не применим к «${String(contract)}»`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
entries.push({ kind: 'preset', key, path: file, sha256: sha256(content), applicableTo: [String(contract)], patterns: [], title });
|
|
244
|
+
}
|
|
245
|
+
return entries;
|
|
246
|
+
}
|
|
194
247
|
/** Субагент — `agents/<name>.md`: frontmatter name, description, skills, tools + инструкция. */
|
|
195
248
|
async function collectAgents(root) {
|
|
196
249
|
const entries = [];
|
|
@@ -222,6 +275,8 @@ export function renderIndex(entries, options) {
|
|
|
222
275
|
lines.push(` - key: ${e.key}`, ...(e.title === undefined ? [] : [` title: ${scalar(e.title)}`]), ` path: ${e.path}`,
|
|
223
276
|
// У skill raw нет: это каталог, raw — у каждого файла.
|
|
224
277
|
...(e.files === undefined ? [` raw: ${options.rawBase}/${e.path}`] : []), ` sha256: ${e.sha256}`);
|
|
278
|
+
if (e.deprecated !== undefined)
|
|
279
|
+
lines.push(` deprecated: ${scalar(e.deprecated)}`);
|
|
225
280
|
if (e.files !== undefined) {
|
|
226
281
|
lines.push(' files:');
|
|
227
282
|
for (const f of e.files) {
|
|
@@ -230,7 +285,7 @@ export function renderIndex(entries, options) {
|
|
|
230
285
|
}
|
|
231
286
|
if (e.patterns.length > 0)
|
|
232
287
|
lines.push(` patterns: ${flowList(e.patterns)}`);
|
|
233
|
-
if (e.kind === 'module' && e.applicableTo.length > 0) {
|
|
288
|
+
if ((e.kind === 'module' || e.kind === 'preset') && e.applicableTo.length > 0) {
|
|
234
289
|
lines.push(` applicable_to: ${flowList(e.applicableTo)}`);
|
|
235
290
|
}
|
|
236
291
|
if (e.feature !== undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shahimat/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Harness для проектов на контрактах ai-prompts: генераторы и проверки",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Gevorg Nakhapetyan",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
".": {
|
|
34
34
|
"types": "./dist/index.d.ts",
|
|
35
35
|
"import": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./expect": {
|
|
38
|
+
"types": "./dist/expect/index.d.ts",
|
|
39
|
+
"import": "./dist/expect/index.js"
|
|
36
40
|
}
|
|
37
41
|
},
|
|
38
42
|
"files": [
|
|
@@ -46,6 +50,7 @@
|
|
|
46
50
|
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
47
51
|
},
|
|
48
52
|
"dependencies": {
|
|
53
|
+
"ajv": "^8.20.0",
|
|
49
54
|
"yaml": "^2.9.1"
|
|
50
55
|
},
|
|
51
56
|
"devDependencies": {
|
|
@@ -55,5 +60,13 @@
|
|
|
55
60
|
},
|
|
56
61
|
"publishConfig": {
|
|
57
62
|
"access": "public"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"vitest": ">=4"
|
|
66
|
+
},
|
|
67
|
+
"peerDependenciesMeta": {
|
|
68
|
+
"vitest": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
58
71
|
}
|
|
59
72
|
}
|