@hawkeyexl/manni 4.5.0 → 4.7.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 +1 -1
- package/dist/batch-U6S2JUUN.js +41 -0
- package/dist/build-4RN6WBCP.js +32 -0
- package/dist/calibrate-6VIZYR4L.js +36 -0
- package/dist/capture-DP3NXAZE.js +34 -0
- package/dist/{check-RQIIIURI.js → check-ERVNGKLC.js} +13 -14
- package/dist/check-IWHUPAZM.js +886 -0
- package/dist/check-IWHUPAZM.js.map +1 -0
- package/dist/{check-ESNS7ZBC.js → check-OAJWC3WD.js} +7 -5
- package/dist/check-STHXZIZO.js +26 -0
- package/dist/{chunk-TA42STP7.js → chunk-2ZWTSRE5.js} +4 -4
- package/dist/{chunk-F2OVHJNC.js → chunk-4Z2MN5FR.js} +11 -11
- package/dist/{chunk-HVFR66GQ.js → chunk-5UB47BEV.js} +3280 -283
- package/dist/chunk-5UB47BEV.js.map +1 -0
- package/dist/{chunk-TCC737IS.js → chunk-5VWDIWOH.js} +4 -4
- package/dist/{chunk-B7XUEQF3.js → chunk-6KKZXB2O.js} +7 -5
- package/dist/{chunk-B7XUEQF3.js.map → chunk-6KKZXB2O.js.map} +1 -1
- package/dist/{chunk-ELZ2IIOF.js → chunk-6URSCTTX.js} +2 -2
- package/dist/{chunk-K3U5F2QT.js → chunk-6VDBP5UV.js} +12 -12
- package/dist/chunk-7EL5LLKD.js +233 -0
- package/dist/chunk-7EL5LLKD.js.map +1 -0
- package/dist/{chunk-C3KI45RF.js → chunk-7XZ6INKO.js} +4 -4
- package/dist/{chunk-ZX43SRZR.js → chunk-AMEILOVV.js} +8 -6
- package/dist/{chunk-ZX43SRZR.js.map → chunk-AMEILOVV.js.map} +1 -1
- package/dist/{chunk-SNEHX4VE.js → chunk-B5CMM57M.js} +2 -2
- package/dist/chunk-BWCWTHYS.js +146 -0
- package/dist/chunk-BWCWTHYS.js.map +1 -0
- package/dist/{chunk-QLUMKWQQ.js → chunk-C2TJG5KV.js} +10 -6
- package/dist/{chunk-QLUMKWQQ.js.map → chunk-C2TJG5KV.js.map} +1 -1
- package/dist/{chunk-GAZFKZHM.js → chunk-CEVU3GFF.js} +6 -6
- package/dist/chunk-CI55ABCK.js +502 -0
- package/dist/chunk-CI55ABCK.js.map +1 -0
- package/dist/{chunk-QCBICNKC.js → chunk-CR3HZERW.js} +202 -54
- package/dist/chunk-CR3HZERW.js.map +1 -0
- package/dist/chunk-CU57HT2E.js +116 -0
- package/dist/chunk-CU57HT2E.js.map +1 -0
- package/dist/chunk-CWXZ2MRT.js +222 -0
- package/dist/chunk-CWXZ2MRT.js.map +1 -0
- package/dist/chunk-D2LBN44V.js +134 -0
- package/dist/chunk-D2LBN44V.js.map +1 -0
- package/dist/chunk-D6RO54QD.js +674 -0
- package/dist/chunk-D6RO54QD.js.map +1 -0
- package/dist/{chunk-52PDIZKV.js → chunk-ECVQNGTH.js} +3 -3
- package/dist/chunk-FQGLRJGX.js +174 -0
- package/dist/chunk-FQGLRJGX.js.map +1 -0
- package/dist/{chunk-NGK3N6F2.js → chunk-GCC52YZD.js} +3 -3
- package/dist/chunk-HX44GB4Z.js +30 -0
- package/dist/chunk-HX44GB4Z.js.map +1 -0
- package/dist/{chunk-NN4ASVTY.js → chunk-IIFADWWI.js} +4 -4
- package/dist/{chunk-OIZ3XMKB.js → chunk-IOL74S4J.js} +4 -4
- package/dist/{chunk-UK2I3ACY.js → chunk-IZBBXKMJ.js} +4 -4
- package/dist/{chunk-YA63PVWI.js → chunk-K52CS4KT.js} +12 -10
- package/dist/{chunk-YA63PVWI.js.map → chunk-K52CS4KT.js.map} +1 -1
- package/dist/{chunk-7OQAU4EA.js → chunk-LDKIIZDW.js} +16 -12
- package/dist/{chunk-7OQAU4EA.js.map → chunk-LDKIIZDW.js.map} +1 -1
- package/dist/{chunk-QY3DOZMS.js → chunk-LONRQBGV.js} +4 -4
- package/dist/{chunk-EL2BZTDL.js → chunk-LXJEAV2V.js} +13 -11
- package/dist/{chunk-EL2BZTDL.js.map → chunk-LXJEAV2V.js.map} +1 -1
- package/dist/{chunk-XWFNO5PO.js → chunk-MIBWI5H7.js} +3 -3
- package/dist/{chunk-527HHQPS.js → chunk-MSMB6XOS.js} +6 -4
- package/dist/{chunk-527HHQPS.js.map → chunk-MSMB6XOS.js.map} +1 -1
- package/dist/{chunk-C6HZ6C5T.js → chunk-NIFEEIGQ.js} +2 -2
- package/dist/{chunk-JQU7FHYH.js → chunk-NZ6TNSCU.js} +10 -10
- package/dist/{chunk-3R5Y3XOL.js → chunk-OOEMKOX6.js} +29 -591
- package/dist/chunk-OOEMKOX6.js.map +1 -0
- package/dist/{chunk-MZC62HPZ.js → chunk-PAOVVHQX.js} +2 -2
- package/dist/chunk-PNX43DYW.js +849 -0
- package/dist/chunk-PNX43DYW.js.map +1 -0
- package/dist/chunk-QCHVR4ZM.js +286 -0
- package/dist/chunk-QCHVR4ZM.js.map +1 -0
- package/dist/{chunk-SJO2FTPG.js → chunk-QP5X66TO.js} +73 -1304
- package/dist/chunk-QP5X66TO.js.map +1 -0
- package/dist/{chunk-HQFVJDEN.js → chunk-QQNPVW3J.js} +19 -19
- package/dist/chunk-TPVOOTMN.js +532 -0
- package/dist/chunk-TPVOOTMN.js.map +1 -0
- package/dist/{chunk-IA7BMIPE.js → chunk-UZKQSWHJ.js} +2 -2
- package/dist/chunk-WQWKYAAK.js +86 -0
- package/dist/chunk-WQWKYAAK.js.map +1 -0
- package/dist/{chunk-CP4ZM43V.js → chunk-WTEILBKC.js} +2 -2
- package/dist/{chunk-LJ2RDH3F.js → chunk-XG6PVB5S.js} +1 -23
- package/dist/chunk-XG6PVB5S.js.map +1 -0
- package/dist/{chunk-Q35YIDBV.js → chunk-XKBEHKWO.js} +29 -264
- package/dist/chunk-XKBEHKWO.js.map +1 -0
- package/dist/{chunk-BOIF2XGG.js → chunk-XXDXIIQ4.js} +2 -2
- package/dist/{chunk-6XTQDFGP.js → chunk-YSCZL4U5.js} +3 -3
- package/dist/{chunk-6XTQDFGP.js.map → chunk-YSCZL4U5.js.map} +1 -1
- package/dist/{chunk-7ZUCEMCY.js → chunk-ZEXGGGLW.js} +9 -11
- package/dist/{chunk-7ZUCEMCY.js.map → chunk-ZEXGGGLW.js.map} +1 -1
- package/dist/{chunk-DX32P2DT.js → chunk-ZKAKA54B.js} +2 -2
- package/dist/{chunk-TVIX653Q.js → chunk-ZKCNPKUO.js} +4 -4
- package/dist/{chunk-DE7JSWXV.js → chunk-ZNE25EAR.js} +8 -6
- package/dist/{chunk-DE7JSWXV.js.map → chunk-ZNE25EAR.js.map} +1 -1
- package/dist/{chunk-FLF6AECQ.js → chunk-ZPP75QWG.js} +17 -15
- package/dist/{chunk-FLF6AECQ.js.map → chunk-ZPP75QWG.js.map} +1 -1
- package/dist/{cli-PRZHQ3QB.js → cli-37SRAYNE.js} +14 -15
- package/dist/{cli-PRZHQ3QB.js.map → cli-37SRAYNE.js.map} +1 -1
- package/dist/{cli-TYGOHJOJ.js → cli-HXWYUKGJ.js} +225 -58
- package/dist/cli-HXWYUKGJ.js.map +1 -0
- package/dist/{cli-HUIMNJT6.js → cli-IRHPPUFJ.js} +19 -20
- package/dist/{cli-HUIMNJT6.js.map → cli-IRHPPUFJ.js.map} +1 -1
- package/dist/{cli-6PGVHP7Y.js → cli-KIID535Y.js} +17 -19
- package/dist/{cli-6PGVHP7Y.js.map → cli-KIID535Y.js.map} +1 -1
- package/dist/{cli-AAG3N4NB.js → cli-MTDGQIIV.js} +17 -18
- package/dist/{cli-AAG3N4NB.js.map → cli-MTDGQIIV.js.map} +1 -1
- package/dist/{cli-E3H6ZP4I.js → cli-NM6FZ54R.js} +5 -3
- package/dist/{cli-E3H6ZP4I.js.map → cli-NM6FZ54R.js.map} +1 -1
- package/dist/{cli-MHTYPVOD.js → cli-XIODOEUG.js} +113 -20
- package/dist/{cli-MHTYPVOD.js.map → cli-XIODOEUG.js.map} +1 -1
- package/dist/{cli-YW6VWBHX.js → cli-YL3IDR52.js} +10 -10
- package/dist/{cli-QKIJOXGW.js → cli-ZQUJYQBA.js} +17 -17
- package/dist/cli.js +11 -11
- package/dist/config-O6UBGEDK.js +26 -0
- package/dist/conformance-ALLPNGEQ.js +18 -0
- package/dist/docmeta.js +9 -9
- package/dist/github-UCYLM3DR.js +26 -0
- package/dist/github-VEJFO6JJ.js +31 -0
- package/dist/host-6GURH5HE.js +23 -0
- package/dist/index.d.ts +72 -4
- package/dist/index.js +176 -166
- package/dist/index.js.map +1 -1
- package/dist/{json-4DCKDGUV.js → json-6F52FSXZ.js} +12 -13
- package/dist/lint-X2GNINK6.js +29 -0
- package/dist/list-DP3O3CWB.js +30 -0
- package/dist/list-P77RZFZF.js +28 -0
- package/dist/{page-QEDB7EVU.js → page-WQCMBNRD.js} +11 -12
- package/dist/prepare-52QOK7M4.js +155 -0
- package/dist/prepare-52QOK7M4.js.map +1 -0
- package/dist/{pretty-SQX33UM2.js → pretty-5MMMNSWF.js} +13 -14
- package/dist/{pretty-5DGEBZSV.js → pretty-H4DQREMW.js} +12 -13
- package/dist/provider-AMGRDY6V.js +34 -0
- package/dist/release-CTYRHZL4.js +36 -0
- package/dist/release-CTYRHZL4.js.map +1 -0
- package/dist/{reporters-YELDDYJD.js → reporters-SU3VUKKX.js} +7 -5
- package/dist/{reporters-OHSI3RMV.js → reporters-WIHBEMPG.js} +11 -12
- package/dist/reporters-WIHBEMPG.js.map +1 -0
- package/dist/run-L2DWJ26K.js +32 -0
- package/dist/run-L2DWJ26K.js.map +1 -0
- package/dist/{term-2WMDZ4PY.js → term-PJIJQZNF.js} +16 -17
- package/dist/{term-2WMDZ4PY.js.map → term-PJIJQZNF.js.map} +1 -1
- package/dist/validate-XCDSPURG.js +17 -0
- package/dist/validate-XCDSPURG.js.map +1 -0
- package/package.json +2 -2
- package/plugin/manni/.claude-plugin/plugin.json +1 -1
- package/plugin/manni/hooks/hooks.json +47 -0
- package/dist/batch-GOE3UXOD.js +0 -36
- package/dist/build-M7BMEHL2.js +0 -33
- package/dist/calibrate-6RRAILUY.js +0 -31
- package/dist/capture-RJPQ6B4N.js +0 -32
- package/dist/check-BQD64X3N.js +0 -27
- package/dist/chunk-3R5Y3XOL.js.map +0 -1
- package/dist/chunk-3YHAYXBC.js +0 -164
- package/dist/chunk-3YHAYXBC.js.map +0 -1
- package/dist/chunk-HVFR66GQ.js.map +0 -1
- package/dist/chunk-LJ2RDH3F.js.map +0 -1
- package/dist/chunk-NAZRLJAK.js +0 -3090
- package/dist/chunk-NAZRLJAK.js.map +0 -1
- package/dist/chunk-Q35YIDBV.js.map +0 -1
- package/dist/chunk-QCBICNKC.js.map +0 -1
- package/dist/chunk-SJO2FTPG.js.map +0 -1
- package/dist/chunk-TU2WUBVZ.js +0 -2
- package/dist/cli-TYGOHJOJ.js.map +0 -1
- package/dist/github-3WMSTNTK.js +0 -32
- package/dist/github-OXSJQ4PS.js +0 -27
- package/dist/lint-YDHVVZHT.js +0 -30
- package/dist/list-3ZO2NIYO.js +0 -31
- package/dist/list-EJH6VAOW.js +0 -29
- package/dist/run-IP6NZHAG.js +0 -33
- package/dist/validate-G6HH3YXL.js +0 -16
- /package/dist/{batch-GOE3UXOD.js.map → batch-U6S2JUUN.js.map} +0 -0
- /package/dist/{build-M7BMEHL2.js.map → build-4RN6WBCP.js.map} +0 -0
- /package/dist/{calibrate-6RRAILUY.js.map → calibrate-6VIZYR4L.js.map} +0 -0
- /package/dist/{capture-RJPQ6B4N.js.map → capture-DP3NXAZE.js.map} +0 -0
- /package/dist/{check-BQD64X3N.js.map → check-ERVNGKLC.js.map} +0 -0
- /package/dist/{check-ESNS7ZBC.js.map → check-OAJWC3WD.js.map} +0 -0
- /package/dist/{check-RQIIIURI.js.map → check-STHXZIZO.js.map} +0 -0
- /package/dist/{chunk-TA42STP7.js.map → chunk-2ZWTSRE5.js.map} +0 -0
- /package/dist/{chunk-F2OVHJNC.js.map → chunk-4Z2MN5FR.js.map} +0 -0
- /package/dist/{chunk-TCC737IS.js.map → chunk-5VWDIWOH.js.map} +0 -0
- /package/dist/{chunk-ELZ2IIOF.js.map → chunk-6URSCTTX.js.map} +0 -0
- /package/dist/{chunk-K3U5F2QT.js.map → chunk-6VDBP5UV.js.map} +0 -0
- /package/dist/{chunk-C3KI45RF.js.map → chunk-7XZ6INKO.js.map} +0 -0
- /package/dist/{chunk-SNEHX4VE.js.map → chunk-B5CMM57M.js.map} +0 -0
- /package/dist/{chunk-GAZFKZHM.js.map → chunk-CEVU3GFF.js.map} +0 -0
- /package/dist/{chunk-52PDIZKV.js.map → chunk-ECVQNGTH.js.map} +0 -0
- /package/dist/{chunk-NGK3N6F2.js.map → chunk-GCC52YZD.js.map} +0 -0
- /package/dist/{chunk-NN4ASVTY.js.map → chunk-IIFADWWI.js.map} +0 -0
- /package/dist/{chunk-OIZ3XMKB.js.map → chunk-IOL74S4J.js.map} +0 -0
- /package/dist/{chunk-UK2I3ACY.js.map → chunk-IZBBXKMJ.js.map} +0 -0
- /package/dist/{chunk-QY3DOZMS.js.map → chunk-LONRQBGV.js.map} +0 -0
- /package/dist/{chunk-XWFNO5PO.js.map → chunk-MIBWI5H7.js.map} +0 -0
- /package/dist/{chunk-C6HZ6C5T.js.map → chunk-NIFEEIGQ.js.map} +0 -0
- /package/dist/{chunk-JQU7FHYH.js.map → chunk-NZ6TNSCU.js.map} +0 -0
- /package/dist/{chunk-MZC62HPZ.js.map → chunk-PAOVVHQX.js.map} +0 -0
- /package/dist/{chunk-HQFVJDEN.js.map → chunk-QQNPVW3J.js.map} +0 -0
- /package/dist/{chunk-IA7BMIPE.js.map → chunk-UZKQSWHJ.js.map} +0 -0
- /package/dist/{chunk-CP4ZM43V.js.map → chunk-WTEILBKC.js.map} +0 -0
- /package/dist/{chunk-BOIF2XGG.js.map → chunk-XXDXIIQ4.js.map} +0 -0
- /package/dist/{chunk-DX32P2DT.js.map → chunk-ZKAKA54B.js.map} +0 -0
- /package/dist/{chunk-TVIX653Q.js.map → chunk-ZKCNPKUO.js.map} +0 -0
- /package/dist/{chunk-TU2WUBVZ.js.map → cli-YL3IDR52.js.map} +0 -0
- /package/dist/{cli-QKIJOXGW.js.map → cli-ZQUJYQBA.js.map} +0 -0
- /package/dist/{cli-YW6VWBHX.js.map → config-O6UBGEDK.js.map} +0 -0
- /package/dist/{github-3WMSTNTK.js.map → conformance-ALLPNGEQ.js.map} +0 -0
- /package/dist/{github-OXSJQ4PS.js.map → github-UCYLM3DR.js.map} +0 -0
- /package/dist/{json-4DCKDGUV.js.map → github-VEJFO6JJ.js.map} +0 -0
- /package/dist/{lint-YDHVVZHT.js.map → host-6GURH5HE.js.map} +0 -0
- /package/dist/{list-3ZO2NIYO.js.map → json-6F52FSXZ.js.map} +0 -0
- /package/dist/{list-EJH6VAOW.js.map → lint-X2GNINK6.js.map} +0 -0
- /package/dist/{page-QEDB7EVU.js.map → list-DP3O3CWB.js.map} +0 -0
- /package/dist/{pretty-5DGEBZSV.js.map → list-P77RZFZF.js.map} +0 -0
- /package/dist/{pretty-SQX33UM2.js.map → page-WQCMBNRD.js.map} +0 -0
- /package/dist/{reporters-OHSI3RMV.js.map → pretty-5MMMNSWF.js.map} +0 -0
- /package/dist/{reporters-YELDDYJD.js.map → pretty-H4DQREMW.js.map} +0 -0
- /package/dist/{run-IP6NZHAG.js.map → provider-AMGRDY6V.js.map} +0 -0
- /package/dist/{validate-G6HH3YXL.js.map → reporters-SU3VUKKX.js.map} +0 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
GB
|
|
4
|
+
} from "./chunk-D2LBN44V.js";
|
|
5
|
+
import {
|
|
6
|
+
validateWhen
|
|
7
|
+
} from "./chunk-FQGLRJGX.js";
|
|
8
|
+
import {
|
|
9
|
+
assertProviderSelection,
|
|
10
|
+
constructProvider,
|
|
11
|
+
resolveSelectionIdentity,
|
|
12
|
+
selectProvider
|
|
13
|
+
} from "./chunk-BWCWTHYS.js";
|
|
14
|
+
import {
|
|
15
|
+
TracevalsError
|
|
16
|
+
} from "./chunk-UKZNLVJW.js";
|
|
17
|
+
import {
|
|
18
|
+
warn
|
|
19
|
+
} from "./chunk-HX44GB4Z.js";
|
|
20
|
+
import {
|
|
21
|
+
errorMessage
|
|
22
|
+
} from "./chunk-G3T47CFV.js";
|
|
23
|
+
|
|
24
|
+
// src/tracevals/rules/mock.ts
|
|
25
|
+
var IMPERATIVE = /^(?:run|use|never|always|do|don't|keep|write|read|prefer|add|avoid|ask|check|make|follow|commit|test|update|stop|ensure)\b/i;
|
|
26
|
+
function kebab(text) {
|
|
27
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").split("-").filter((w) => w !== "").slice(0, 5).join("-");
|
|
28
|
+
}
|
|
29
|
+
function mockRulesResponse(content) {
|
|
30
|
+
const rules = [];
|
|
31
|
+
for (const line of content.split(/\r?\n/)) {
|
|
32
|
+
const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
|
|
33
|
+
const text = bullet?.[1]?.trim();
|
|
34
|
+
if (text === void 0 || !IMPERATIVE.test(text)) continue;
|
|
35
|
+
const id = kebab(text);
|
|
36
|
+
if (id !== "") rules.push({ id, text });
|
|
37
|
+
}
|
|
38
|
+
return { json: { rules } };
|
|
39
|
+
}
|
|
40
|
+
var ITEM = /^\s*(?:[-*]|\d+\.)\s+(?:\[[ xX]\]\s+)?(.+)$/;
|
|
41
|
+
var SPEC_ITEM = /^(?:\*\*)?([A-Z]+-\d+|T\d+|\d+(?:\.\d+)+)(?:\*\*)?:?\s+(.+)$/;
|
|
42
|
+
function mockRequestsResponse(content) {
|
|
43
|
+
const rules = [];
|
|
44
|
+
for (const line of content.split(/\r?\n/)) {
|
|
45
|
+
const item = ITEM.exec(line)?.[1]?.trim();
|
|
46
|
+
if (item === void 0) continue;
|
|
47
|
+
const spec = SPEC_ITEM.exec(item);
|
|
48
|
+
const specId = spec?.[1];
|
|
49
|
+
const specText = spec?.[2]?.trim();
|
|
50
|
+
if (specId !== void 0 && specText !== void 0) {
|
|
51
|
+
rules.push({ id: specId, text: specText });
|
|
52
|
+
} else if (IMPERATIVE.test(item)) {
|
|
53
|
+
const id = kebab(item);
|
|
54
|
+
if (id !== "") rules.push({ id, text: item });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { json: { rules } };
|
|
58
|
+
}
|
|
59
|
+
var PROHIBITION = /^(?:never|don't|do not|avoid)\b/i;
|
|
60
|
+
function mockTurnScores(user) {
|
|
61
|
+
const marker = user.lastIndexOf("# The rule\n\n");
|
|
62
|
+
const turn = user.slice(Math.max(0, user.indexOf("# The turn\n\n")), marker);
|
|
63
|
+
const line = user.slice(marker).split("\n")[2] ?? "";
|
|
64
|
+
const text = line.slice(line.indexOf(": ") + 2);
|
|
65
|
+
const span = /`([^`]+)`/.exec(text)?.[1];
|
|
66
|
+
const answer = (reasoning2, followed, notFollowed) => ({
|
|
67
|
+
reasoning: reasoning2,
|
|
68
|
+
"not-applicable": 0,
|
|
69
|
+
followed,
|
|
70
|
+
"not-followed": notFollowed
|
|
71
|
+
});
|
|
72
|
+
if (span === void 0) return answer("The rule names nothing to look for.", 100, 0);
|
|
73
|
+
const shown = turn.includes(span);
|
|
74
|
+
const reasoning = shown ? `The turn shows \`${span}\`.` : `The turn does not show \`${span}\`.`;
|
|
75
|
+
if (PROHIBITION.test(text.trim())) return shown ? answer(reasoning, 0, 100) : answer(reasoning, 100, 0);
|
|
76
|
+
return shown ? answer(reasoning, 100, 0) : answer(reasoning, 0, 0);
|
|
77
|
+
}
|
|
78
|
+
function mockTurnJudge(model, name = "mock") {
|
|
79
|
+
return {
|
|
80
|
+
provider: () => name,
|
|
81
|
+
modelName: () => model,
|
|
82
|
+
completeJSON: (req) => Promise.resolve({ json: mockTurnScores(req.user) })
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/tracevals/commands/conformance.ts
|
|
87
|
+
import { resolve } from "node:path";
|
|
88
|
+
|
|
89
|
+
// src/tracevals/rules/cache.ts
|
|
90
|
+
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
91
|
+
import { join } from "node:path";
|
|
92
|
+
import { JsonCache, buildCacheKey } from "@hawkeyexl/inference";
|
|
93
|
+
|
|
94
|
+
// src/tracevals/rules/prompt.ts
|
|
95
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
96
|
+
var RULES_PROMPT_VERSION = 2;
|
|
97
|
+
var WHEN_GUIDE = [
|
|
98
|
+
"- `when` (optional): a trigger saying which turns the rule concerns. Every",
|
|
99
|
+
" listed condition must hold. The only conditions are:",
|
|
100
|
+
" - `file-access`: a glob. The turn read, wrote or edited a matching file.",
|
|
101
|
+
" - `tool-used`: a tool name. The turn called that tool.",
|
|
102
|
+
" - `prompt-matches`: a regular expression. A prompt the user typed matches.",
|
|
103
|
+
" - `turn-count-above`: a whole number. The session has more turns than that.",
|
|
104
|
+
" - `command-matches`: a regular expression. A Bash command in the turn matches.",
|
|
105
|
+
"",
|
|
106
|
+
"Omit `when` unless the rule clearly concerns only certain files, tools or",
|
|
107
|
+
"commands. A trigger that is too narrow hides violations, and a rule with no",
|
|
108
|
+
"`when` is simply checked on every turn. When in doubt, leave it out."
|
|
109
|
+
];
|
|
110
|
+
var RULES_SYSTEM_PROMPT = [
|
|
111
|
+
"You extract rules from a file that governs an AI agent working in a repository.",
|
|
112
|
+
"",
|
|
113
|
+
"A rule is a directive addressed to the AI agent, which a recorded session",
|
|
114
|
+
"transcript could show it following or breaking. The transcript holds the",
|
|
115
|
+
"agent's prompts, its tool calls (with their inputs), the files it read or",
|
|
116
|
+
"wrote, and its replies. Nothing else is observable.",
|
|
117
|
+
"",
|
|
118
|
+
"Return a rule only when the file tells the agent to do, or not to do,",
|
|
119
|
+
"something. Return nothing for:",
|
|
120
|
+
"- descriptions of how a system, tool or codebase behaves;",
|
|
121
|
+
"- instructions written for end users or readers rather than the agent;",
|
|
122
|
+
"- examples, sample output and code listings;",
|
|
123
|
+
"- rules the file quotes from somewhere else, or reports as another party's.",
|
|
124
|
+
"",
|
|
125
|
+
"Keep a procedure's rules too, which a session shows across many turns:",
|
|
126
|
+
"the order of steps, a gate that one step must pass before another, a route",
|
|
127
|
+
"that a step's result chooses, a condition that calls for a step, a required",
|
|
128
|
+
"step, and the step that comes last. Write each as its own rule.",
|
|
129
|
+
"",
|
|
130
|
+
'Drop a directive a transcript cannot show, such as "prefer boring code" or',
|
|
131
|
+
'"keep things simple". If you cannot say what a violation would look like in',
|
|
132
|
+
"a transcript, it does not qualify.",
|
|
133
|
+
"",
|
|
134
|
+
"Each rule has:",
|
|
135
|
+
"- `id`: a short kebab-case identifier, unique within this file.",
|
|
136
|
+
"- `text`: one imperative sentence, faithful to the file. Do not strengthen,",
|
|
137
|
+
" soften or merge directives, and do not invent ones the file lacks.",
|
|
138
|
+
...WHEN_GUIDE,
|
|
139
|
+
"",
|
|
140
|
+
"Return none at all when nothing qualifies."
|
|
141
|
+
].join("\n");
|
|
142
|
+
var RULES_SCHEMA = {
|
|
143
|
+
type: "object",
|
|
144
|
+
required: ["rules"],
|
|
145
|
+
additionalProperties: false,
|
|
146
|
+
properties: {
|
|
147
|
+
rules: {
|
|
148
|
+
type: "array",
|
|
149
|
+
items: {
|
|
150
|
+
type: "object",
|
|
151
|
+
required: ["id", "text"],
|
|
152
|
+
additionalProperties: false,
|
|
153
|
+
properties: {
|
|
154
|
+
id: { type: "string" },
|
|
155
|
+
text: { type: "string", minLength: 1 },
|
|
156
|
+
// Open on purpose: `extractRules` gates each `when` with `validateWhen`
|
|
157
|
+
// and drops the one rule, where a schema miss would void the whole file.
|
|
158
|
+
when: { type: "object" }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var ajv = new Ajv2020({ allErrors: true });
|
|
165
|
+
var validateRules = ajv.compile(
|
|
166
|
+
RULES_SCHEMA
|
|
167
|
+
);
|
|
168
|
+
function isValidRules(value) {
|
|
169
|
+
return validateRules(value);
|
|
170
|
+
}
|
|
171
|
+
function buildRulesUser(options) {
|
|
172
|
+
return [
|
|
173
|
+
"# Rule source",
|
|
174
|
+
`path: ${options.path}`,
|
|
175
|
+
`format: ${options.format}`,
|
|
176
|
+
"",
|
|
177
|
+
"# File content",
|
|
178
|
+
"",
|
|
179
|
+
options.content
|
|
180
|
+
].join("\n");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/tracevals/rules/requests-prompt.ts
|
|
184
|
+
var REQUIREMENTS_PROMPT_VERSION = 1;
|
|
185
|
+
var REQUEST_FORMATS = /* @__PURE__ */ new Set([
|
|
186
|
+
"prompt",
|
|
187
|
+
"plan",
|
|
188
|
+
"speckit-spec",
|
|
189
|
+
"kiro-spec",
|
|
190
|
+
"openspec-change",
|
|
191
|
+
"plans"
|
|
192
|
+
]);
|
|
193
|
+
function isRequestFormat(format) {
|
|
194
|
+
return REQUEST_FORMATS.has(format);
|
|
195
|
+
}
|
|
196
|
+
var REQUIREMENTS_SYSTEM_PROMPT = [
|
|
197
|
+
"You read what an AI agent working in a repository was asked to build or keep,",
|
|
198
|
+
"and return each item as a rule.",
|
|
199
|
+
"",
|
|
200
|
+
"The source is one of three things: the prompts a person typed in the",
|
|
201
|
+
"session, numbered in order; a plan the person approved; or a spec file,",
|
|
202
|
+
"such as a feature spec, a design, a task list or a change proposal.",
|
|
203
|
+
"",
|
|
204
|
+
"A rule is something the work must deliver or keep, which a recorded session",
|
|
205
|
+
"transcript could show met or broken. The transcript holds the agent's",
|
|
206
|
+
"prompts, its tool calls (with their inputs), the files it read or wrote, and",
|
|
207
|
+
"its replies. Nothing else is observable.",
|
|
208
|
+
"",
|
|
209
|
+
"Typed prompts are one sequence. Return the set that stands after the last",
|
|
210
|
+
"prompt: a later prompt can add an item, change an earlier one, or drop it.",
|
|
211
|
+
"A question with nothing to build yields nothing. Never return chat, such as",
|
|
212
|
+
"greetings, thanks, or remarks about the work.",
|
|
213
|
+
"",
|
|
214
|
+
"A spec's items keep the spec's own ids, exactly as written, such as",
|
|
215
|
+
"`FR-001`, `T014` or `1.2`. A task the file marks done is still a rule:",
|
|
216
|
+
"ticking it says that task is done.",
|
|
217
|
+
"",
|
|
218
|
+
"Return nothing for background, rationale, examples, open questions, or",
|
|
219
|
+
"items the source marks out of scope.",
|
|
220
|
+
"",
|
|
221
|
+
"Each rule has:",
|
|
222
|
+
"- `id`: the source's own id for the item when it gives one, exactly as",
|
|
223
|
+
" written. Otherwise a short kebab-case identifier. Unique within this source.",
|
|
224
|
+
"- `text`: one imperative sentence, faithful to the source. Do not strengthen,",
|
|
225
|
+
" soften or merge items, and do not invent ones the source lacks.",
|
|
226
|
+
...WHEN_GUIDE,
|
|
227
|
+
"",
|
|
228
|
+
"Return none at all when nothing qualifies."
|
|
229
|
+
].join("\n");
|
|
230
|
+
function buildRequirementsUser(options) {
|
|
231
|
+
return [
|
|
232
|
+
"# Request source",
|
|
233
|
+
// Callers pass the display path (`prompt`, `plan`); a blank one names the format.
|
|
234
|
+
`path: ${options.path !== "" ? options.path : options.format}`,
|
|
235
|
+
`format: ${options.format}`,
|
|
236
|
+
"",
|
|
237
|
+
"# Content",
|
|
238
|
+
"",
|
|
239
|
+
options.content
|
|
240
|
+
].join("\n");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/tracevals/rules/cache.ts
|
|
244
|
+
var DEFAULT_RULES_CACHE_DIR = ".manni/tracevals/cache/rules";
|
|
245
|
+
function rulesCacheKey(parts) {
|
|
246
|
+
return buildCacheKey([
|
|
247
|
+
parts.provider,
|
|
248
|
+
parts.model,
|
|
249
|
+
// The rules prompt keeps the slot's old spelling, so rule files keep
|
|
250
|
+
// their entries.
|
|
251
|
+
parts.prompt === "rules" ? `rules-v${String(RULES_PROMPT_VERSION)}` : `requests-v${String(REQUIREMENTS_PROMPT_VERSION)}`,
|
|
252
|
+
`t${String(parts.temperature)}`,
|
|
253
|
+
parts.sha256
|
|
254
|
+
]);
|
|
255
|
+
}
|
|
256
|
+
function isRule(value) {
|
|
257
|
+
if (typeof value !== "object" || value === null) return false;
|
|
258
|
+
const r = value;
|
|
259
|
+
return typeof r.id === "string" && typeof r.text === "string" && (r.when === void 0 || typeof r.when === "object" && r.when !== null && !Array.isArray(r.when));
|
|
260
|
+
}
|
|
261
|
+
var RulesCache = class {
|
|
262
|
+
constructor(dir = DEFAULT_RULES_CACHE_DIR, enabled = true) {
|
|
263
|
+
this.dir = dir;
|
|
264
|
+
this.enabled = enabled;
|
|
265
|
+
this.store = new JsonCache(dir, enabled, "manni-tracevals");
|
|
266
|
+
}
|
|
267
|
+
dir;
|
|
268
|
+
enabled;
|
|
269
|
+
store;
|
|
270
|
+
warned = false;
|
|
271
|
+
/** An entry that is not a list of rules, from an older shape, is a miss. */
|
|
272
|
+
get(key) {
|
|
273
|
+
const value = this.store.get(key);
|
|
274
|
+
if (!Array.isArray(value) || !value.every(isRule)) return void 0;
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
set(key, rules) {
|
|
278
|
+
if (!this.enabled) return;
|
|
279
|
+
const tmp = join(this.dir, `${key}.${process.pid}.${Date.now()}.tmp`);
|
|
280
|
+
try {
|
|
281
|
+
mkdirSync(this.dir, { recursive: true });
|
|
282
|
+
writeFileSync(tmp, JSON.stringify(rules, null, 2));
|
|
283
|
+
renameSync(tmp, join(this.dir, `${key}.json`));
|
|
284
|
+
} catch (err) {
|
|
285
|
+
rmSync(tmp, { force: true });
|
|
286
|
+
if (!this.warned) {
|
|
287
|
+
this.warned = true;
|
|
288
|
+
warn(
|
|
289
|
+
`could not write the rules cache at ${this.dir} (${err instanceof Error ? err.message : String(err)}). Continuing without caching.`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// src/tracevals/rules/extract.ts
|
|
297
|
+
import {
|
|
298
|
+
completeValidatedJSON
|
|
299
|
+
} from "@hawkeyexl/inference";
|
|
300
|
+
var KEBAB = /^[a-z0-9][a-z0-9-]*$/;
|
|
301
|
+
var SPEC_ID = /^[A-Za-z0-9][A-Za-z0-9.-]*$/;
|
|
302
|
+
var PROMPTS = {
|
|
303
|
+
rules: {
|
|
304
|
+
system: RULES_SYSTEM_PROMPT,
|
|
305
|
+
user: buildRulesUser,
|
|
306
|
+
id: KEBAB,
|
|
307
|
+
idReason: "id is not kebab-case"
|
|
308
|
+
},
|
|
309
|
+
requests: {
|
|
310
|
+
system: REQUIREMENTS_SYSTEM_PROMPT,
|
|
311
|
+
user: buildRequirementsUser,
|
|
312
|
+
id: SPEC_ID,
|
|
313
|
+
idReason: "id is neither a spec's own id nor kebab-case"
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
function promptFor(format) {
|
|
317
|
+
return isRequestFormat(format) ? "requests" : "rules";
|
|
318
|
+
}
|
|
319
|
+
function gateRules(proposed, prompt = "rules") {
|
|
320
|
+
const { id: idPattern, idReason } = PROMPTS[prompt];
|
|
321
|
+
const rules = [];
|
|
322
|
+
const dropped = [];
|
|
323
|
+
const seen = /* @__PURE__ */ new Set();
|
|
324
|
+
for (const proposedRule of proposed) {
|
|
325
|
+
const { when, ...bare } = proposedRule;
|
|
326
|
+
const rule = when === void 0 || Object.keys(when).length === 0 ? bare : proposedRule;
|
|
327
|
+
if (!idPattern.test(rule.id)) {
|
|
328
|
+
dropped.push({ id: rule.id, reason: idReason });
|
|
329
|
+
} else if (rule.text.trim() === "") {
|
|
330
|
+
dropped.push({ id: rule.id, reason: "text is empty" });
|
|
331
|
+
} else if (seen.has(rule.id)) {
|
|
332
|
+
dropped.push({ id: rule.id, reason: "id repeats an earlier rule" });
|
|
333
|
+
} else {
|
|
334
|
+
const invalid = rule.when === void 0 ? void 0 : validateWhen({ when: rule.when });
|
|
335
|
+
if (invalid !== void 0) {
|
|
336
|
+
dropped.push({ id: rule.id, reason: invalid });
|
|
337
|
+
} else {
|
|
338
|
+
seen.add(rule.id);
|
|
339
|
+
rules.push(rule);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return { rules, dropped };
|
|
344
|
+
}
|
|
345
|
+
async function extractRules(source, deps) {
|
|
346
|
+
const { provider, cache } = deps;
|
|
347
|
+
const prompt = promptFor(source.format);
|
|
348
|
+
const key = rulesCacheKey({
|
|
349
|
+
provider: provider.provider(),
|
|
350
|
+
model: provider.modelName(),
|
|
351
|
+
prompt,
|
|
352
|
+
temperature: deps.temperature ?? 0,
|
|
353
|
+
sha256: source.sha256
|
|
354
|
+
});
|
|
355
|
+
const hit = cache?.get(key);
|
|
356
|
+
if (hit !== void 0) return { rules: hit, cached: true, dropped: [] };
|
|
357
|
+
const run = await completeValidatedJSON({
|
|
358
|
+
provider,
|
|
359
|
+
system: PROMPTS[prompt].system,
|
|
360
|
+
user: PROMPTS[prompt].user(source),
|
|
361
|
+
schema: RULES_SCHEMA,
|
|
362
|
+
...deps.temperature !== void 0 ? { temperature: deps.temperature } : {}
|
|
363
|
+
});
|
|
364
|
+
if (run.result === void 0 || !isValidRules(run.result)) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
run.error ?? "provider returned rules that do not match the schema"
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
const gated = gateRules(run.result.rules, prompt);
|
|
370
|
+
cache?.set(key, gated.rules);
|
|
371
|
+
return { ...gated, cached: false };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/tracevals/commands/conformance.ts
|
|
375
|
+
var JUDGE_ENV = "MANNI_TRACEVALS_JUDGE";
|
|
376
|
+
function markJudgeProcess() {
|
|
377
|
+
process.env[JUDGE_ENV] = "1";
|
|
378
|
+
}
|
|
379
|
+
var LOCAL_PROVIDERS = /* @__PURE__ */ new Set(["llama-cpp", "mock"]);
|
|
380
|
+
function isNetworkProvider(provider) {
|
|
381
|
+
return !LOCAL_PROVIDERS.has(provider);
|
|
382
|
+
}
|
|
383
|
+
function offlineRefusal(role, identity) {
|
|
384
|
+
return new TracevalsError(
|
|
385
|
+
`--offline runs no network provider, and ${role} uses ${identity.provider}`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
function firstLine(err) {
|
|
389
|
+
return errorMessage(err).split("\n")[0] ?? "";
|
|
390
|
+
}
|
|
391
|
+
function conformanceOf(config) {
|
|
392
|
+
return config.conformance ?? {
|
|
393
|
+
hook: { provider: null, model: null, runs: 1 },
|
|
394
|
+
include: [],
|
|
395
|
+
exclude: [],
|
|
396
|
+
plans: []
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function rulesCacheFor(config, configDir, noCache) {
|
|
400
|
+
return new RulesCache(resolve(configDir, config.judge.cacheDir, "rules"), !noCache);
|
|
401
|
+
}
|
|
402
|
+
async function extraction(config, cache, injected, host) {
|
|
403
|
+
let identity;
|
|
404
|
+
if (injected !== void 0) {
|
|
405
|
+
identity = { provider: injected.provider(), model: injected.modelName() };
|
|
406
|
+
} else {
|
|
407
|
+
const selection = selectProvider(config);
|
|
408
|
+
assertProviderSelection(selection);
|
|
409
|
+
identity = await resolveSelectionIdentity(config, selection);
|
|
410
|
+
}
|
|
411
|
+
let built = injected;
|
|
412
|
+
const providerFor = (source) => {
|
|
413
|
+
if (built === void 0 && identity.provider === "mock") {
|
|
414
|
+
markJudgeProcess();
|
|
415
|
+
const scripted = promptFor(source.format) === "requests" ? mockRequestsResponse(source.content) : mockRulesResponse(source.content);
|
|
416
|
+
return constructProvider(config, identity, { mockResponses: [scripted] });
|
|
417
|
+
}
|
|
418
|
+
if (built === void 0) {
|
|
419
|
+
markJudgeProcess();
|
|
420
|
+
built = constructProvider(config, identity, host === void 0 ? {} : { host });
|
|
421
|
+
}
|
|
422
|
+
return built;
|
|
423
|
+
};
|
|
424
|
+
const keyOf = (source) => rulesCacheKey({
|
|
425
|
+
provider: identity.provider,
|
|
426
|
+
model: identity.model,
|
|
427
|
+
prompt: promptFor(source.format),
|
|
428
|
+
temperature: config.judge.temperature,
|
|
429
|
+
sha256: source.sha256
|
|
430
|
+
});
|
|
431
|
+
return {
|
|
432
|
+
identity,
|
|
433
|
+
cache,
|
|
434
|
+
needs: (source) => source.declaredRules === void 0 && cache.get(keyOf(source)) === void 0,
|
|
435
|
+
rulesOf: async (source) => {
|
|
436
|
+
if (source.declaredRules !== void 0) {
|
|
437
|
+
return { rules: source.declaredRules, origin: "declared" };
|
|
438
|
+
}
|
|
439
|
+
const hit = cache.get(keyOf(source));
|
|
440
|
+
if (hit !== void 0) return { rules: hit, origin: "extracted" };
|
|
441
|
+
try {
|
|
442
|
+
const extracted = await extractRules(
|
|
443
|
+
{
|
|
444
|
+
path: source.displayPath,
|
|
445
|
+
format: source.format,
|
|
446
|
+
content: source.content,
|
|
447
|
+
sha256: source.sha256
|
|
448
|
+
},
|
|
449
|
+
{ provider: providerFor(source), cache, temperature: config.judge.temperature }
|
|
450
|
+
);
|
|
451
|
+
return { rules: extracted.rules, origin: "extracted" };
|
|
452
|
+
} catch (err) {
|
|
453
|
+
if (err instanceof TracevalsError) throw err;
|
|
454
|
+
throw new TracevalsError(
|
|
455
|
+
`could not extract rules from ${source.displayPath}: ${firstLine(err)}`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
var wholeGb = (bytes) => Math.round(bytes / GB);
|
|
462
|
+
async function localGate(local, model, gates, memoryUnneeded) {
|
|
463
|
+
const state = await local.state(model);
|
|
464
|
+
if (gates.includes("downloading") && state === "downloading") {
|
|
465
|
+
return { gate: "downloading", message: `tracevals skipped this turn: ${model} is still downloading.` };
|
|
466
|
+
}
|
|
467
|
+
if (gates.includes("not-downloaded") && (state !== "ready" || !await local.runtimePresent())) {
|
|
468
|
+
return {
|
|
469
|
+
gate: "not-downloaded",
|
|
470
|
+
message: `tracevals skipped this turn: ${model} is not downloaded yet. Run manni tracevals prepare to fetch it.`
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
if (gates.includes("memory") && !await memoryUnneeded?.()) {
|
|
474
|
+
const fit = await local.fits(model);
|
|
475
|
+
if (!fit.fits) {
|
|
476
|
+
return {
|
|
477
|
+
gate: "memory",
|
|
478
|
+
message: `tracevals skipped this turn: ${model} needs about ${String(wholeGb(fit.needBytes))} GB and ${String(wholeGb(fit.freeBytes))} GB is free.`
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return void 0;
|
|
483
|
+
}
|
|
484
|
+
async function assertDownloaded(local, model) {
|
|
485
|
+
if (await local.state(model) === "ready" && await local.runtimePresent()) return;
|
|
486
|
+
throw new TracevalsError(`${model} is not downloaded; run manni tracevals prepare`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export {
|
|
490
|
+
mockTurnJudge,
|
|
491
|
+
JUDGE_ENV,
|
|
492
|
+
markJudgeProcess,
|
|
493
|
+
isNetworkProvider,
|
|
494
|
+
offlineRefusal,
|
|
495
|
+
firstLine,
|
|
496
|
+
conformanceOf,
|
|
497
|
+
rulesCacheFor,
|
|
498
|
+
extraction,
|
|
499
|
+
localGate,
|
|
500
|
+
assertDownloaded
|
|
501
|
+
};
|
|
502
|
+
//# sourceMappingURL=chunk-CI55ABCK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracevals/rules/mock.ts","../src/tracevals/commands/conformance.ts","../src/tracevals/rules/cache.ts","../src/tracevals/rules/prompt.ts","../src/tracevals/rules/requests-prompt.ts","../src/tracevals/rules/extract.ts"],"sourcesContent":["/**\n * A deterministic, schema-valid extraction for `--provider mock`.\n *\n * It reads the file the way the real prompt asks a model to, crudely: a bullet\n * that opens with an imperative verb is a rule, everything else is not. That\n * gives offline tests and the CI dogfood path rules they can predict from the\n * fixture alone.\n */\nimport type { InferenceProvider } from \"@hawkeyexl/inference\";\n\nconst IMPERATIVE =\n /^(?:run|use|never|always|do|don't|keep|write|read|prefer|add|avoid|ask|check|make|follow|commit|test|update|stop|ensure)\\b/i;\n\nfunction kebab(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .split(\"-\")\n .filter((w) => w !== \"\")\n .slice(0, 5)\n .join(\"-\");\n}\n\nexport function mockRulesResponse(content: string): {\n json: { rules: { id: string; text: string }[] };\n} {\n const rules: { id: string; text: string }[] = [];\n for (const line of content.split(/\\r?\\n/)) {\n const bullet = /^\\s*[-*]\\s+(.+)$/.exec(line);\n const text = bullet?.[1]?.trim();\n if (text === undefined || !IMPERATIVE.test(text)) continue;\n const id = kebab(text);\n if (id !== \"\") rules.push({ id, text });\n }\n return { json: { rules } };\n}\n\nconst ITEM = /^\\s*(?:[-*]|\\d+\\.)\\s+(?:\\[[ xX]\\]\\s+)?(.+)$/;\nconst SPEC_ITEM = /^(?:\\*\\*)?([A-Z]+-\\d+|T\\d+|\\d+(?:\\.\\d+)+)(?:\\*\\*)?:?\\s+(.+)$/;\n\n/**\n * The same crude reading for a source that says what the session was asked.\n * A bullet, a numbered prompt or a task checkbox is an item. One that opens\n * with a spec's own id (`FR-001`, `T014`, `1.2`) keeps that id, and any other\n * is a rule only when it opens with an imperative verb.\n */\nexport function mockRequestsResponse(content: string): {\n json: { rules: { id: string; text: string }[] };\n} {\n const rules: { id: string; text: string }[] = [];\n for (const line of content.split(/\\r?\\n/)) {\n const item = ITEM.exec(line)?.[1]?.trim();\n if (item === undefined) continue;\n const spec = SPEC_ITEM.exec(item);\n const specId = spec?.[1];\n const specText = spec?.[2]?.trim();\n if (specId !== undefined && specText !== undefined) {\n rules.push({ id: specId, text: specText });\n } else if (IMPERATIVE.test(item)) {\n const id = kebab(item);\n if (id !== \"\") rules.push({ id, text: item });\n }\n }\n return { json: { rules } };\n}\n\nconst PROHIBITION =/^(?:never|don't|do not|avoid)\\b/i;\n\n/**\n * A deterministic turn answer for `--provider mock`, read from the rule's\n * first code span as crudely as `mockRulesResponse` reads a file. A\n * prohibition is not followed when the turn shows its span, and followed\n * otherwise. Any other rule is followed when the turn shows its span. When it\n * does not, every score is 0, which the block bar sends to review. A rule with\n * no code span is followed. The reasoning says which it found.\n */\nexport function mockTurnScores(user: string): Record<string, string | number> {\n const marker = user.lastIndexOf(\"# The rule\\n\\n\");\n // The turn alone: what earlier turns did is not this turn's evidence.\n const turn = user.slice(Math.max(0, user.indexOf(\"# The turn\\n\\n\")), marker);\n const line = user.slice(marker).split(\"\\n\")[2] ?? \"\";\n const text = line.slice(line.indexOf(\": \") + 2);\n const span = /`([^`]+)`/.exec(text)?.[1];\n const answer = (reasoning: string, followed: number, notFollowed: number) => ({\n reasoning,\n \"not-applicable\": 0,\n followed,\n \"not-followed\": notFollowed,\n });\n if (span === undefined) return answer(\"The rule names nothing to look for.\", 100, 0);\n const shown = turn.includes(span);\n const reasoning = shown ? `The turn shows \\`${span}\\`.` : `The turn does not show \\`${span}\\`.`;\n if (PROHIBITION.test(text.trim())) return shown ? answer(reasoning, 0, 100) : answer(reasoning, 100, 0);\n return shown ? answer(reasoning, 100, 0) : answer(reasoning, 0, 0);\n}\n\n/** The turn judge under `--provider mock`: answers from `mockTurnScores`. */\nexport function mockTurnJudge(model: string, name = \"mock\"): InferenceProvider {\n return {\n provider: () => name,\n modelName: () => model,\n completeJSON: (req) => Promise.resolve({ json: mockTurnScores(req.user) }),\n };\n}\n","/**\n * What `prepare` and `check` share (proposal 0079): the two model roles,\n * extraction through the rules cache, and the local-model gates. Free of CLI\n * and IO plumbing, like every command core.\n */\nimport { resolve } from \"node:path\";\nimport type { InferenceProvider, ProviderName } from \"@hawkeyexl/inference\";\nimport { errorMessage } from \"../../shared/errors.js\";\nimport type { TracevalsConfig } from \"../core/config.js\";\nimport {\n assertProviderSelection,\n constructProvider,\n resolveSelectionIdentity,\n selectProvider,\n} from \"../judge/provider.js\";\nimport { RulesCache, rulesCacheKey } from \"../rules/cache.js\";\nimport type { HostSetting } from \"../rules/host.js\";\nimport { extractRules, promptFor, type Rule } from \"../rules/extract.js\";\nimport { GB, type LocalModels } from \"../rules/local.js\";\nimport { mockRequestsResponse, mockRulesResponse } from \"../rules/mock.js\";\nimport type { RuleSource } from \"../rules/sources.js\";\nimport { TracevalsError } from \"../types.js\";\n\nexport interface Identity {\n provider: ProviderName;\n model: string;\n}\n\n/**\n * Set on this process before any provider is built, so a judge it spawns\n * (`claude-cli`) runs its own hooks under gate 2 and never judges itself.\n */\nexport const JUDGE_ENV = \"MANNI_TRACEVALS_JUDGE\";\n\nexport function markJudgeProcess(): void {\n process.env[JUDGE_ENV] = \"1\";\n}\n\n/** Providers that never leave the machine, which `--offline` admits. */\nconst LOCAL_PROVIDERS: ReadonlySet<string> = new Set([\"llama-cpp\", \"mock\"]);\n\nexport function isNetworkProvider(provider: string): boolean {\n return !LOCAL_PROVIDERS.has(provider);\n}\n\n/** `--offline` met a role whose provider would leave the machine. */\nexport function offlineRefusal(role: string, identity: Identity): TracevalsError {\n return new TracevalsError(\n `--offline runs no network provider, and ${role} uses ${identity.provider}`,\n );\n}\n\n/** The first line of an error, as every message quoting one shows it. */\nexport function firstLine(err: unknown): string {\n return errorMessage(err).split(\"\\n\")[0] ?? \"\";\n}\n\n/** Defaults filled for a run by hand, where the section may be absent. */\nexport function conformanceOf(config: TracevalsConfig): NonNullable<TracevalsConfig[\"conformance\"]> {\n return (\n config.conformance ?? {\n hook: { provider: null, model: null, runs: 1 },\n include: [],\n exclude: [],\n plans: [],\n }\n );\n}\n\n/** The rules cache, beside the judge's, resolved as `run` resolves it. */\nexport function rulesCacheFor(\n config: TracevalsConfig,\n configDir: string,\n noCache: boolean,\n): RulesCache {\n return new RulesCache(resolve(configDir, config.judge.cacheDir, \"rules\"), !noCache);\n}\n\nexport interface Extraction {\n identity: Identity;\n cache: RulesCache;\n /** Whether a source still needs the model: not declared, not cached. */\n needs(source: RuleSource): boolean;\n /** A source's rules, declared or extracted. */\n rulesOf(source: RuleSource): Promise<{ rules: Rule[]; origin: \"declared\" | \"extracted\" }>;\n}\n\n/**\n * The out-of-loop model as extraction uses it: `tracevals.provider`/`model`,\n * then the family's `providers:`, then `auto`. No flag reaches it, so\n * `prepare`, the hook and `check` share one cache. jev is refused, because\n * extraction generates.\n *\n * The provider is built on the first uncached source, so a fully cached run\n * needs no API key. Under `mock` each source gets a scripted reading of its\n * own text.\n */\nexport async function extraction(\n config: TracevalsConfig,\n cache: RulesCache,\n injected?: InferenceProvider,\n host?: HostSetting,\n): Promise<Extraction> {\n let identity: Identity;\n if (injected !== undefined) {\n identity = { provider: injected.provider() as ProviderName, model: injected.modelName() };\n } else {\n const selection = selectProvider(config);\n assertProviderSelection(selection);\n identity = await resolveSelectionIdentity(config, selection);\n }\n let built: InferenceProvider | undefined = injected;\n const providerFor = (source: RuleSource): InferenceProvider => {\n // Under `mock` each source gets a scripted reading of its own text, so a\n // provider is built per source and never kept.\n if (built === undefined && identity.provider === \"mock\") {\n markJudgeProcess();\n const scripted =\n promptFor(source.format) === \"requests\"\n ? mockRequestsResponse(source.content)\n : mockRulesResponse(source.content);\n return constructProvider(config, identity, { mockResponses: [scripted] });\n }\n // Every other provider is built once, on the first uncached source.\n if (built === undefined) {\n markJudgeProcess();\n built = constructProvider(config, identity, host === undefined ? {} : { host });\n }\n return built;\n };\n const keyOf = (source: RuleSource): string =>\n rulesCacheKey({\n provider: identity.provider,\n model: identity.model,\n prompt: promptFor(source.format),\n temperature: config.judge.temperature,\n sha256: source.sha256,\n });\n return {\n identity,\n cache,\n needs: (source) =>\n source.declaredRules === undefined && cache.get(keyOf(source)) === undefined,\n rulesOf: async (source) => {\n if (source.declaredRules !== undefined) {\n return { rules: source.declaredRules, origin: \"declared\" };\n }\n const hit = cache.get(keyOf(source));\n if (hit !== undefined) return { rules: hit, origin: \"extracted\" };\n try {\n const extracted = await extractRules(\n {\n path: source.displayPath,\n format: source.format,\n content: source.content,\n sha256: source.sha256,\n },\n { provider: providerFor(source), cache, temperature: config.judge.temperature },\n );\n return { rules: extracted.rules, origin: \"extracted\" };\n } catch (err) {\n if (err instanceof TracevalsError) throw err;\n throw new TracevalsError(\n `could not extract rules from ${source.displayPath}: ${firstLine(err)}`,\n );\n }\n },\n };\n}\n\nexport type LocalGate = \"downloading\" | \"not-downloaded\" | \"memory\";\n\nexport interface GateSkip<G extends string = string> {\n gate: G;\n message: string;\n}\n\nconst wholeGb = (bytes: number): number => Math.round(bytes / GB);\n\n/**\n * Gates 5, 7 and 8 for one local model, in that order, each asked only when\n * listed. Nothing here loads or fetches a model: a runtime that is absent is\n * gate 7, and the memory probe is asked only once the runtime is there.\n *\n * The probe forks a worker and initializes the GPU to read free memory, which\n * costs most of a second. `memoryUnneeded` is asked first and, when it says so,\n * the probe is not run: memory only matters to a model about to be loaded.\n */\nexport async function localGate(\n local: LocalModels,\n model: string,\n gates: readonly LocalGate[],\n memoryUnneeded?: () => boolean | Promise<boolean>,\n): Promise<GateSkip<LocalGate> | undefined> {\n const state = await local.state(model);\n if (gates.includes(\"downloading\") && state === \"downloading\") {\n return { gate: \"downloading\", message: `tracevals skipped this turn: ${model} is still downloading.` };\n }\n if (gates.includes(\"not-downloaded\") && (state !== \"ready\" || !(await local.runtimePresent()))) {\n return {\n gate: \"not-downloaded\",\n message: `tracevals skipped this turn: ${model} is not downloaded yet. Run manni tracevals prepare to fetch it.`,\n };\n }\n if (gates.includes(\"memory\") && !(await memoryUnneeded?.())) {\n const fit = await local.fits(model);\n if (!fit.fits) {\n return {\n gate: \"memory\",\n message: `tracevals skipped this turn: ${model} needs about ${String(wholeGb(fit.needBytes))} GB and ${String(wholeGb(fit.freeBytes))} GB is free.`,\n };\n }\n }\n return undefined;\n}\n\n/** By hand a missing local model is an error, and nothing downloads. */\nexport async function assertDownloaded(local: LocalModels, model: string): Promise<void> {\n if ((await local.state(model)) === \"ready\" && (await local.runtimePresent())) return;\n throw new TracevalsError(`${model} is not downloaded; run manni tracevals prepare`);\n}\n","/**\n * Extracted-rules cache: content-addressed JSON holding the *gated* rules for\n * one rule-source file (proposal 0079, \"Extraction and its cache\").\n *\n * The key is the provider, the model, the prompt and its version, and the\n * sha256 of the whole file. The rules prompt and the requirements prompt read\n * one content differently, so each has entries of its own (proposal 0080).\n * The path is deliberately absent: identical content anywhere shares one\n * entry, so a vendored or copied rules file pays once.\n *\n * Reads go through the inference library's `JsonCache`. Writes do not: the\n * library writes in place, so a reader racing a writer could see half an\n * entry. A hook and a `check` run can overlap, so each entry is written to a\n * temporary file and renamed into place.\n */\nimport { mkdirSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { JsonCache, buildCacheKey } from \"@hawkeyexl/inference\";\nimport { warn } from \"../../shared/warn.js\";\nimport type { Rule } from \"./extract.js\";\nimport { RULES_PROMPT_VERSION } from \"./prompt.js\";\nimport { REQUIREMENTS_PROMPT_VERSION } from \"./requests-prompt.js\";\n\n/** Default location, under the tool's per-project cache directory. */\nexport const DEFAULT_RULES_CACHE_DIR = \".manni/tracevals/cache/rules\";\n\nexport interface RulesCacheKeyParts {\n provider: string;\n model: string;\n /** Which extraction prompt read the file. */\n prompt: \"rules\" | \"requests\";\n /** The extraction temperature, `judge.temperature`: another reading of one file. */\n temperature: number;\n /** sha256 of the whole source file. */\n sha256: string;\n}\n\nexport function rulesCacheKey(parts: RulesCacheKeyParts): string {\n return buildCacheKey([\n parts.provider,\n parts.model,\n // The rules prompt keeps the slot's old spelling, so rule files keep\n // their entries.\n parts.prompt === \"rules\"\n ? `rules-v${String(RULES_PROMPT_VERSION)}`\n : `requests-v${String(REQUIREMENTS_PROMPT_VERSION)}`,\n `t${String(parts.temperature)}`,\n parts.sha256,\n ]);\n}\n\nfunction isRule(value: unknown): value is Rule {\n if (typeof value !== \"object\" || value === null) return false;\n const r = value as Record<string, unknown>;\n return (\n typeof r.id === \"string\" &&\n typeof r.text === \"string\" &&\n (r.when === undefined ||\n (typeof r.when === \"object\" && r.when !== null && !Array.isArray(r.when)))\n );\n}\n\nexport class RulesCache {\n private readonly store: JsonCache<unknown>;\n private warned = false;\n\n constructor(\n private readonly dir: string = DEFAULT_RULES_CACHE_DIR,\n private readonly enabled: boolean = true,\n ) {\n this.store = new JsonCache<unknown>(dir, enabled, \"manni-tracevals\");\n }\n\n /** An entry that is not a list of rules, from an older shape, is a miss. */\n get(key: string): Rule[] | undefined {\n const value = this.store.get(key);\n if (!Array.isArray(value) || !value.every(isRule)) return undefined;\n return value;\n }\n\n set(key: string, rules: Rule[]): void {\n if (!this.enabled) return;\n const tmp = join(this.dir, `${key}.${process.pid}.${Date.now()}.tmp`);\n try {\n mkdirSync(this.dir, { recursive: true });\n writeFileSync(tmp, JSON.stringify(rules, null, 2));\n renameSync(tmp, join(this.dir, `${key}.json`));\n } catch (err) {\n rmSync(tmp, { force: true });\n if (!this.warned) {\n this.warned = true;\n warn(\n `could not write the rules cache at ${this.dir} (${err instanceof Error ? err.message : String(err)}). Continuing without caching.`,\n );\n }\n }\n }\n}\n","/**\n * Rule extraction prompt: asks the provider to read ONE rule-source file and\n * return only the directives a session transcript could show followed or\n * broken (proposal 0079, \"Only files that govern an agent\").\n *\n * The whole file goes in, uncapped. A cache keyed on slices would rarely hit,\n * and a partial read would let the agent off for sentences it never saw.\n */\nimport { Ajv2020 } from \"ajv/dist/2020.js\";\n\n/**\n * Part of the cache key: bump whenever the prompt or schema changes.\n *\n * A prompt edit that leaves this alone makes every cached extraction replay the\n * *old* prompt's output, silently, for as long as the cache lives.\n * `test/tracevals/unit/rules-prompt.test.ts` pins this to a digest of the\n * prompt surface so the pair has to move together.\n */\nexport const RULES_PROMPT_VERSION = 2;\n\n/** The `when` field and its conditions, which the requirements prompt shares. */\nexport const WHEN_GUIDE: readonly string[] = [\n \"- `when` (optional): a trigger saying which turns the rule concerns. Every\",\n \" listed condition must hold. The only conditions are:\",\n \" - `file-access`: a glob. The turn read, wrote or edited a matching file.\",\n \" - `tool-used`: a tool name. The turn called that tool.\",\n \" - `prompt-matches`: a regular expression. A prompt the user typed matches.\",\n \" - `turn-count-above`: a whole number. The session has more turns than that.\",\n \" - `command-matches`: a regular expression. A Bash command in the turn matches.\",\n \"\",\n \"Omit `when` unless the rule clearly concerns only certain files, tools or\",\n \"commands. A trigger that is too narrow hides violations, and a rule with no\",\n \"`when` is simply checked on every turn. When in doubt, leave it out.\",\n];\n\nexport const RULES_SYSTEM_PROMPT = [\n \"You extract rules from a file that governs an AI agent working in a repository.\",\n \"\",\n \"A rule is a directive addressed to the AI agent, which a recorded session\",\n \"transcript could show it following or breaking. The transcript holds the\",\n \"agent's prompts, its tool calls (with their inputs), the files it read or\",\n \"wrote, and its replies. Nothing else is observable.\",\n \"\",\n \"Return a rule only when the file tells the agent to do, or not to do,\",\n \"something. Return nothing for:\",\n \"- descriptions of how a system, tool or codebase behaves;\",\n \"- instructions written for end users or readers rather than the agent;\",\n \"- examples, sample output and code listings;\",\n \"- rules the file quotes from somewhere else, or reports as another party's.\",\n \"\",\n \"Keep a procedure's rules too, which a session shows across many turns:\",\n \"the order of steps, a gate that one step must pass before another, a route\",\n \"that a step's result chooses, a condition that calls for a step, a required\",\n \"step, and the step that comes last. Write each as its own rule.\",\n \"\",\n 'Drop a directive a transcript cannot show, such as \"prefer boring code\" or',\n '\"keep things simple\". If you cannot say what a violation would look like in',\n \"a transcript, it does not qualify.\",\n \"\",\n \"Each rule has:\",\n \"- `id`: a short kebab-case identifier, unique within this file.\",\n \"- `text`: one imperative sentence, faithful to the file. Do not strengthen,\",\n \" soften or merge directives, and do not invent ones the file lacks.\",\n ...WHEN_GUIDE,\n \"\",\n \"Return none at all when nothing qualifies.\",\n].join(\"\\n\");\n\nexport const RULES_SCHEMA = {\n type: \"object\",\n required: [\"rules\"],\n additionalProperties: false,\n properties: {\n rules: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"id\", \"text\"],\n additionalProperties: false,\n properties: {\n id: { type: \"string\" },\n text: { type: \"string\", minLength: 1 },\n // Open on purpose: `extractRules` gates each `when` with `validateWhen`\n // and drops the one rule, where a schema miss would void the whole file.\n when: { type: \"object\" },\n },\n },\n },\n },\n} as const;\n\nconst ajv = new Ajv2020({ allErrors: true });\nconst validateRules = ajv.compile(\n RULES_SCHEMA as unknown as Record<string, unknown>,\n);\n\nexport function isValidRules(value: unknown): boolean {\n return validateRules(value);\n}\n\nexport interface RulesUserOptions {\n path: string;\n format: string;\n content: string;\n}\n\nexport function buildRulesUser(options: RulesUserOptions): string {\n return [\n \"# Rule source\",\n `path: ${options.path}`,\n `format: ${options.format}`,\n \"\",\n \"# File content\",\n \"\",\n options.content,\n ].join(\"\\n\");\n}\n","/**\n * Requirements extraction prompt: asks the provider to read ONE source that\n * says what the session was asked to build or keep, and return each item as a\n * rule in 0079's shape (proposal 0080, \"What the session was asked\").\n *\n * The sources are the typed prompts, an approved plan, and spec files. The\n * response schema and the `when` grammar are the rules prompt's, so a\n * requirement is judged like any other rule.\n */\nimport { WHEN_GUIDE } from \"./prompt.js\";\n\n/**\n * Part of the cache key: bump whenever the prompt changes.\n * `test/tracevals/unit/requests-prompt.test.ts` pins this to a digest of the\n * prompt surface so the pair has to move together.\n */\nexport const REQUIREMENTS_PROMPT_VERSION = 1;\n\n/** The source formats that say what the session was asked, which this prompt reads. */\nconst REQUEST_FORMATS: ReadonlySet<string> = new Set([\n \"prompt\",\n \"plan\",\n \"speckit-spec\",\n \"kiro-spec\",\n \"openspec-change\",\n \"plans\",\n]);\n\nexport function isRequestFormat(format: string): boolean {\n return REQUEST_FORMATS.has(format);\n}\n\nexport const REQUIREMENTS_SYSTEM_PROMPT = [\n \"You read what an AI agent working in a repository was asked to build or keep,\",\n \"and return each item as a rule.\",\n \"\",\n \"The source is one of three things: the prompts a person typed in the\",\n \"session, numbered in order; a plan the person approved; or a spec file,\",\n \"such as a feature spec, a design, a task list or a change proposal.\",\n \"\",\n \"A rule is something the work must deliver or keep, which a recorded session\",\n \"transcript could show met or broken. The transcript holds the agent's\",\n \"prompts, its tool calls (with their inputs), the files it read or wrote, and\",\n \"its replies. Nothing else is observable.\",\n \"\",\n \"Typed prompts are one sequence. Return the set that stands after the last\",\n \"prompt: a later prompt can add an item, change an earlier one, or drop it.\",\n \"A question with nothing to build yields nothing. Never return chat, such as\",\n \"greetings, thanks, or remarks about the work.\",\n \"\",\n \"A spec's items keep the spec's own ids, exactly as written, such as\",\n \"`FR-001`, `T014` or `1.2`. A task the file marks done is still a rule:\",\n \"ticking it says that task is done.\",\n \"\",\n \"Return nothing for background, rationale, examples, open questions, or\",\n \"items the source marks out of scope.\",\n \"\",\n \"Each rule has:\",\n \"- `id`: the source's own id for the item when it gives one, exactly as\",\n \" written. Otherwise a short kebab-case identifier. Unique within this source.\",\n \"- `text`: one imperative sentence, faithful to the source. Do not strengthen,\",\n \" soften or merge items, and do not invent ones the source lacks.\",\n ...WHEN_GUIDE,\n \"\",\n \"Return none at all when nothing qualifies.\",\n].join(\"\\n\");\n\nexport interface RequirementsUserOptions {\n path: string;\n format: string;\n content: string;\n}\n\nexport function buildRequirementsUser(options: RequirementsUserOptions): string {\n return [\n \"# Request source\",\n // Callers pass the display path (`prompt`, `plan`); a blank one names the format.\n `path: ${options.path !== \"\" ? options.path : options.format}`,\n `format: ${options.format}`,\n \"\",\n \"# Content\",\n \"\",\n options.content,\n ].join(\"\\n\");\n}\n","/**\n * Extract the rules one rule-source file declares for the agent, then gate\n * them (proposal 0079). The model proposes; the gate decides what survives, and\n * a rule it drops is reported with a reason rather than lost.\n *\n * A source that says what the session was asked, such as the typed prompts, a\n * plan or a spec, is read by the requirements prompt instead (proposal 0080).\n * Its gate keeps a spec's own ids as written, so `T014` and `1.2` survive.\n */\nimport {\n completeValidatedJSON,\n type InferenceProvider,\n} from \"@hawkeyexl/inference\";\nimport { validateWhen } from \"../graders/when.js\";\nimport { type RulesCache, rulesCacheKey } from \"./cache.js\";\nimport {\n RULES_SCHEMA,\n RULES_SYSTEM_PROMPT,\n buildRulesUser,\n isValidRules,\n} from \"./prompt.js\";\nimport {\n REQUIREMENTS_SYSTEM_PROMPT,\n buildRequirementsUser,\n isRequestFormat,\n} from \"./requests-prompt.js\";\n\nexport interface Rule {\n id: string;\n text: string;\n when?: Record<string, unknown>;\n}\n\nexport interface ExtractSource {\n path: string;\n format: string;\n content: string;\n /** sha256 of `content`; the cache key, so the path never enters it. */\n sha256: string;\n}\n\nexport interface ExtractDeps {\n provider: InferenceProvider;\n cache?: RulesCache;\n temperature?: number;\n}\n\nexport interface ExtractedRules {\n rules: Rule[];\n cached: boolean;\n dropped: { id?: string; reason: string }[];\n}\n\nconst KEBAB = /^[a-z0-9][a-z0-9-]*$/;\n/** A spec's own id, such as `FR-001`, `T014` or `1.2`, or a kebab-case one. */\nconst SPEC_ID = /^[A-Za-z0-9][A-Za-z0-9.-]*$/;\n\n/** Each extraction prompt, and the ids its gate accepts. */\nconst PROMPTS = {\n rules: {\n system: RULES_SYSTEM_PROMPT,\n user: buildRulesUser,\n id: KEBAB,\n idReason: \"id is not kebab-case\",\n },\n requests: {\n system: REQUIREMENTS_SYSTEM_PROMPT,\n user: buildRequirementsUser,\n id: SPEC_ID,\n idReason: \"id is neither a spec's own id nor kebab-case\",\n },\n} as const;\n\nexport type ExtractionPrompt = keyof typeof PROMPTS;\n\n/** The prompt a source of this format is read with. */\nexport function promptFor(format: string): ExtractionPrompt {\n return isRequestFormat(format) ? \"requests\" : \"rules\";\n}\n\n/** Gate the model's rules: valid id, text, unique id, grounded `when`. */\nexport function gateRules(\n proposed: Rule[],\n prompt: ExtractionPrompt = \"rules\",\n): Pick<ExtractedRules, \"rules\" | \"dropped\"> {\n const { id: idPattern, idReason } = PROMPTS[prompt];\n const rules: Rule[] = [];\n const dropped: ExtractedRules[\"dropped\"] = [];\n const seen = new Set<string>();\n for (const proposedRule of proposed) {\n // An empty `when` names no condition, which is what an absent one means.\n // Local models write it that way, so it is read as absent, not refused.\n const { when, ...bare } = proposedRule;\n const rule: Rule = when === undefined || Object.keys(when).length === 0 ? bare : proposedRule;\n if (!idPattern.test(rule.id)) {\n dropped.push({ id: rule.id, reason: idReason });\n } else if (rule.text.trim() === \"\") {\n dropped.push({ id: rule.id, reason: \"text is empty\" });\n } else if (seen.has(rule.id)) {\n dropped.push({ id: rule.id, reason: \"id repeats an earlier rule\" });\n } else {\n const invalid =\n rule.when === undefined ? undefined : validateWhen({ when: rule.when });\n if (invalid !== undefined) {\n dropped.push({ id: rule.id, reason: invalid });\n } else {\n seen.add(rule.id);\n rules.push(rule);\n }\n }\n }\n return { rules, dropped };\n}\n\n/**\n * Throws when the provider errors or returns a shape the schema rejects, so a\n * caller can tell \"this file yielded no rules\" (an empty list) from \"the model\n * never answered\".\n */\nexport async function extractRules(\n source: ExtractSource,\n deps: ExtractDeps,\n): Promise<ExtractedRules> {\n const { provider, cache } = deps;\n const prompt = promptFor(source.format);\n const key = rulesCacheKey({\n provider: provider.provider(),\n model: provider.modelName(),\n prompt,\n temperature: deps.temperature ?? 0,\n sha256: source.sha256,\n });\n\n const hit = cache?.get(key);\n if (hit !== undefined) return { rules: hit, cached: true, dropped: [] };\n\n const run = await completeValidatedJSON<{ rules: Rule[] }>({\n provider,\n system: PROMPTS[prompt].system,\n user: PROMPTS[prompt].user(source),\n schema: RULES_SCHEMA,\n ...(deps.temperature !== undefined ? { temperature: deps.temperature } : {}),\n });\n if (run.result === undefined || !isValidRules(run.result)) {\n throw new Error(\n run.error ?? \"provider returned rules that do not match the schema\",\n );\n }\n\n const gated = gateRules(run.result.rules, prompt);\n cache?.set(key, gated.rules);\n return { ...gated, cached: false };\n}\n"],"mappings":"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","names":["reasoning"]}
|