@hawkeyexl/manni 4.3.1 → 4.4.1
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 -1
- package/dist/batch-GFNLTGOH.js +36 -0
- package/dist/build-COYIQ25L.js +31 -0
- package/dist/calibrate-MDBLJKEE.js +31 -0
- package/dist/capture-LRML2PKV.js +32 -0
- package/dist/check-ASPIHA6B.js +40 -0
- package/dist/check-ASPIHA6B.js.map +1 -0
- package/dist/check-EDLBYL5N.js +27 -0
- package/dist/check-EDLBYL5N.js.map +1 -0
- package/dist/check-HYNGXYJV.js +18 -0
- package/dist/check-HYNGXYJV.js.map +1 -0
- package/dist/chunk-2B7ZNTPV.js +230 -0
- package/dist/chunk-2B7ZNTPV.js.map +1 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-37LP5HMZ.js +1042 -0
- package/dist/chunk-37LP5HMZ.js.map +1 -0
- package/dist/chunk-3VK37OR2.js +2957 -0
- package/dist/chunk-3VK37OR2.js.map +1 -0
- package/dist/chunk-4IUC2MNL.js +2165 -0
- package/dist/chunk-4IUC2MNL.js.map +1 -0
- package/dist/{chunk-3R23C5DT.js → chunk-4TSWU2SU.js} +2 -14
- package/dist/chunk-4TSWU2SU.js.map +1 -0
- package/dist/chunk-57NEQRVO.js +10921 -0
- package/dist/chunk-57NEQRVO.js.map +1 -0
- package/dist/chunk-6JSSEWBG.js +6737 -0
- package/dist/chunk-6JSSEWBG.js.map +1 -0
- package/dist/chunk-6OYEI2VV.js +88 -0
- package/dist/chunk-6OYEI2VV.js.map +1 -0
- package/dist/chunk-77AINBYM.js +19 -0
- package/dist/chunk-77AINBYM.js.map +1 -0
- package/dist/chunk-7UKO3ASZ.js +54 -0
- package/dist/chunk-7UKO3ASZ.js.map +1 -0
- package/dist/chunk-A5ULMVHQ.js +164 -0
- package/dist/chunk-A5ULMVHQ.js.map +1 -0
- package/dist/chunk-C4SQM22E.js +299 -0
- package/dist/chunk-C4SQM22E.js.map +1 -0
- package/dist/chunk-CK7SKVHR.js +1042 -0
- package/dist/chunk-CK7SKVHR.js.map +1 -0
- package/dist/{chunk-G5CM2VGQ.js → chunk-DXNZS72W.js} +8 -94
- package/dist/chunk-DXNZS72W.js.map +1 -0
- package/dist/chunk-EPH54CAY.js +374 -0
- package/dist/chunk-EPH54CAY.js.map +1 -0
- package/dist/{chunk-KJR7OVDX.js → chunk-FBEQCK47.js} +56 -137
- package/dist/chunk-FBEQCK47.js.map +1 -0
- package/dist/{chunk-IOYXPBCL.js → chunk-G3T47CFV.js} +1 -7
- package/dist/{chunk-IOYXPBCL.js.map → chunk-G3T47CFV.js.map} +1 -1
- package/dist/chunk-GCQRPUSB.js +1287 -0
- package/dist/chunk-GCQRPUSB.js.map +1 -0
- package/dist/chunk-GLP5PSXB.js +72 -0
- package/dist/chunk-GLP5PSXB.js.map +1 -0
- package/dist/chunk-I2AH3CJJ.js +1192 -0
- package/dist/chunk-I2AH3CJJ.js.map +1 -0
- package/dist/chunk-IDV6HJCB.js +388 -0
- package/dist/chunk-IDV6HJCB.js.map +1 -0
- package/dist/chunk-IKEWBMCN.js +98 -0
- package/dist/chunk-IKEWBMCN.js.map +1 -0
- package/dist/chunk-JCDXE76J.js +52 -0
- package/dist/chunk-JCDXE76J.js.map +1 -0
- package/dist/chunk-JXLTXMJ7.js +455 -0
- package/dist/chunk-JXLTXMJ7.js.map +1 -0
- package/dist/chunk-LJ2RDH3F.js +49 -0
- package/dist/chunk-LJ2RDH3F.js.map +1 -0
- package/dist/chunk-M2JCK5F6.js +805 -0
- package/dist/chunk-M2JCK5F6.js.map +1 -0
- package/dist/chunk-M3W7J6MU.js +4398 -0
- package/dist/chunk-M3W7J6MU.js.map +1 -0
- package/dist/chunk-MMTSSTG7.js +23 -0
- package/dist/chunk-MMTSSTG7.js.map +1 -0
- package/dist/chunk-MNH7QO4Z.js +15 -0
- package/dist/chunk-MNH7QO4Z.js.map +1 -0
- package/dist/chunk-MWJLZMBL.js +934 -0
- package/dist/chunk-MWJLZMBL.js.map +1 -0
- package/dist/chunk-MYMBCTDE.js +387 -0
- package/dist/chunk-MYMBCTDE.js.map +1 -0
- package/dist/chunk-NCPWXHDO.js +683 -0
- package/dist/chunk-NCPWXHDO.js.map +1 -0
- package/dist/chunk-NLXTXE5Y.js +1661 -0
- package/dist/chunk-NLXTXE5Y.js.map +1 -0
- package/dist/{chunk-CFDUZRM2.js → chunk-NXV7WE2D.js} +32 -240
- package/dist/chunk-NXV7WE2D.js.map +1 -0
- package/dist/chunk-OWBRDNRZ.js +16 -0
- package/dist/chunk-OWBRDNRZ.js.map +1 -0
- package/dist/{chunk-KTSJ2ZHA.js → chunk-PAU352SV.js} +5 -3
- package/dist/{chunk-KTSJ2ZHA.js.map → chunk-PAU352SV.js.map} +1 -1
- package/dist/chunk-PCE7QHXJ.js +68 -0
- package/dist/chunk-PCE7QHXJ.js.map +1 -0
- package/dist/chunk-PIKOVN6W.js +1651 -0
- package/dist/chunk-PIKOVN6W.js.map +1 -0
- package/dist/chunk-PNPQTHBT.js +17 -0
- package/dist/chunk-PNPQTHBT.js.map +1 -0
- package/dist/chunk-PU6DFJMY.js +1975 -0
- package/dist/chunk-PU6DFJMY.js.map +1 -0
- package/dist/chunk-Q35YIDBV.js +1202 -0
- package/dist/chunk-Q35YIDBV.js.map +1 -0
- package/dist/{chunk-5HNHEUD5.js → chunk-QQUKGCAJ.js} +3 -3
- package/dist/chunk-QRWGPQKO.js +198 -0
- package/dist/chunk-QRWGPQKO.js.map +1 -0
- package/dist/{chunk-UBMMXS2Y.js → chunk-QZ6LRSNV.js} +69 -48
- package/dist/{chunk-UBMMXS2Y.js.map → chunk-QZ6LRSNV.js.map} +1 -1
- package/dist/chunk-RL5XWQOL.js +3872 -0
- package/dist/chunk-RL5XWQOL.js.map +1 -0
- package/dist/chunk-RNVYGWIC.js +30 -0
- package/dist/chunk-RNVYGWIC.js.map +1 -0
- package/dist/chunk-SE4JN5XN.js +19 -0
- package/dist/chunk-SE4JN5XN.js.map +1 -0
- package/dist/chunk-SKSDRUUK.js +622 -0
- package/dist/chunk-SKSDRUUK.js.map +1 -0
- package/dist/chunk-TIUTIOFB.js +25 -0
- package/dist/chunk-TIUTIOFB.js.map +1 -0
- package/dist/chunk-TK3FC4RF.js +3090 -0
- package/dist/chunk-TK3FC4RF.js.map +1 -0
- package/dist/chunk-TU2WUBVZ.js +2 -0
- package/dist/chunk-TU2WUBVZ.js.map +1 -0
- package/dist/chunk-TZOYZK2E.js +98 -0
- package/dist/chunk-TZOYZK2E.js.map +1 -0
- package/dist/chunk-UJCVAWP7.js +178 -0
- package/dist/chunk-UJCVAWP7.js.map +1 -0
- package/dist/chunk-UKZNLVJW.js +17 -0
- package/dist/chunk-UKZNLVJW.js.map +1 -0
- package/dist/{chunk-HAPTXWU2.js → chunk-UQISLDAV.js} +4 -4
- package/dist/chunk-WB3QM6PP.js +5440 -0
- package/dist/chunk-WB3QM6PP.js.map +1 -0
- package/dist/chunk-YB4IBW3M.js +96 -0
- package/dist/chunk-YB4IBW3M.js.map +1 -0
- package/dist/chunk-YEUYKR62.js +72 -0
- package/dist/chunk-YEUYKR62.js.map +1 -0
- package/dist/chunk-YJOGUXGP.js +634 -0
- package/dist/chunk-YJOGUXGP.js.map +1 -0
- package/dist/chunk-YNHZHSSM.js +41 -0
- package/dist/chunk-YNHZHSSM.js.map +1 -0
- package/dist/chunk-YTURO7SI.js +979 -0
- package/dist/chunk-YTURO7SI.js.map +1 -0
- package/dist/chunk-ZXRAE55H.js +31 -0
- package/dist/chunk-ZXRAE55H.js.map +1 -0
- package/dist/cli-3BIBYHFK.js +473 -0
- package/dist/cli-3BIBYHFK.js.map +1 -0
- package/dist/cli-673OTRGE.js +28 -0
- package/dist/cli-673OTRGE.js.map +1 -0
- package/dist/cli-6HIDIFVT.js +1203 -0
- package/dist/cli-6HIDIFVT.js.map +1 -0
- package/dist/cli-6UB5N6CU.js +232 -0
- package/dist/cli-6UB5N6CU.js.map +1 -0
- package/dist/cli-LFP5ZGYS.js +346 -0
- package/dist/cli-LFP5ZGYS.js.map +1 -0
- package/dist/cli-MN7PYMBI.js +552 -0
- package/dist/cli-MN7PYMBI.js.map +1 -0
- package/dist/cli-PYPX6O6E.js +739 -0
- package/dist/cli-PYPX6O6E.js.map +1 -0
- package/dist/cli-RPM5PELX.js +626 -0
- package/dist/cli-RPM5PELX.js.map +1 -0
- package/dist/cli-VI7VHWNZ.js +423 -0
- package/dist/cli-VI7VHWNZ.js.map +1 -0
- package/dist/cli.d.ts +12 -2
- package/dist/cli.js +109 -7661
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +15 -3
- package/dist/docmeta.js.map +1 -1
- package/dist/github-6QMYOHC3.js +27 -0
- package/dist/github-6QMYOHC3.js.map +1 -0
- package/dist/github-SXPGP5DB.js +32 -0
- package/dist/github-SXPGP5DB.js.map +1 -0
- package/dist/index.d.ts +65 -3
- package/dist/index.js +407 -337
- package/dist/index.js.map +1 -1
- package/dist/json-7WV7KL6H.js +17 -0
- package/dist/json-7WV7KL6H.js.map +1 -0
- package/dist/json-SY7KLNC7.js +35 -0
- package/dist/json-SY7KLNC7.js.map +1 -0
- package/dist/lint-ASMJLLPA.js +28 -0
- package/dist/lint-ASMJLLPA.js.map +1 -0
- package/dist/list-KZNVTNVC.js +31 -0
- package/dist/list-KZNVTNVC.js.map +1 -0
- package/dist/list-Q57YTFUQ.js +29 -0
- package/dist/list-Q57YTFUQ.js.map +1 -0
- package/dist/page-KSGV5RD4.js +41 -0
- package/dist/page-KSGV5RD4.js.map +1 -0
- package/dist/picker-3XF36MJF.js +14 -0
- package/dist/picker-3XF36MJF.js.map +1 -0
- package/dist/pretty-A5EGTN35.js +37 -0
- package/dist/pretty-A5EGTN35.js.map +1 -0
- package/dist/pretty-CXSDXNAS.js +43 -0
- package/dist/pretty-CXSDXNAS.js.map +1 -0
- package/dist/reporters-HUY7UXFS.js +42 -0
- package/dist/reporters-HUY7UXFS.js.map +1 -0
- package/dist/reporters-K2VUKSAK.js +71 -0
- package/dist/reporters-K2VUKSAK.js.map +1 -0
- package/dist/reporters-M6LJ2IYU.js +21 -0
- package/dist/reporters-M6LJ2IYU.js.map +1 -0
- package/dist/run-3DYJNRIG.js +33 -0
- package/dist/run-3DYJNRIG.js.map +1 -0
- package/dist/term-BIQUPMOR.js +1592 -0
- package/dist/term-BIQUPMOR.js.map +1 -0
- package/dist/types-Y6TXHM7M.js +10 -0
- package/dist/types-Y6TXHM7M.js.map +1 -0
- package/dist/validate-53RKM2XG.js +16 -0
- package/dist/validate-53RKM2XG.js.map +1 -0
- package/package.json +1 -1
- package/plugin/manni/.claude-plugin/plugin.json +9 -0
- package/plugin/manni/hooks/hooks.json +69 -0
- package/plugin/manni/hooks/manni.mjs +157 -0
- package/plugin/manni/skills/check/SKILL.md +20 -0
- package/plugin/manni/skills/evals/SKILL.md +27 -0
- package/plugin/manni/skills/fix/SKILL.md +56 -0
- package/plugin/manni/skills/setup/SKILL.md +41 -0
- package/dist/batch-A5HZKYIW.js +0 -18
- package/dist/calibrate-S5K6QHUY.js +0 -13
- package/dist/capture-OZEZDABQ.js +0 -15
- package/dist/chunk-3R23C5DT.js.map +0 -1
- package/dist/chunk-CFDUZRM2.js.map +0 -1
- package/dist/chunk-G5CM2VGQ.js.map +0 -1
- package/dist/chunk-KJR7OVDX.js.map +0 -1
- package/dist/chunk-V4KUVE47.js +0 -27170
- package/dist/chunk-V4KUVE47.js.map +0 -1
- package/dist/chunk-ZQBTK3DO.js +0 -26518
- package/dist/chunk-ZQBTK3DO.js.map +0 -1
- package/dist/picker-6HRMETE7.js +0 -12
- package/plugin/tracevals/.claude-plugin/plugin.json +0 -9
- package/plugin/tracevals/hooks/hooks.json +0 -17
- /package/dist/{batch-A5HZKYIW.js.map → batch-GFNLTGOH.js.map} +0 -0
- /package/dist/{calibrate-S5K6QHUY.js.map → build-COYIQ25L.js.map} +0 -0
- /package/dist/{capture-OZEZDABQ.js.map → calibrate-MDBLJKEE.js.map} +0 -0
- /package/dist/{picker-6HRMETE7.js.map → capture-LRML2PKV.js.map} +0 -0
- /package/dist/{chunk-5HNHEUD5.js.map → chunk-QQUKGCAJ.js.map} +0 -0
- /package/dist/{chunk-HAPTXWU2.js.map → chunk-UQISLDAV.js.map} +0 -0
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
EVIDENCE_SCHEMA,
|
|
4
|
+
EVIDENCE_SYSTEM_PROMPT,
|
|
5
|
+
JUDGE_SYSTEM_PROMPT,
|
|
6
|
+
VerdictCache,
|
|
7
|
+
buildEvidenceUser,
|
|
8
|
+
buildUserContent,
|
|
9
|
+
cacheKey,
|
|
10
|
+
frontmatterSchema,
|
|
11
|
+
judgeCacheBody,
|
|
12
|
+
leadingFrontmatterFormat,
|
|
13
|
+
loadRunConfig,
|
|
14
|
+
readTarget,
|
|
15
|
+
renderEvidence,
|
|
16
|
+
runConfigOptions,
|
|
17
|
+
runEvals,
|
|
18
|
+
selfPreferenceOf,
|
|
19
|
+
sha256,
|
|
20
|
+
splitBody,
|
|
21
|
+
urlManifestRefusal
|
|
22
|
+
} from "./chunk-3VK37OR2.js";
|
|
23
|
+
import {
|
|
24
|
+
turnBudgetSkipReason
|
|
25
|
+
} from "./chunk-LJ2RDH3F.js";
|
|
26
|
+
import {
|
|
27
|
+
isExecutionGrant,
|
|
28
|
+
unknownGrantsMessage
|
|
29
|
+
} from "./chunk-YNHZHSSM.js";
|
|
30
|
+
import {
|
|
31
|
+
DocevalsError
|
|
32
|
+
} from "./chunk-SE4JN5XN.js";
|
|
33
|
+
import {
|
|
34
|
+
externalWriteWarnings,
|
|
35
|
+
offerExternalHomes
|
|
36
|
+
} from "./chunk-A5ULMVHQ.js";
|
|
37
|
+
import {
|
|
38
|
+
LOCATION_KEYWORD,
|
|
39
|
+
Validator,
|
|
40
|
+
keyHome,
|
|
41
|
+
readManifestOrEmpty,
|
|
42
|
+
readManifestValue,
|
|
43
|
+
spliceManifestValue
|
|
44
|
+
} from "./chunk-M3W7J6MU.js";
|
|
45
|
+
import {
|
|
46
|
+
writeFileAtomic
|
|
47
|
+
} from "./chunk-57NEQRVO.js";
|
|
48
|
+
import {
|
|
49
|
+
assertKnownProvider,
|
|
50
|
+
assertLocalFlag,
|
|
51
|
+
assertModelHasProvider,
|
|
52
|
+
localNotice,
|
|
53
|
+
noticeOnce,
|
|
54
|
+
providerSpecFor,
|
|
55
|
+
resolveIdentity,
|
|
56
|
+
selectProvider,
|
|
57
|
+
warn
|
|
58
|
+
} from "./chunk-Q35YIDBV.js";
|
|
59
|
+
import {
|
|
60
|
+
errorMessage
|
|
61
|
+
} from "./chunk-G3T47CFV.js";
|
|
62
|
+
|
|
63
|
+
// src/docevals/judge/judge.ts
|
|
64
|
+
import {
|
|
65
|
+
computeConsensus,
|
|
66
|
+
runEnsemble,
|
|
67
|
+
zoneFor
|
|
68
|
+
} from "@hawkeyexl/inference";
|
|
69
|
+
|
|
70
|
+
// src/docevals/judge/verdict-schema.json
|
|
71
|
+
var verdict_schema_default = {
|
|
72
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
73
|
+
$id: "manni:docevals:verdict",
|
|
74
|
+
title: "manni docevals judge verdict",
|
|
75
|
+
type: "object",
|
|
76
|
+
required: ["claim", "observed", "match", "confidence", "reasoning"],
|
|
77
|
+
properties: {
|
|
78
|
+
claim: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "The specific documented assertion under evaluation."
|
|
81
|
+
},
|
|
82
|
+
observed: {
|
|
83
|
+
type: "string",
|
|
84
|
+
description: "What was actually observed in the page, quoting evidence where possible."
|
|
85
|
+
},
|
|
86
|
+
match: {
|
|
87
|
+
enum: ["pass", "fail", "partial"],
|
|
88
|
+
description: "pass only if the assertion is fully satisfied; partial for partial compliance."
|
|
89
|
+
},
|
|
90
|
+
confidence: {
|
|
91
|
+
type: "number",
|
|
92
|
+
minimum: 0,
|
|
93
|
+
maximum: 1,
|
|
94
|
+
description: "Self-reported confidence in this verdict."
|
|
95
|
+
},
|
|
96
|
+
reasoning: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Why this conclusion was reached."
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
additionalProperties: false
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// src/docevals/core/reviews.ts
|
|
105
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
106
|
+
import { dirname, join } from "node:path";
|
|
107
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
108
|
+
var REVIEWS_PATH = join(".manni/docevals", "reviews.yaml");
|
|
109
|
+
function loadReviews(root) {
|
|
110
|
+
const path = join(root, REVIEWS_PATH);
|
|
111
|
+
if (!existsSync(path)) return [];
|
|
112
|
+
const raw = parseYaml(readFileSync(path, "utf8"));
|
|
113
|
+
if (!Array.isArray(raw)) return [];
|
|
114
|
+
return raw;
|
|
115
|
+
}
|
|
116
|
+
function saveReviews(root, reviews) {
|
|
117
|
+
const path = join(root, REVIEWS_PATH);
|
|
118
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
119
|
+
writeFileSync(path, stringifyYaml(reviews));
|
|
120
|
+
}
|
|
121
|
+
function recordReview(root, entry) {
|
|
122
|
+
const reviews = loadReviews(root).filter(
|
|
123
|
+
(r) => !(r.file === entry.file && r.evalName === entry.evalName)
|
|
124
|
+
);
|
|
125
|
+
reviews.push(entry);
|
|
126
|
+
saveReviews(root, reviews);
|
|
127
|
+
}
|
|
128
|
+
function findReview(reviews, file, evalName, body) {
|
|
129
|
+
const entry = reviews.find(
|
|
130
|
+
(r) => r.file === file && r.evalName === evalName
|
|
131
|
+
);
|
|
132
|
+
if (!entry) return void 0;
|
|
133
|
+
return entry.contentHash === sha256(body) ? entry : void 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/docevals/judge/provider.ts
|
|
137
|
+
import {
|
|
138
|
+
makeProvider as makeInferenceProvider
|
|
139
|
+
} from "@hawkeyexl/inference";
|
|
140
|
+
var CONFIG_KEY = "docevals.provider";
|
|
141
|
+
var toDocevalsError = (message) => new DocevalsError(message);
|
|
142
|
+
function selectProvider2(config, flags = {}, ev = {}) {
|
|
143
|
+
assertLocalFlag(flags, toDocevalsError);
|
|
144
|
+
return selectProvider(flags, [
|
|
145
|
+
{
|
|
146
|
+
...ev.provider !== void 0 ? { provider: ev.provider } : {},
|
|
147
|
+
...ev.model !== void 0 ? { model: ev.model } : {},
|
|
148
|
+
...ev.origin !== void 0 ? { origin: ev.origin } : {}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
...config.provider !== null ? { provider: config.provider } : {},
|
|
152
|
+
...config.model !== null ? { model: config.model } : {},
|
|
153
|
+
origin: CONFIG_KEY
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
...config.providers.provider !== void 0 ? { provider: config.providers.provider } : {},
|
|
157
|
+
...config.providers.model !== void 0 ? { model: config.providers.model } : {},
|
|
158
|
+
origin: "providers.provider"
|
|
159
|
+
}
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
function announceSelection(selection) {
|
|
163
|
+
const message = localNotice(selection);
|
|
164
|
+
if (message !== void 0) noticeOnce(message);
|
|
165
|
+
}
|
|
166
|
+
function assertProviderSelection(selection) {
|
|
167
|
+
assertKnownProvider(selection.provider, toDocevalsError);
|
|
168
|
+
assertModelHasProvider(selection.provider, selection.model, CONFIG_KEY, toDocevalsError);
|
|
169
|
+
}
|
|
170
|
+
function providerSpecFor2(config, selection) {
|
|
171
|
+
const spec = providerSpecFor(config.providers, selection);
|
|
172
|
+
switch (selection.provider) {
|
|
173
|
+
case "anthropic":
|
|
174
|
+
return { ...spec, anthropic: { toolName: "record_verdict" } };
|
|
175
|
+
case "openai":
|
|
176
|
+
return { ...spec, openai: { schemaName: "verdict" } };
|
|
177
|
+
default:
|
|
178
|
+
return spec;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function resolveProviderIdentity(config, flags = {}, ev = {}) {
|
|
182
|
+
const selection = selectProvider2(config, flags, ev);
|
|
183
|
+
const { provider, model } = selection;
|
|
184
|
+
assertKnownProvider(provider, toDocevalsError);
|
|
185
|
+
assertModelHasProvider(provider, model, CONFIG_KEY, toDocevalsError);
|
|
186
|
+
announceSelection(selection);
|
|
187
|
+
return resolveIdentity(providerSpecFor2(config, { provider, model: model ?? null }), toDocevalsError);
|
|
188
|
+
}
|
|
189
|
+
function constructProvider(config, identity) {
|
|
190
|
+
try {
|
|
191
|
+
return makeInferenceProvider(providerSpecFor2(config, identity));
|
|
192
|
+
} catch (e) {
|
|
193
|
+
throw new DocevalsError(errorMessage(e));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function makeProvider(config, flags = {}, ev = {}) {
|
|
197
|
+
return constructProvider(config, await resolveProviderIdentity(config, flags, ev));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/docevals/judge/judge.ts
|
|
201
|
+
import { resolve as resolvePath } from "node:path";
|
|
202
|
+
var verdictSchema = verdict_schema_default;
|
|
203
|
+
function makeJudge(deps) {
|
|
204
|
+
return async (targets, config, options) => {
|
|
205
|
+
const { provider, root } = deps;
|
|
206
|
+
const overridden = /* @__PURE__ */ new Map();
|
|
207
|
+
const buildProvider = deps.providerFor ?? ((ev) => makeProvider(config, options, ev));
|
|
208
|
+
const keyOf = (s) => `${s.provider}:${s.model ?? ""}`;
|
|
209
|
+
const defaultKey = keyOf(selectProvider2(config, options));
|
|
210
|
+
const providerFor = (ev, file) => {
|
|
211
|
+
const selection = selectProvider2(config, options, {
|
|
212
|
+
...ev.provider !== void 0 ? { provider: ev.provider } : {},
|
|
213
|
+
...ev.model !== void 0 ? { model: ev.model } : {},
|
|
214
|
+
origin: `eval "${ev.name}" in ${file}`
|
|
215
|
+
});
|
|
216
|
+
announceSelection(selection);
|
|
217
|
+
const key = keyOf(selection);
|
|
218
|
+
if (key === defaultKey) return Promise.resolve(provider);
|
|
219
|
+
let p = overridden.get(key);
|
|
220
|
+
if (p === void 0) {
|
|
221
|
+
p = (async () => {
|
|
222
|
+
assertProviderSelection(selection);
|
|
223
|
+
return buildProvider(ev);
|
|
224
|
+
})();
|
|
225
|
+
overridden.set(key, p);
|
|
226
|
+
}
|
|
227
|
+
return p;
|
|
228
|
+
};
|
|
229
|
+
const runsFor = (ev) => options.runs ?? ev.runs ?? config.judge.ensembleRuns;
|
|
230
|
+
const temperature = config.judge.temperature;
|
|
231
|
+
const chunkChars = options.chunkChars ?? config.judge.chunkChars;
|
|
232
|
+
const cache = new VerdictCache(
|
|
233
|
+
resolvePath(root, config.judge.cacheDir),
|
|
234
|
+
options.noCache !== true,
|
|
235
|
+
"manni-docevals"
|
|
236
|
+
);
|
|
237
|
+
const reviews = loadReviews(root);
|
|
238
|
+
const maxTurns = options.maxTurns ?? config.judge.maxTurns;
|
|
239
|
+
let turnsSpent = 0;
|
|
240
|
+
const results = [];
|
|
241
|
+
const configured = config.judge.concurrency;
|
|
242
|
+
const concurrency = Number.isInteger(configured) && configured >= 1 ? configured : config.defaults.concurrency;
|
|
243
|
+
let index = 0;
|
|
244
|
+
const judgeTarget = async (target) => {
|
|
245
|
+
const { plan, eval: ev } = target;
|
|
246
|
+
const start = Date.now();
|
|
247
|
+
const runsPerEval = runsFor(ev);
|
|
248
|
+
let judgeProvider;
|
|
249
|
+
try {
|
|
250
|
+
judgeProvider = await providerFor(ev, plan.page.file);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
if (!(e instanceof DocevalsError)) throw e;
|
|
253
|
+
return {
|
|
254
|
+
evalName: ev.name,
|
|
255
|
+
type: ev.type,
|
|
256
|
+
grader: ev.grader,
|
|
257
|
+
file: plan.page.file,
|
|
258
|
+
outcome: "error",
|
|
259
|
+
skipReason: e.message,
|
|
260
|
+
durationMs: Date.now() - start
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const preference = selfPreferenceOf(plan, ev, judgeProvider.modelName());
|
|
264
|
+
if (preference) warn(`${plan.page.file}: ${preference.message}`);
|
|
265
|
+
const selected = readTarget(ev.target, plan);
|
|
266
|
+
if (!selected.ok) {
|
|
267
|
+
return {
|
|
268
|
+
evalName: ev.name,
|
|
269
|
+
type: ev.type,
|
|
270
|
+
grader: ev.grader,
|
|
271
|
+
file: plan.page.file,
|
|
272
|
+
outcome: "error",
|
|
273
|
+
skipReason: selected.reason,
|
|
274
|
+
durationMs: Date.now() - start
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const chunks = splitBody(selected.text, chunkChars);
|
|
278
|
+
let judged = selected.text;
|
|
279
|
+
let judgedLabel = selected.label;
|
|
280
|
+
const key = cacheKey(
|
|
281
|
+
judgeProvider.provider(),
|
|
282
|
+
judgeProvider.modelName(),
|
|
283
|
+
runsPerEval,
|
|
284
|
+
temperature,
|
|
285
|
+
judgeCacheBody(chunkChars, selected.text),
|
|
286
|
+
ev
|
|
287
|
+
);
|
|
288
|
+
const cached = cache.get(key) !== void 0;
|
|
289
|
+
if (chunks.length > 1 && !cached) {
|
|
290
|
+
const gathered = [];
|
|
291
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
292
|
+
if (maxTurns != null && turnsSpent >= maxTurns) {
|
|
293
|
+
return {
|
|
294
|
+
evalName: ev.name,
|
|
295
|
+
type: ev.type,
|
|
296
|
+
grader: ev.grader,
|
|
297
|
+
file: plan.page.file,
|
|
298
|
+
outcome: "skipped",
|
|
299
|
+
skipReason: `${turnBudgetSkipReason(maxTurns)} (after ${String(i)} of ${String(chunks.length)} parts)`,
|
|
300
|
+
durationMs: Date.now() - start
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
turnsSpent += 1;
|
|
304
|
+
try {
|
|
305
|
+
const res = await judgeProvider.completeJSON({
|
|
306
|
+
system: EVIDENCE_SYSTEM_PROMPT,
|
|
307
|
+
user: buildEvidenceUser(ev, chunk, {
|
|
308
|
+
index: i,
|
|
309
|
+
total: chunks.length
|
|
310
|
+
}),
|
|
311
|
+
schema: EVIDENCE_SCHEMA,
|
|
312
|
+
temperature
|
|
313
|
+
});
|
|
314
|
+
const json = res.json;
|
|
315
|
+
gathered.push({
|
|
316
|
+
supports: json.supports ?? [],
|
|
317
|
+
contradicts: json.contradicts ?? []
|
|
318
|
+
});
|
|
319
|
+
} catch (e) {
|
|
320
|
+
return {
|
|
321
|
+
evalName: ev.name,
|
|
322
|
+
type: ev.type,
|
|
323
|
+
grader: ev.grader,
|
|
324
|
+
file: plan.page.file,
|
|
325
|
+
outcome: "error",
|
|
326
|
+
skipReason: `gathering evidence from part ${String(i + 1)} of ${String(chunks.length)} failed: ${errorMessage(e)}`,
|
|
327
|
+
durationMs: Date.now() - start
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
judged = renderEvidence(gathered, chunks.length);
|
|
332
|
+
judgedLabel = `${selected.label}, ${String(chunks.length)} parts`;
|
|
333
|
+
}
|
|
334
|
+
if (maxTurns != null && !cached) {
|
|
335
|
+
if (turnsSpent + runsPerEval > maxTurns) {
|
|
336
|
+
return {
|
|
337
|
+
evalName: ev.name,
|
|
338
|
+
type: ev.type,
|
|
339
|
+
grader: ev.grader,
|
|
340
|
+
file: plan.page.file,
|
|
341
|
+
outcome: "skipped",
|
|
342
|
+
skipReason: turnBudgetSkipReason(maxTurns),
|
|
343
|
+
durationMs: 0
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
turnsSpent += runsPerEval;
|
|
347
|
+
}
|
|
348
|
+
const runs = await runEnsemble({
|
|
349
|
+
provider: judgeProvider,
|
|
350
|
+
system: JUDGE_SYSTEM_PROMPT,
|
|
351
|
+
user: buildUserContent(ev, judged, judgedLabel),
|
|
352
|
+
runs: runsPerEval,
|
|
353
|
+
temperature,
|
|
354
|
+
schema: verdictSchema,
|
|
355
|
+
cache,
|
|
356
|
+
cacheKey: key,
|
|
357
|
+
label: "manni-docevals"
|
|
358
|
+
});
|
|
359
|
+
const consensusBase = computeConsensus(runs);
|
|
360
|
+
const zone = zoneFor(consensusBase, config.judge.zones);
|
|
361
|
+
const consensus = { ...consensusBase, zone };
|
|
362
|
+
let outcome = zone === "auto-pass" ? "pass" : zone === "auto-fail" ? "fail" : "needs-review";
|
|
363
|
+
let via;
|
|
364
|
+
if (outcome === "needs-review") {
|
|
365
|
+
const review = findReview(reviews, plan.page.file, ev.name, plan.page.body);
|
|
366
|
+
if (review) {
|
|
367
|
+
outcome = review.verdict;
|
|
368
|
+
via = "human-review";
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
evalName: ev.name,
|
|
373
|
+
type: ev.type,
|
|
374
|
+
grader: ev.grader,
|
|
375
|
+
file: plan.page.file,
|
|
376
|
+
outcome,
|
|
377
|
+
consensus,
|
|
378
|
+
via,
|
|
379
|
+
...preference ? { selfPreference: { axis: preference.axis, model: preference.model } } : {},
|
|
380
|
+
durationMs: Date.now() - start
|
|
381
|
+
};
|
|
382
|
+
};
|
|
383
|
+
const workers = Array.from(
|
|
384
|
+
{ length: Math.min(concurrency, targets.length) },
|
|
385
|
+
async () => {
|
|
386
|
+
while (index < targets.length) {
|
|
387
|
+
const i = index++;
|
|
388
|
+
const target = targets[i];
|
|
389
|
+
if (target === void 0) break;
|
|
390
|
+
results[i] = await judgeTarget(target);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
);
|
|
394
|
+
await Promise.all(workers);
|
|
395
|
+
return results;
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/docevals/graders/scriptgen.ts
|
|
400
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
401
|
+
import { basename, dirname as dirname2, extname, join as join2, relative, resolve as resolve2 } from "node:path";
|
|
402
|
+
|
|
403
|
+
// src/docevals/core/frontmatter-edit.ts
|
|
404
|
+
import { parseDocument, Document, YAMLSeq, isMap, isScalar } from "yaml";
|
|
405
|
+
var NAMESPACE = "docevals";
|
|
406
|
+
var BOM = "\uFEFF";
|
|
407
|
+
function splitYamlFrontmatter(content, path) {
|
|
408
|
+
const bom = content.startsWith(BOM) ? BOM : "";
|
|
409
|
+
const body = bom ? content.slice(1) : content;
|
|
410
|
+
const openMatch = /^---(\r?\n)/.exec(body);
|
|
411
|
+
if (!openMatch) {
|
|
412
|
+
throw new DocevalsError(
|
|
413
|
+
`${path}: no YAML frontmatter block to edit (only YAML frontmatter is editable)`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
const eol = openMatch[1] === "\r\n" ? "\r\n" : "\n";
|
|
417
|
+
const [openLine = "", ...rest] = body.split(/(?<=\n)/);
|
|
418
|
+
let offset = openLine.length;
|
|
419
|
+
for (const line of rest) {
|
|
420
|
+
const stripped = line.replace(/\r?\n$/, "");
|
|
421
|
+
if (stripped === "---" || stripped === "...") {
|
|
422
|
+
const blockEnd = offset;
|
|
423
|
+
return {
|
|
424
|
+
open: bom + openLine,
|
|
425
|
+
block: body.slice(openLine.length, blockEnd),
|
|
426
|
+
suffix: body.slice(blockEnd),
|
|
427
|
+
eol
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
offset += line.length;
|
|
431
|
+
}
|
|
432
|
+
throw new DocevalsError(`${path}: unterminated frontmatter block`);
|
|
433
|
+
}
|
|
434
|
+
function applyUpdates(doc, node, updates) {
|
|
435
|
+
if (updates.grader !== void 0) node.set("grader", updates.grader);
|
|
436
|
+
if (updates.command !== void 0) {
|
|
437
|
+
const seq = doc.createNode(updates.command);
|
|
438
|
+
seq.flow = true;
|
|
439
|
+
node.set("command", seq);
|
|
440
|
+
}
|
|
441
|
+
const hash = updates["generated-assertion-hash"];
|
|
442
|
+
if (hash !== void 0) node.set("generated-assertion-hash", hash);
|
|
443
|
+
}
|
|
444
|
+
function evalSeq(doc) {
|
|
445
|
+
const node = doc.get("evals", true);
|
|
446
|
+
return node instanceof YAMLSeq ? node : void 0;
|
|
447
|
+
}
|
|
448
|
+
function findEvalNode(doc, evalName) {
|
|
449
|
+
const evals = evalSeq(doc);
|
|
450
|
+
if (!evals) return void 0;
|
|
451
|
+
for (const item of evals.items) {
|
|
452
|
+
if (isMap(item)) {
|
|
453
|
+
const name = item.get("id") ?? item.get("use");
|
|
454
|
+
if (name === evalName) return item;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return void 0;
|
|
458
|
+
}
|
|
459
|
+
function updatePageEval(content, path, evalName, updates) {
|
|
460
|
+
const { open, block, suffix, eol } = splitYamlFrontmatter(content, path);
|
|
461
|
+
const doc = parseDocument(block);
|
|
462
|
+
if (doc.errors.length > 0) {
|
|
463
|
+
throw new DocevalsError(
|
|
464
|
+
`${path}: cannot edit frontmatter \u2014 ${doc.errors[0]?.message ?? "parse error"}`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const node = findEvalNode(doc, evalName);
|
|
468
|
+
if (!node) {
|
|
469
|
+
throw new DocevalsError(
|
|
470
|
+
`${path}: eval "${evalName}" not found in frontmatter`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
applyUpdates(doc, node, updates);
|
|
474
|
+
let newBlock = doc.toString();
|
|
475
|
+
if (eol === "\r\n") newBlock = newBlock.replace(/(?<!\r)\n/g, "\r\n");
|
|
476
|
+
return open + newBlock + suffix;
|
|
477
|
+
}
|
|
478
|
+
function updateConfigEval(configText, configPath, evalName, updates) {
|
|
479
|
+
const doc = parseDocument(configText);
|
|
480
|
+
if (doc.errors.length > 0) {
|
|
481
|
+
throw new DocevalsError(
|
|
482
|
+
`${configPath}: cannot edit config \u2014 ${doc.errors[0]?.message ?? "parse error"}`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
const node = doc.getIn([NAMESPACE, "evals", evalName]);
|
|
486
|
+
if (!isMap(node)) {
|
|
487
|
+
throw new DocevalsError(
|
|
488
|
+
`${configPath}: eval "${evalName}" not found in config`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
applyUpdates(doc, node, updates);
|
|
492
|
+
return doc.toString();
|
|
493
|
+
}
|
|
494
|
+
function entryObject(entry) {
|
|
495
|
+
const obj = { id: entry.id, assertion: entry.assertion };
|
|
496
|
+
if (entry.type !== void 0) obj.type = entry.type;
|
|
497
|
+
if (entry.grader !== void 0) obj.grader = entry.grader;
|
|
498
|
+
if (entry.evidence !== void 0) obj.evidence = entry.evidence;
|
|
499
|
+
if (entry.examples !== void 0) obj.examples = entry.examples;
|
|
500
|
+
if (entry.severity !== void 0) obj.severity = entry.severity;
|
|
501
|
+
return obj;
|
|
502
|
+
}
|
|
503
|
+
var META_PROVENANCE_KEY = "meta-provenance";
|
|
504
|
+
function assertNoCollision(seq, name, path) {
|
|
505
|
+
for (const item of seq.items) {
|
|
506
|
+
const existing = isMap(item) ? item.get("id") ?? item.get("use") : isScalar(item) ? item.value : void 0;
|
|
507
|
+
if (existing === name) {
|
|
508
|
+
throw new DocevalsError(
|
|
509
|
+
`${path}: eval "${name}" already exists in frontmatter`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function appendPageEvals(content, path, entries, metaProvenance) {
|
|
515
|
+
const format = leadingFrontmatterFormat(content);
|
|
516
|
+
if (format === "toml" || format === "json") {
|
|
517
|
+
throw new DocevalsError(
|
|
518
|
+
`${path}: only YAML frontmatter can be edited (found ${format} frontmatter)`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
const bom = content.startsWith(BOM) ? BOM : "";
|
|
522
|
+
const stripped = bom ? content.slice(1) : content;
|
|
523
|
+
const eol = stripped.includes("\r\n") ? "\r\n" : "\n";
|
|
524
|
+
if (format === void 0) {
|
|
525
|
+
const doc2 = new Document(
|
|
526
|
+
entries.length === 0 ? {} : { evals: entries.map(entryObject) }
|
|
527
|
+
);
|
|
528
|
+
if (metaProvenance) doc2.set(META_PROVENANCE_KEY, doc2.createNode(metaProvenance));
|
|
529
|
+
let block2 = doc2.toString();
|
|
530
|
+
if (eol === "\r\n") block2 = block2.replace(/(?<!\r)\n/g, "\r\n");
|
|
531
|
+
return `${bom}---${eol}${block2}---${eol}${stripped}`;
|
|
532
|
+
}
|
|
533
|
+
const { open, block, suffix, eol: blockEol } = splitYamlFrontmatter(content, path);
|
|
534
|
+
const doc = parseDocument(block);
|
|
535
|
+
if (doc.errors.length > 0) {
|
|
536
|
+
throw new DocevalsError(
|
|
537
|
+
`${path}: cannot edit frontmatter \u2014 ${doc.errors[0]?.message ?? "parse error"}`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
if (entries.length === 0) {
|
|
541
|
+
if (metaProvenance) doc.set(META_PROVENANCE_KEY, doc.createNode(metaProvenance));
|
|
542
|
+
let onlyEntry = doc.toString();
|
|
543
|
+
if (blockEol === "\r\n") onlyEntry = onlyEntry.replace(/(?<!\r)\n/g, "\r\n");
|
|
544
|
+
return open + onlyEntry + suffix;
|
|
545
|
+
}
|
|
546
|
+
let seq = evalSeq(doc);
|
|
547
|
+
if (!seq) {
|
|
548
|
+
const node = doc.get("evals", true);
|
|
549
|
+
if (node !== void 0) {
|
|
550
|
+
throw new DocevalsError(
|
|
551
|
+
`${path}: the evals key in frontmatter is not a list \u2014 expand the string shorthand into a list before appending`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
seq = doc.createNode([]);
|
|
555
|
+
doc.set("evals", seq);
|
|
556
|
+
}
|
|
557
|
+
for (const entry of entries) {
|
|
558
|
+
assertNoCollision(seq, entry.id, path);
|
|
559
|
+
seq.add(doc.createNode(entryObject(entry)));
|
|
560
|
+
}
|
|
561
|
+
if (metaProvenance) doc.set(META_PROVENANCE_KEY, doc.createNode(metaProvenance));
|
|
562
|
+
let newBlock = doc.toString();
|
|
563
|
+
if (blockEol === "\r\n") newBlock = newBlock.replace(/(?<!\r)\n/g, "\r\n");
|
|
564
|
+
return open + newBlock + suffix;
|
|
565
|
+
}
|
|
566
|
+
function hasEditableEval(content, evalName) {
|
|
567
|
+
try {
|
|
568
|
+
const { block } = splitYamlFrontmatter(content, "");
|
|
569
|
+
const doc = parseDocument(block);
|
|
570
|
+
return findEvalNode(doc, evalName) !== void 0;
|
|
571
|
+
} catch {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/docevals/core/write-location.ts
|
|
577
|
+
import { resolve } from "node:path";
|
|
578
|
+
var EVALS_KEY = "evals";
|
|
579
|
+
var META_PROVENANCE_KEY2 = "meta-provenance";
|
|
580
|
+
function prefersExternal(key) {
|
|
581
|
+
const properties = frontmatterSchema.properties;
|
|
582
|
+
if (typeof properties !== "object" || properties === null) return false;
|
|
583
|
+
const property = properties[key];
|
|
584
|
+
if (typeof property !== "object" || property === null) return false;
|
|
585
|
+
return property[LOCATION_KEYWORD] === "external";
|
|
586
|
+
}
|
|
587
|
+
function noEntryMessage(label, join3, file, key) {
|
|
588
|
+
return `${label} carries no ${join3}, which ${file} joins on, so its ${key} has no entry there.`;
|
|
589
|
+
}
|
|
590
|
+
var EvalWriter = class _EvalWriter {
|
|
591
|
+
constructor(ctx, configSource) {
|
|
592
|
+
this.ctx = ctx;
|
|
593
|
+
this.configSource = configSource;
|
|
594
|
+
}
|
|
595
|
+
ctx;
|
|
596
|
+
configSource;
|
|
597
|
+
landed = [];
|
|
598
|
+
/**
|
|
599
|
+
* Build the writer for a run. `targets` are the positional paths as typed,
|
|
600
|
+
* which is where a page in no collection gets its proposed home from; a run
|
|
601
|
+
* that took its paths from the collections passes none.
|
|
602
|
+
*
|
|
603
|
+
* The context is built from the config docevals already loaded rather than
|
|
604
|
+
* by loading it again through meta. A `manni.config.yaml` carrying only a
|
|
605
|
+
* `docevals:` key is a single-tool file to the metadata tool, which refuses
|
|
606
|
+
* it; docevals reads it every run, and where its own evals go cannot depend
|
|
607
|
+
* on whether a sibling can read the same file. The collections are the
|
|
608
|
+
* declared ones, since a page named by path is still a member of whatever
|
|
609
|
+
* contains it. `config: null` leaves the page's schema set to the page,
|
|
610
|
+
* which is enough: the keys this writer homes are named outright in the
|
|
611
|
+
* relocation it plans.
|
|
612
|
+
*/
|
|
613
|
+
static for(config, cwd, targets) {
|
|
614
|
+
const noConfig = config.configSource === null;
|
|
615
|
+
const collections = config.collections;
|
|
616
|
+
return new _EvalWriter(
|
|
617
|
+
{
|
|
618
|
+
cwd,
|
|
619
|
+
// Page labels are relative to the run's discovery root, so a manifest
|
|
620
|
+
// path this writer reports is spelled against the same directory
|
|
621
|
+
// `core/external.ts` reports a merged value's source against.
|
|
622
|
+
base: cwd,
|
|
623
|
+
noConfig,
|
|
624
|
+
config: null,
|
|
625
|
+
...noConfig ? {} : { configDir: config.configDir, configPath: config.configPath },
|
|
626
|
+
collections,
|
|
627
|
+
declaredCollections: collections,
|
|
628
|
+
targets,
|
|
629
|
+
validator: new Validator()
|
|
630
|
+
},
|
|
631
|
+
config.configSource ?? cwd
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
/** Where `key` of the page labelled `label` goes. `data` is its metadata. */
|
|
635
|
+
async homeFor(label, data, key) {
|
|
636
|
+
const home = await keyHome(this.ctx, label, data, key);
|
|
637
|
+
if (home.kind === "url") {
|
|
638
|
+
return { kind: "url", collection: home.collection, file: home.file };
|
|
639
|
+
}
|
|
640
|
+
if (home.kind === "manifest") {
|
|
641
|
+
if (home.entry === void 0) {
|
|
642
|
+
return {
|
|
643
|
+
kind: "no-entry",
|
|
644
|
+
message: noEntryMessage(label, home.join, home.file, key)
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
kind: "manifest",
|
|
649
|
+
file: home.file,
|
|
650
|
+
absPath: home.absPath,
|
|
651
|
+
join: home.join,
|
|
652
|
+
entry: home.entry,
|
|
653
|
+
perPage: home.perPage
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
return { kind: "page", prefersExternal: prefersExternal(key), proposed: home.home };
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* The refusal for an eval key a URL manifest owns, in chunk 1's wording. A
|
|
660
|
+
* run reaches it only where the load-time refusal could not: a manifest
|
|
661
|
+
* declared for a collection whose pages this run never merged.
|
|
662
|
+
*/
|
|
663
|
+
urlRefusal(home, key) {
|
|
664
|
+
return new DocevalsError(urlManifestRefusal(this.configSource, home.collection, key));
|
|
665
|
+
}
|
|
666
|
+
/** Record that `key` stayed on the page, for the W1 and W2 lines. */
|
|
667
|
+
stayedOnPage(label, key, proposed) {
|
|
668
|
+
this.landed.push({ label, key, home: proposed });
|
|
669
|
+
}
|
|
670
|
+
/** The W1 and W2 lines for everything that stayed on a page. Without the prefix. */
|
|
671
|
+
warnings(dryRun) {
|
|
672
|
+
return this.landed.length === 0 ? [] : externalWriteWarnings(this.landed, dryRun);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Ask P1 once per collection the flagged writes would be homed in. Returns
|
|
676
|
+
* true when a relocation was applied, which leaves the caller's config,
|
|
677
|
+
* pages and this writer stale: rebuild all three before writing.
|
|
678
|
+
*/
|
|
679
|
+
async offer(writes, hooks) {
|
|
680
|
+
if (writes.length === 0) return false;
|
|
681
|
+
const applied = await offerExternalHomes(this.ctx, writes, hooks);
|
|
682
|
+
return applied.size > 0;
|
|
683
|
+
}
|
|
684
|
+
/** The value the manifest holds for this page's entry, or `undefined`. */
|
|
685
|
+
async readManifest(home, key) {
|
|
686
|
+
return readManifestValue(await this.text(home.absPath, home.perPage), {
|
|
687
|
+
entry: home.entry,
|
|
688
|
+
key,
|
|
689
|
+
join: home.join
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
/** Write `value` into this page's entry, changing no other byte of the file. */
|
|
693
|
+
async writeManifest(home, key, value) {
|
|
694
|
+
const text = await this.text(home.absPath, home.perPage);
|
|
695
|
+
let spliced;
|
|
696
|
+
try {
|
|
697
|
+
spliced = spliceManifestValue(text, {
|
|
698
|
+
entry: home.entry,
|
|
699
|
+
key,
|
|
700
|
+
value,
|
|
701
|
+
join: home.join,
|
|
702
|
+
file: home.file
|
|
703
|
+
}).text;
|
|
704
|
+
} catch (e) {
|
|
705
|
+
throw new DocevalsError(errorMessage(e));
|
|
706
|
+
}
|
|
707
|
+
if (spliced !== text) {
|
|
708
|
+
await writeFileAtomic(home.absPath, spliced, { createParents: true });
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* The manifest's bytes. A per-page manifest that is not on disk yet is a
|
|
713
|
+
* page with nothing recorded, held as empty text and created by the write,
|
|
714
|
+
* exactly as `manni meta fill` holds one. A missing concrete manifest is
|
|
715
|
+
* refused, as it always was.
|
|
716
|
+
*/
|
|
717
|
+
async text(absPath, perPage) {
|
|
718
|
+
try {
|
|
719
|
+
return await readManifestOrEmpty(absPath, perPage);
|
|
720
|
+
} catch (e) {
|
|
721
|
+
throw new DocevalsError(
|
|
722
|
+
`${resolve(absPath)} could not be read: ${errorMessage(e)}`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
function manifestEvalList(value, file, entry) {
|
|
728
|
+
if (value === void 0 || value === null) return [];
|
|
729
|
+
if (Array.isArray(value)) return [...value];
|
|
730
|
+
throw new DocevalsError(
|
|
731
|
+
`${file}: the evals key of ${entry} is not a list \u2014 expand the string shorthand into a list before appending`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
function updateManifestEval(list, name, updates) {
|
|
735
|
+
const index = list.findIndex((item) => named(item) === name);
|
|
736
|
+
const held = index === -1 ? void 0 : list[index];
|
|
737
|
+
if (held === void 0) return void 0;
|
|
738
|
+
const set = { ...held };
|
|
739
|
+
if (updates.grader !== void 0) set.grader = updates.grader;
|
|
740
|
+
if (updates.command !== void 0) set.command = updates.command;
|
|
741
|
+
const hash = updates["generated-assertion-hash"];
|
|
742
|
+
if (hash !== void 0) set["generated-assertion-hash"] = hash;
|
|
743
|
+
const next = [...list];
|
|
744
|
+
next[index] = set;
|
|
745
|
+
return next;
|
|
746
|
+
}
|
|
747
|
+
function named(item) {
|
|
748
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) return void 0;
|
|
749
|
+
const entry = item;
|
|
750
|
+
const id = entry.id ?? entry.use;
|
|
751
|
+
return typeof id === "string" ? id : void 0;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/docevals/graders/scriptgen.ts
|
|
755
|
+
var SCRIPTGEN_SYSTEM_PROMPT = [
|
|
756
|
+
"You write small, self-contained Node.js check scripts that verify a",
|
|
757
|
+
"deterministic assertion about a documentation page.",
|
|
758
|
+
"",
|
|
759
|
+
"Contract for every script:",
|
|
760
|
+
"- ES module (.mjs) using only Node.js built-in modules. No dependencies.",
|
|
761
|
+
"- The page's absolute path arrives as process.argv[2] (also MANNI_DOCEVALS_FILE).",
|
|
762
|
+
"- Exit 0 when the assertion holds, 1 when it fails, 2 on operational error.",
|
|
763
|
+
"- On failure, print a short human-readable reason to stderr.",
|
|
764
|
+
"- Deterministic: no network access, no spawning processes, no randomness.",
|
|
765
|
+
"- The page content may start with a YAML frontmatter block fenced by ---",
|
|
766
|
+
" lines; account for it when checking body content.",
|
|
767
|
+
"- Prefer simple, reviewable logic (regex/string checks) over cleverness.",
|
|
768
|
+
'Respond with JSON: { "code": "<the full script source>" }.'
|
|
769
|
+
].join("\n");
|
|
770
|
+
var SCRIPT_SCHEMA = {
|
|
771
|
+
type: "object",
|
|
772
|
+
required: ["code"],
|
|
773
|
+
properties: {
|
|
774
|
+
code: { type: "string", minLength: 1 },
|
|
775
|
+
notes: { type: "string" }
|
|
776
|
+
},
|
|
777
|
+
additionalProperties: false
|
|
778
|
+
};
|
|
779
|
+
function buildScriptgenUser(assertion, evalName, file, body) {
|
|
780
|
+
return [
|
|
781
|
+
`# Assertion to check`,
|
|
782
|
+
assertion,
|
|
783
|
+
"",
|
|
784
|
+
`# Eval name`,
|
|
785
|
+
evalName,
|
|
786
|
+
"",
|
|
787
|
+
`# Page path`,
|
|
788
|
+
file,
|
|
789
|
+
"",
|
|
790
|
+
"# Current page content (for grounding \u2014 the script must express the",
|
|
791
|
+
"# assertion generally, not hardcode this exact content)",
|
|
792
|
+
"",
|
|
793
|
+
body
|
|
794
|
+
].join("\n");
|
|
795
|
+
}
|
|
796
|
+
function scriptLocationFor(target, config, root) {
|
|
797
|
+
const ev = target.eval;
|
|
798
|
+
const page = target.plan.page;
|
|
799
|
+
if (ev.source === "page") {
|
|
800
|
+
const pageDir = dirname2(page.absPath);
|
|
801
|
+
const pattern = config.scripts.dir;
|
|
802
|
+
const dir2 = pattern.includes("{docDir}") ? pattern.replaceAll("{docDir}", pageDir) : resolve2(root, pattern);
|
|
803
|
+
const base = basename(page.absPath, extname(page.absPath));
|
|
804
|
+
const scriptAbsPath2 = join2(dir2, `${base}.${ev.name}.mjs`);
|
|
805
|
+
const rel2 = relative(pageDir, scriptAbsPath2).replace(/\\/g, "/");
|
|
806
|
+
return { scriptAbsPath: scriptAbsPath2, command: ["node", rel2, "{file}"] };
|
|
807
|
+
}
|
|
808
|
+
const dir = resolve2(config.configDir, config.scripts.configDir);
|
|
809
|
+
const scriptAbsPath = join2(dir, `${ev.name}.mjs`);
|
|
810
|
+
const rel = relative(config.configDir, scriptAbsPath).replace(/\\/g, "/");
|
|
811
|
+
return { scriptAbsPath, command: ["node", rel, "{file}"] };
|
|
812
|
+
}
|
|
813
|
+
function header(assertion, evalName) {
|
|
814
|
+
return [
|
|
815
|
+
"// manni docevals generated check",
|
|
816
|
+
`// Eval: ${evalName}`,
|
|
817
|
+
`// Assertion: ${assertion.replace(/\s+/g, " ").trim()}`,
|
|
818
|
+
"// Exit 0 = pass, 1 = fail, 2 = operational error.",
|
|
819
|
+
""
|
|
820
|
+
].join("\n");
|
|
821
|
+
}
|
|
822
|
+
function makeGenerateScripts(deps) {
|
|
823
|
+
return async (targets, config, _options) => {
|
|
824
|
+
const generatedPaths = [];
|
|
825
|
+
const refusals = [];
|
|
826
|
+
const doneConfigEvals = /* @__PURE__ */ new Set();
|
|
827
|
+
let provider;
|
|
828
|
+
let writer;
|
|
829
|
+
const getWriter = () => writer ??= EvalWriter.for(config, deps.root, []);
|
|
830
|
+
for (const target of targets) {
|
|
831
|
+
const ev = target.eval;
|
|
832
|
+
if (!ev.assertion) continue;
|
|
833
|
+
if (ev.source === "config" && doneConfigEvals.has(ev.name)) continue;
|
|
834
|
+
const page = target.plan.page;
|
|
835
|
+
let home;
|
|
836
|
+
if (ev.source === "page") {
|
|
837
|
+
home = await getWriter().homeFor(
|
|
838
|
+
page.file,
|
|
839
|
+
page.frontmatter.data,
|
|
840
|
+
EVALS_KEY
|
|
841
|
+
);
|
|
842
|
+
if (home.kind === "url") {
|
|
843
|
+
throw getWriter().urlRefusal(home, EVALS_KEY);
|
|
844
|
+
}
|
|
845
|
+
if (home.kind === "no-entry") {
|
|
846
|
+
refusals.push({ file: page.file, evalName: ev.name, message: home.message });
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (provider === void 0) {
|
|
851
|
+
try {
|
|
852
|
+
provider = typeof deps.provider === "function" ? await deps.provider() : deps.provider;
|
|
853
|
+
} catch (e) {
|
|
854
|
+
if (!(e instanceof DocevalsError)) throw e;
|
|
855
|
+
return { generatedPaths, refusals, unavailable: e.message };
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const location = scriptLocationFor(target, config, deps.root);
|
|
859
|
+
let code;
|
|
860
|
+
try {
|
|
861
|
+
const response = await provider.completeJSON({
|
|
862
|
+
system: SCRIPTGEN_SYSTEM_PROMPT,
|
|
863
|
+
user: buildScriptgenUser(
|
|
864
|
+
ev.assertion,
|
|
865
|
+
ev.name,
|
|
866
|
+
target.plan.page.file,
|
|
867
|
+
target.plan.page.body
|
|
868
|
+
),
|
|
869
|
+
schema: SCRIPT_SCHEMA,
|
|
870
|
+
temperature: 0
|
|
871
|
+
});
|
|
872
|
+
const json = response.json;
|
|
873
|
+
if (typeof json.code !== "string" || json.code.length === 0) {
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
code = json.code;
|
|
877
|
+
} catch {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
mkdirSync2(dirname2(location.scriptAbsPath), { recursive: true });
|
|
881
|
+
writeFileSync2(
|
|
882
|
+
location.scriptAbsPath,
|
|
883
|
+
// Exactly one final newline, whatever the model sent: the script is
|
|
884
|
+
// committed and reviewed, and a missing one shows in every diff.
|
|
885
|
+
`${header(ev.assertion, ev.name)}${code.trim()}
|
|
886
|
+
`
|
|
887
|
+
);
|
|
888
|
+
generatedPaths.push(
|
|
889
|
+
relative(deps.root, location.scriptAbsPath).replace(/\\/g, "/")
|
|
890
|
+
);
|
|
891
|
+
const updates = {
|
|
892
|
+
command: location.command,
|
|
893
|
+
"generated-assertion-hash": sha256(ev.assertion)
|
|
894
|
+
};
|
|
895
|
+
if (ev.source === "page" && home?.kind === "manifest") {
|
|
896
|
+
const at = getWriter();
|
|
897
|
+
const list = manifestEvalList(
|
|
898
|
+
await at.readManifest(home, EVALS_KEY),
|
|
899
|
+
home.file,
|
|
900
|
+
home.entry
|
|
901
|
+
);
|
|
902
|
+
const next = updateManifestEval(list, ev.name, updates);
|
|
903
|
+
if (next === void 0) {
|
|
904
|
+
throw new DocevalsError(
|
|
905
|
+
`${home.file}: eval "${ev.name}" not found in the entry for ${home.entry}`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
await at.writeManifest(home, EVALS_KEY, next);
|
|
909
|
+
} else if (ev.source === "page") {
|
|
910
|
+
const abs = page.absPath;
|
|
911
|
+
const updated = updatePageEval(
|
|
912
|
+
readFileSync2(abs, "utf8"),
|
|
913
|
+
page.file,
|
|
914
|
+
ev.name,
|
|
915
|
+
updates
|
|
916
|
+
);
|
|
917
|
+
writeFileSync2(abs, updated);
|
|
918
|
+
} else {
|
|
919
|
+
const updated = updateConfigEval(
|
|
920
|
+
readFileSync2(config.configPath, "utf8"),
|
|
921
|
+
config.configPath,
|
|
922
|
+
ev.name,
|
|
923
|
+
updates
|
|
924
|
+
);
|
|
925
|
+
writeFileSync2(config.configPath, updated);
|
|
926
|
+
doneConfigEvals.add(ev.name);
|
|
927
|
+
}
|
|
928
|
+
if (ev.source === "config") {
|
|
929
|
+
for (const t of targets) {
|
|
930
|
+
if (t.eval.name === ev.name && t.eval.source === "config") {
|
|
931
|
+
t.eval.command = scriptLocationFor(t, config, deps.root).command;
|
|
932
|
+
t.eval.generatedAssertionHash = updates["generated-assertion-hash"];
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
} else {
|
|
936
|
+
target.eval.command = location.command;
|
|
937
|
+
target.eval.generatedAssertionHash = updates["generated-assertion-hash"];
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return { generatedPaths, refusals };
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// src/docevals/commands/run.ts
|
|
945
|
+
function asGrants(values) {
|
|
946
|
+
if (values === void 0) return void 0;
|
|
947
|
+
const unknown = values.filter((v) => !isExecutionGrant(v));
|
|
948
|
+
if (unknown.length > 0) {
|
|
949
|
+
throw new DocevalsError(unknownGrantsMessage(unknown));
|
|
950
|
+
}
|
|
951
|
+
return values;
|
|
952
|
+
}
|
|
953
|
+
async function runRun(paths, options = {}, engineOverrides = {}) {
|
|
954
|
+
const cwd = options.cwd ?? process.cwd();
|
|
955
|
+
const judgeOptions = {
|
|
956
|
+
provider: options.provider,
|
|
957
|
+
model: options.model,
|
|
958
|
+
local: options.local,
|
|
959
|
+
runs: options.runs,
|
|
960
|
+
chunkChars: options.chunkChars,
|
|
961
|
+
noCache: options.cache === false,
|
|
962
|
+
maxTurns: options.maxTurns ?? null
|
|
963
|
+
};
|
|
964
|
+
const config = loadRunConfig(runConfigOptions(paths, options), cwd);
|
|
965
|
+
assertProviderSelection(selectProvider2(config, judgeOptions));
|
|
966
|
+
let judge;
|
|
967
|
+
let generateScripts;
|
|
968
|
+
let resolving;
|
|
969
|
+
const provider = () => resolving ??= makeProvider(config, judgeOptions);
|
|
970
|
+
const wantsJudge = !("judge" in engineOverrides);
|
|
971
|
+
const wantsGeneration = !("generateScripts" in engineOverrides);
|
|
972
|
+
if (wantsJudge || wantsGeneration) {
|
|
973
|
+
if (!options.deterministicOnly) {
|
|
974
|
+
try {
|
|
975
|
+
const resolved = await provider();
|
|
976
|
+
if (wantsJudge) judge = makeJudge({ provider: resolved, root: cwd });
|
|
977
|
+
if (wantsGeneration && options.generate !== false) {
|
|
978
|
+
generateScripts = makeGenerateScripts({ provider: resolved, root: cwd });
|
|
979
|
+
}
|
|
980
|
+
} catch (e) {
|
|
981
|
+
if (options.aiOnly || !(e instanceof DocevalsError)) throw e;
|
|
982
|
+
warn(`provider unavailable \u2014 ${e.message}. Running deterministic evals only.`);
|
|
983
|
+
}
|
|
984
|
+
} else if (wantsGeneration && options.generate !== false) {
|
|
985
|
+
generateScripts = makeGenerateScripts({ provider, root: cwd });
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return runEvals({
|
|
989
|
+
judge,
|
|
990
|
+
generateScripts,
|
|
991
|
+
config,
|
|
992
|
+
configPath: options.config,
|
|
993
|
+
noConfig: options.noConfig,
|
|
994
|
+
paths,
|
|
995
|
+
collection: options.collection,
|
|
996
|
+
exclude: options.exclude,
|
|
997
|
+
cwd: options.cwd,
|
|
998
|
+
deterministicOnly: options.deterministicOnly,
|
|
999
|
+
aiOnly: options.aiOnly,
|
|
1000
|
+
allowExecution: asGrants(options.allowExecution),
|
|
1001
|
+
execution: options.execution,
|
|
1002
|
+
generate: options.generate,
|
|
1003
|
+
failOnReview: options.failOnReview,
|
|
1004
|
+
evalNames: options.evalNames,
|
|
1005
|
+
suite: options.suite,
|
|
1006
|
+
since: options.since,
|
|
1007
|
+
newerThan: options.newerThan,
|
|
1008
|
+
baseline: options.baseline,
|
|
1009
|
+
writeBaseline: options.writeBaseline,
|
|
1010
|
+
toolVersion: options.toolVersion,
|
|
1011
|
+
judgeOptions,
|
|
1012
|
+
...engineOverrides
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
export {
|
|
1017
|
+
loadReviews,
|
|
1018
|
+
recordReview,
|
|
1019
|
+
selectProvider2 as selectProvider,
|
|
1020
|
+
assertProviderSelection,
|
|
1021
|
+
providerSpecFor2 as providerSpecFor,
|
|
1022
|
+
resolveProviderIdentity,
|
|
1023
|
+
constructProvider,
|
|
1024
|
+
makeProvider,
|
|
1025
|
+
makeJudge,
|
|
1026
|
+
updatePageEval,
|
|
1027
|
+
updateConfigEval,
|
|
1028
|
+
entryObject,
|
|
1029
|
+
appendPageEvals,
|
|
1030
|
+
hasEditableEval,
|
|
1031
|
+
EVALS_KEY,
|
|
1032
|
+
META_PROVENANCE_KEY2 as META_PROVENANCE_KEY,
|
|
1033
|
+
EvalWriter,
|
|
1034
|
+
manifestEvalList,
|
|
1035
|
+
updateManifestEval,
|
|
1036
|
+
SCRIPTGEN_SYSTEM_PROMPT,
|
|
1037
|
+
buildScriptgenUser,
|
|
1038
|
+
scriptLocationFor,
|
|
1039
|
+
makeGenerateScripts,
|
|
1040
|
+
runRun
|
|
1041
|
+
};
|
|
1042
|
+
//# sourceMappingURL=chunk-37LP5HMZ.js.map
|