structured-context 0.12.0 → 0.13.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 +29 -216
- package/dist/api.d.ts +17 -17
- package/dist/api.js +12 -12
- package/dist/commands/docs.js +1 -0
- package/dist/commands/providers.d.ts +4 -0
- package/dist/commands/providers.js +52 -0
- package/dist/commands/schemas.js +21 -2
- package/dist/commands/validate-file.d.ts +3 -0
- package/dist/commands/validate-file.js +17 -2
- package/dist/commands/validate.d.ts +4 -0
- package/dist/commands/validate.js +42 -3
- package/dist/config.d.ts +7 -0
- package/dist/config.js +26 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +59 -18
- package/dist/judge/context.d.ts +20 -0
- package/dist/judge/context.js +105 -0
- package/dist/judge/factory.d.ts +44 -0
- package/dist/judge/factory.js +148 -0
- package/dist/judge/index.d.ts +13 -0
- package/dist/judge/index.js +8 -0
- package/dist/judge/judge-node.d.ts +27 -0
- package/dist/judge/judge-node.js +122 -0
- package/dist/judge/prompt.d.ts +10 -0
- package/dist/judge/prompt.js +92 -0
- package/dist/judge/provider/anthropic.d.ts +17 -0
- package/dist/judge/provider/anthropic.js +43 -0
- package/dist/judge/provider/fake.d.ts +26 -0
- package/dist/judge/provider/fake.js +56 -0
- package/dist/judge/provider/jev.d.ts +37 -0
- package/dist/judge/provider/jev.js +169 -0
- package/dist/judge/provider/openai-decisions.d.ts +21 -0
- package/dist/judge/provider/openai-decisions.js +84 -0
- package/dist/judge/provider/openai.d.ts +20 -0
- package/dist/judge/provider/openai.js +53 -0
- package/dist/judge/provider/per-rule.d.ts +10 -0
- package/dist/judge/provider/per-rule.js +18 -0
- package/dist/judge/trim.d.ts +27 -0
- package/dist/judge/trim.js +62 -0
- package/dist/judge/types.d.ts +92 -0
- package/dist/judge/types.js +9 -0
- package/dist/judge/verdicts.d.ts +24 -0
- package/dist/judge/verdicts.js +82 -0
- package/dist/load-space-graph.d.ts +3 -3
- package/dist/load-space-graph.js +4 -4
- package/dist/plugins/loader.js +1 -1
- package/dist/plugins/markdown/parse-embedded.d.ts +2 -0
- package/dist/plugins/markdown/parse-embedded.js +161 -24
- package/dist/plugins/markdown/read-space.js +4 -9
- package/dist/plugins/markdown/render-directory.js +5 -5
- package/dist/plugins/markdown/template-sync.js +3 -4
- package/dist/plugins/markdown/util.d.ts +26 -5
- package/dist/plugins/markdown/util.js +56 -15
- package/dist/render/render.d.ts +1 -1
- package/dist/render/render.js +1 -1
- package/dist/schema/evaluate-rule.d.ts +33 -2
- package/dist/schema/evaluate-rule.js +58 -4
- package/dist/schema/gating.d.ts +69 -0
- package/dist/schema/gating.js +253 -0
- package/dist/schema/metadata-contract.d.ts +625 -12
- package/dist/schema/metadata-contract.js +110 -4
- package/dist/schema/rule-filter.d.ts +12 -0
- package/dist/schema/rule-filter.js +46 -0
- package/dist/schema/rule-severity.d.ts +13 -0
- package/dist/schema/rule-severity.js +33 -0
- package/dist/schema/schema.js +31 -1
- package/dist/schema/validate-rules.d.ts +17 -8
- package/dist/schema/validate-rules.js +91 -48
- package/dist/schemas/_knowledge_wiki.json +4 -4
- package/dist/schemas/_okf.json +172 -0
- package/dist/schemas/_ost_strict.json +15 -6
- package/dist/schemas/_sctx_base.json +1 -1
- package/dist/schemas/generated/0.13.0/_structured_context_schema_meta.json +400 -0
- package/dist/schemas/generated/latest/_structured_context_schema_meta.json +177 -6
- package/dist/schemas/okf.json +8 -0
- package/dist/space-context.d.ts +2 -2
- package/dist/space-context.js +3 -3
- package/dist/space-graph.d.ts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/types.d.ts +11 -4
- package/dist/validate.d.ts +59 -9
- package/dist/validate.js +225 -120
- package/docs/concepts.md +52 -6
- package/docs/config.md +70 -1
- package/docs/rules.md +140 -49
- package/docs/schemas.md +43 -18
- package/package.json +6 -5
- package/schemas/_knowledge_wiki.json +4 -4
- package/schemas/_okf.json +172 -0
- package/schemas/_ost_strict.json +15 -6
- package/schemas/_sctx_base.json +1 -1
- package/schemas/generated/0.13.0/_structured_context_schema_meta.json +400 -0
- package/schemas/generated/latest/_structured_context_schema_meta.json +177 -6
- package/schemas/okf.json +8 -0
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
2
|
import chokidar from 'chokidar';
|
|
3
3
|
import { getConfigSourceFiles } from '../config';
|
|
4
|
+
import { judgeForRun } from '../judge/factory';
|
|
4
5
|
import { resolveSpacePluginSchemas } from '../plugins/util';
|
|
6
|
+
import { parseRuleFilter } from '../schema/rule-filter';
|
|
5
7
|
import { bundledSchemasDir } from '../schema/schema';
|
|
6
|
-
import { formatErrors, validateSpace } from '../validate';
|
|
8
|
+
import { formatErrors, ruleViolationMessage, validateSpace } from '../validate';
|
|
7
9
|
export async function validate(context, options = {}) {
|
|
8
10
|
const { schema, schemaRefRegistry } = context;
|
|
9
|
-
const result = await validateSpace(context
|
|
11
|
+
const result = await validateSpace(context, {
|
|
12
|
+
depth: options.depth,
|
|
13
|
+
includeGated: options.includeGated,
|
|
14
|
+
judge: judgeForRun(context.config, context.space, options.semantic),
|
|
15
|
+
ruleFilter: options.ruleFilter && parseRuleFilter(options.ruleFilter),
|
|
16
|
+
});
|
|
10
17
|
const ruleCounts = { error: 0, warning: 0, info: 0 };
|
|
11
18
|
for (const v of result.ruleViolations)
|
|
12
19
|
ruleCounts[v.severity]++;
|
|
@@ -35,7 +42,7 @@ export async function validate(context, options = {}) {
|
|
|
35
42
|
}
|
|
36
43
|
for (const v of result.ruleViolations) {
|
|
37
44
|
if (v.file && v.severity === 'error') {
|
|
38
|
-
addError(v.file, `rule:${v.ruleId}`, 'rule',
|
|
45
|
+
addError(v.file, `rule:${v.ruleId}`, 'rule', ruleViolationMessage(v), v.severity);
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
48
|
const globalRuleErrorCount = result.ruleViolations.filter((v) => !v.file && v.severity === 'error').length;
|
|
@@ -56,6 +63,10 @@ export async function validate(context, options = {}) {
|
|
|
56
63
|
orphanCount: result.orphans.length,
|
|
57
64
|
parseIssues: result.parseIssues,
|
|
58
65
|
unresolvedContentLinks: result.unresolvedContentLinks,
|
|
66
|
+
coverageGates: result.coverageGates,
|
|
67
|
+
contextTrimmed: result.contextTrimmed,
|
|
68
|
+
skippedByDepth: result.skippedByDepth,
|
|
69
|
+
depthLimit: result.depthLimit,
|
|
59
70
|
}, null, 2));
|
|
60
71
|
return errorCount > 0 ? 1 : 0;
|
|
61
72
|
}
|
|
@@ -95,6 +106,11 @@ export async function validate(context, options = {}) {
|
|
|
95
106
|
console.log(fmt(' Unresolved content links', result.unresolvedContentLinks.length, true, true));
|
|
96
107
|
const parseIssueErrorCount = result.parseIssues.filter((i) => i.severity === 'error').length;
|
|
97
108
|
console.log(fmt(' Excluded during parsing', result.parseIssues.length, true, parseIssueErrorCount === 0));
|
|
109
|
+
if (result.depthLimit !== undefined) {
|
|
110
|
+
console.log(fmt(' Skipped by depth limit', result.skippedByDepth.length, true, true));
|
|
111
|
+
}
|
|
112
|
+
const gatedFileCount = result.coverageGates.reduce((sum, g) => sum + g.gatedFiles.length, 0);
|
|
113
|
+
console.log(fmt(' Rule checks gated (cascade)', gatedFileCount, true, true));
|
|
98
114
|
if (result.orphans.length > 0) {
|
|
99
115
|
console.log(`\nOrphans (hierarchy nodes - no parent):`);
|
|
100
116
|
for (const node of result.orphans)
|
|
@@ -159,6 +175,12 @@ export async function validate(context, options = {}) {
|
|
|
159
175
|
console.log(` ${severityColor[severity]}${severity.toUpperCase()}${reset} (${violations.length}):`);
|
|
160
176
|
for (const v of violations) {
|
|
161
177
|
console.log(` ${v.file ? `${v.file}: ` : ''}${v.description} [${v.category}: ${v.ruleId}]`);
|
|
178
|
+
if (v.evidence) {
|
|
179
|
+
console.log(` Evidence: ${v.evidence}`);
|
|
180
|
+
}
|
|
181
|
+
if (v.guidance) {
|
|
182
|
+
console.log(` Guidance: ${v.guidance}`);
|
|
183
|
+
}
|
|
162
184
|
}
|
|
163
185
|
}
|
|
164
186
|
}
|
|
@@ -168,6 +190,23 @@ export async function validate(context, options = {}) {
|
|
|
168
190
|
console.log(` ${v.file}: ${v.description}`);
|
|
169
191
|
}
|
|
170
192
|
}
|
|
193
|
+
if (result.coverageGates.length > 0) {
|
|
194
|
+
console.log(`\nCoverage — rule checks gated by cascade (use --include-gated to evaluate anyway):`);
|
|
195
|
+
for (const gate of result.coverageGates) {
|
|
196
|
+
const cause = gate.reason === 'rule' ? `blocking rule "${gate.ruleId}"` : `${gate.reason} failure`;
|
|
197
|
+
console.log(` ${gate.file} (${gate.nodeTitle}) — ${cause} gated ${gate.gatedFiles.length} node(s):`);
|
|
198
|
+
for (const file of gate.gatedFiles)
|
|
199
|
+
console.log(` ${file}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (result.skippedByDepth.length > 0) {
|
|
203
|
+
console.log(`\nSkipped by depth limit (--depth ${result.depthLimit}):`);
|
|
204
|
+
for (const { file } of result.skippedByDepth)
|
|
205
|
+
console.log(` ${file}`);
|
|
206
|
+
}
|
|
207
|
+
if (result.contextTrimmed.nodeCount > 0) {
|
|
208
|
+
console.log(`\nContext trimmed for ${result.contextTrimmed.nodeCount} node(s) across rules ${result.contextTrimmed.ruleIds.join(', ')}.`);
|
|
209
|
+
}
|
|
171
210
|
console.log(`\n`);
|
|
172
211
|
if (result.nodeErrorCount > 0 ||
|
|
173
212
|
result.refErrors.length > 0 ||
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type JudgeConfig, type ModelProviderConfig } from './judge/types.js';
|
|
1
2
|
export type SpaceConfig = {
|
|
2
3
|
name: string;
|
|
3
4
|
path: string;
|
|
@@ -8,6 +9,8 @@ export type SpaceConfig = {
|
|
|
8
9
|
views?: Record<string, {
|
|
9
10
|
expression: string;
|
|
10
11
|
}>;
|
|
12
|
+
/** Model provider for this space's semantic rules; overrides the top-level `semantic`. */
|
|
13
|
+
semantic?: JudgeConfig;
|
|
11
14
|
/**
|
|
12
15
|
* Directory of the config file that defined this space, used to anchor relative plugin-config
|
|
13
16
|
* paths. Set by loadConfig; absent for a hand-assembled Config, whose callers must pass an
|
|
@@ -19,6 +22,10 @@ export type Config = {
|
|
|
19
22
|
spaces: SpaceConfig[];
|
|
20
23
|
schema?: string;
|
|
21
24
|
includeSpacesFrom?: string[];
|
|
25
|
+
/** Model providers by name: how to reach each model. */
|
|
26
|
+
modelProviders?: Record<string, ModelProviderConfig>;
|
|
27
|
+
/** Model provider for semantic rules (a provider name). A space's `semantic` overrides it. */
|
|
28
|
+
semantic?: JudgeConfig;
|
|
22
29
|
};
|
|
23
30
|
/** Override the config file path used by loadConfig/updateSpaceField. */
|
|
24
31
|
export declare function setConfigPath(path: string | undefined): void;
|
package/dist/config.js
CHANGED
|
@@ -3,9 +3,29 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import Ajv2020 from 'ajv/dist/2020';
|
|
5
5
|
import JSON5 from 'json5';
|
|
6
|
-
import { ENV_CONFIG_VAR, XDG_CONFIG_DIR } from './constants';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { ENV_CONFIG_VAR, XDG_CONFIG_DIR } from './constants.js';
|
|
7
|
+
import { MODEL_PROVIDER_APIS } from './judge/types.js';
|
|
8
|
+
import { normalizePluginName, resolvePluginSchemasDir, resolveSpacePluginSchemas, shortenPluginName, } from './plugins/util.js';
|
|
9
|
+
import { bundledSchemasDir } from './schema/schema.js';
|
|
10
|
+
const MODEL_PROVIDER_SCHEMA = {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
api: { type: 'string', enum: MODEL_PROVIDER_APIS },
|
|
14
|
+
model: { type: 'string' },
|
|
15
|
+
modelEnv: { type: 'string' },
|
|
16
|
+
endpoint: { type: 'string' },
|
|
17
|
+
endpointEnv: { type: 'string' },
|
|
18
|
+
apiKey: { type: 'string' },
|
|
19
|
+
apiKeyEnv: { type: 'string' },
|
|
20
|
+
},
|
|
21
|
+
required: ['api'],
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
};
|
|
24
|
+
const SEMANTIC_CONFIG_SCHEMA = {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: { provider: { type: 'string', minLength: 1 } },
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
};
|
|
9
29
|
const CONFIG_SCHEMA = {
|
|
10
30
|
type: 'object',
|
|
11
31
|
properties: {
|
|
@@ -27,6 +47,7 @@ const CONFIG_SCHEMA = {
|
|
|
27
47
|
additionalProperties: false,
|
|
28
48
|
},
|
|
29
49
|
},
|
|
50
|
+
semantic: SEMANTIC_CONFIG_SCHEMA,
|
|
30
51
|
},
|
|
31
52
|
required: ['name', 'path'],
|
|
32
53
|
additionalProperties: false,
|
|
@@ -34,6 +55,8 @@ const CONFIG_SCHEMA = {
|
|
|
34
55
|
},
|
|
35
56
|
schema: { type: 'string' },
|
|
36
57
|
includeSpacesFrom: { type: 'array', items: { type: 'string' } },
|
|
58
|
+
modelProviders: { type: 'object', additionalProperties: MODEL_PROVIDER_SCHEMA },
|
|
59
|
+
semantic: SEMANTIC_CONFIG_SCHEMA,
|
|
37
60
|
},
|
|
38
61
|
anyOf: [{ required: ['spaces'] }, { required: ['includeSpacesFrom'] }],
|
|
39
62
|
additionalProperties: false,
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { format } from 'node:util';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
-
import { diagram } from './commands/diagram';
|
|
6
|
-
import { docs } from './commands/docs';
|
|
7
|
-
import { dump } from './commands/dump';
|
|
8
|
-
import { miroSyncCommand } from './commands/miro-sync';
|
|
9
|
-
import { listPlugins } from './commands/plugins';
|
|
10
|
-
import { render, renderList } from './commands/render';
|
|
11
|
-
import { listSchemas, showSchema } from './commands/schemas';
|
|
12
|
-
import { show } from './commands/show';
|
|
13
|
-
import { listSpaces } from './commands/spaces';
|
|
14
|
-
import { templateSync } from './commands/template-sync';
|
|
15
|
-
import { validate, watchValidate } from './commands/validate';
|
|
16
|
-
import { validateFileCommand } from './commands/validate-file';
|
|
17
|
-
import { loadConfig, setConfigPath } from './config';
|
|
18
|
-
import { CLI_NAME } from './constants';
|
|
19
|
-
import {
|
|
5
|
+
import { diagram } from './commands/diagram.js';
|
|
6
|
+
import { docs } from './commands/docs.js';
|
|
7
|
+
import { dump } from './commands/dump.js';
|
|
8
|
+
import { miroSyncCommand } from './commands/miro-sync.js';
|
|
9
|
+
import { listPlugins } from './commands/plugins.js';
|
|
10
|
+
import { render, renderList } from './commands/render.js';
|
|
11
|
+
import { listSchemas, showSchema } from './commands/schemas.js';
|
|
12
|
+
import { show } from './commands/show.js';
|
|
13
|
+
import { listSpaces } from './commands/spaces.js';
|
|
14
|
+
import { templateSync } from './commands/template-sync.js';
|
|
15
|
+
import { validate, watchValidate } from './commands/validate.js';
|
|
16
|
+
import { validateFileCommand } from './commands/validate-file.js';
|
|
17
|
+
import { loadConfig, setConfigPath } from './config.js';
|
|
18
|
+
import { CLI_NAME } from './constants.js';
|
|
19
|
+
import { JudgeConfigError } from './judge/factory.js';
|
|
20
|
+
import { createSpaceContext, SpaceNotFoundError } from './space-context.js';
|
|
20
21
|
// Once node:process is loaded (vfile, via unified, loads it), Bun's console.log drops piped output
|
|
21
22
|
// past 64KB when the reader is slow. process.stdout.write delivers all of it before exit.
|
|
22
23
|
console.log = (...args) => {
|
|
@@ -34,6 +35,18 @@ export function buildSpaceContext(spaceName) {
|
|
|
34
35
|
throw err;
|
|
35
36
|
}
|
|
36
37
|
}
|
|
38
|
+
/** Run a validate command, reporting a misconfigured model provider as a plain error rather than a stack trace. */
|
|
39
|
+
async function reportJudgeConfigErrors(run) {
|
|
40
|
+
try {
|
|
41
|
+
return await run();
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (!(err instanceof JudgeConfigError))
|
|
45
|
+
throw err;
|
|
46
|
+
console.error(`Error: ${err.message}`);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
37
50
|
const require = createRequire(import.meta.url);
|
|
38
51
|
const packageJson = require('../package.json');
|
|
39
52
|
const program = new Command();
|
|
@@ -50,8 +63,16 @@ program
|
|
|
50
63
|
.description('Validate a single file within its space')
|
|
51
64
|
.argument('<path>', 'Path to the file to validate')
|
|
52
65
|
.option('--json', 'Output results as JSON (machine-readable, for hooks)')
|
|
66
|
+
.option('--include-gated', 'Evaluate rules even when the file is gated by an upstream schema/hierarchy/rule failure (still reported as gated)')
|
|
67
|
+
.option('--rule-filter <dimension=values>', 'Evaluate only matching rules; dimensions: category, severity, type, id (repeatable, e.g. severity=error)', (v, prev = []) => [...prev, v])
|
|
68
|
+
.option('--semantic [provider]', 'Evaluate semantic rules, with the configured model provider or the one named')
|
|
53
69
|
.action(async (filePath, options) => {
|
|
54
|
-
process.exitCode = await validateFileCommand(filePath, {
|
|
70
|
+
process.exitCode = await reportJudgeConfigErrors(() => validateFileCommand(filePath, {
|
|
71
|
+
json: options.json,
|
|
72
|
+
includeGated: options.includeGated,
|
|
73
|
+
semantic: options.semantic,
|
|
74
|
+
ruleFilter: options.ruleFilter,
|
|
75
|
+
}));
|
|
55
76
|
});
|
|
56
77
|
program
|
|
57
78
|
.command('validate')
|
|
@@ -59,6 +80,10 @@ program
|
|
|
59
80
|
.argument('<space-name>', 'Space name')
|
|
60
81
|
.option('-w, --watch', 'Watch for changes and re-run validation')
|
|
61
82
|
.option('--json', 'Output results as JSON')
|
|
83
|
+
.option('--depth <n>', 'Evaluate only hierarchy nodes within N breadth-first levels from the top (e.g. 1 = root level only)', (v) => parseInt(v, 10))
|
|
84
|
+
.option('--include-gated', 'Evaluate rules even on nodes gated by an upstream schema/hierarchy/rule failure (still reported as gated)')
|
|
85
|
+
.option('--rule-filter <dimension=values>', 'Evaluate only matching rules; dimensions: category, severity, type, id (repeatable, e.g. severity=error)', (v, prev = []) => [...prev, v])
|
|
86
|
+
.option('--semantic [provider]', 'Evaluate semantic rules, with the configured model provider or the one named')
|
|
62
87
|
.action(async (spaceName, options) => {
|
|
63
88
|
const context = buildSpaceContext(spaceName);
|
|
64
89
|
if (options.watch) {
|
|
@@ -66,7 +91,13 @@ program
|
|
|
66
91
|
}
|
|
67
92
|
else {
|
|
68
93
|
// exitCode rather than process.exit(): exiting straight away can cut off large piped output
|
|
69
|
-
process.exitCode = await validate(context, {
|
|
94
|
+
process.exitCode = await reportJudgeConfigErrors(() => validate(context, {
|
|
95
|
+
json: options.json,
|
|
96
|
+
depth: options.depth,
|
|
97
|
+
includeGated: options.includeGated,
|
|
98
|
+
semantic: options.semantic,
|
|
99
|
+
ruleFilter: options.ruleFilter,
|
|
100
|
+
}));
|
|
70
101
|
}
|
|
71
102
|
});
|
|
72
103
|
program
|
|
@@ -120,6 +151,16 @@ spacesCmd
|
|
|
120
151
|
.option('--json', 'Output as JSON: config files, and each space with its path and schema')
|
|
121
152
|
.action((options) => listSpaces(options));
|
|
122
153
|
program.addCommand(spacesCmd);
|
|
154
|
+
const providersCmd = new Command('providers').description('List model providers (bundled and configured) with their effective config');
|
|
155
|
+
providersCmd
|
|
156
|
+
.command('list', { isDefault: true })
|
|
157
|
+
.description('List bundled and configured model providers; keys are never shown')
|
|
158
|
+
.option('--json', 'Output as JSON')
|
|
159
|
+
.action(async (options) => {
|
|
160
|
+
const { listProviders } = await import('./commands/providers.js');
|
|
161
|
+
listProviders(options);
|
|
162
|
+
});
|
|
163
|
+
program.addCommand(providersCmd);
|
|
123
164
|
const schemasCmd = new Command('schemas').alias('schema').description('List and inspect schemas');
|
|
124
165
|
schemasCmd
|
|
125
166
|
.command('list', { isDefault: true })
|
|
@@ -140,7 +181,7 @@ schemasCmd
|
|
|
140
181
|
program.addCommand(schemasCmd);
|
|
141
182
|
program
|
|
142
183
|
.command('docs [topic]')
|
|
143
|
-
.description('Show documentation (no arg: README; topics: concepts, config, schema, rules)')
|
|
184
|
+
.description('Show documentation (no arg: README; topics: concepts, config, schema, rules, semantic-rules)')
|
|
144
185
|
.action((topic) => docs(topic));
|
|
145
186
|
const renderCmd = new Command('render').description('Render a space in a given format');
|
|
146
187
|
renderCmd
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SEMANTIC_SCOPES } from '../schema/metadata-contract';
|
|
2
|
+
import type { SpaceGraph } from '../space-graph';
|
|
3
|
+
import type { Rule, SpaceNode } from '../types';
|
|
4
|
+
import type { RelatedNode, SemanticJudgeInput } from '../types.js';
|
|
5
|
+
export type SemanticScope = Exclude<(typeof SEMANTIC_SCOPES)[number], 'branch'>;
|
|
6
|
+
export type ContentMode = 'none' | 'nodeOnly' | 'fullScope';
|
|
7
|
+
/** What a rule asks to send besides the evaluated node's data. */
|
|
8
|
+
export interface RuleContext {
|
|
9
|
+
scope: SemanticScope[];
|
|
10
|
+
content: ContentMode;
|
|
11
|
+
}
|
|
12
|
+
/** The related nodes and bodies a semantic rule asks for. `includeContent: true` means `fullScope`. */
|
|
13
|
+
export declare function ruleContext(rule: Rule): RuleContext;
|
|
14
|
+
/**
|
|
15
|
+
* The nodes a scope adds for a node, along main-hierarchy edges only. Selection reads the graph's structure and
|
|
16
|
+
* nothing else: not verdicts, not gating. Each node appears once, with every relation it has, nearest first.
|
|
17
|
+
*/
|
|
18
|
+
export declare function relatedNodes(graph: SpaceGraph, node: SpaceNode, context: RuleContext): RelatedNode[];
|
|
19
|
+
/** The judge input context for rules that share `context`: the node view and its related nodes. */
|
|
20
|
+
export declare function judgeContext(graph: SpaceGraph, node: SpaceNode, context: RuleContext): Pick<SemanticJudgeInput, 'node' | 'related'>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { SEMANTIC_SCOPES } from '../schema/metadata-contract';
|
|
2
|
+
import { ruleSemantic } from '../schema/rule-severity';
|
|
3
|
+
/** The related nodes and bodies a semantic rule asks for. `includeContent: true` means `fullScope`. */
|
|
4
|
+
export function ruleContext(rule) {
|
|
5
|
+
const semantic = ruleSemantic(rule);
|
|
6
|
+
const include = semantic?.includeContent;
|
|
7
|
+
const asked = new Set(semantic?.scope ?? []);
|
|
8
|
+
if (asked.has('branch'))
|
|
9
|
+
asked.add('ancestors').add('descendants');
|
|
10
|
+
// A wider scope implies the narrower one, so equal node sets give equal inputs and direct relations keep their label.
|
|
11
|
+
if (asked.has('ancestors'))
|
|
12
|
+
asked.add('parent');
|
|
13
|
+
if (asked.has('descendants'))
|
|
14
|
+
asked.add('children');
|
|
15
|
+
return {
|
|
16
|
+
scope: SEMANTIC_SCOPES.filter((s) => s !== 'branch' && asked.has(s)),
|
|
17
|
+
content: include === undefined ? 'none' : include === true ? 'fullScope' : include,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** All schema data, without the body unless asked for. */
|
|
21
|
+
function dataWithBody(node, withBody) {
|
|
22
|
+
if (withBody)
|
|
23
|
+
return node.schemaData;
|
|
24
|
+
const { content: _content, ...data } = node.schemaData;
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
/** The evaluated node as a judge sees it. */
|
|
28
|
+
function judgeNodeView(node, withBody) {
|
|
29
|
+
return {
|
|
30
|
+
label: node.label,
|
|
31
|
+
title: node.title,
|
|
32
|
+
type: node.type,
|
|
33
|
+
resolvedType: node.resolvedType,
|
|
34
|
+
schemaData: dataWithBody(node, withBody),
|
|
35
|
+
resolvedParents: node.resolvedParents,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function relatedView(node, relation, withBody) {
|
|
39
|
+
const { title: _title, type: _type, ...data } = dataWithBody(node, withBody);
|
|
40
|
+
return { relations: [relation], title: node.title, type: node.resolvedType, data };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The nodes a scope adds for a node, along main-hierarchy edges only. Selection reads the graph's structure and
|
|
44
|
+
* nothing else: not verdicts, not gating. Each node appears once, with every relation it has, nearest first.
|
|
45
|
+
*/
|
|
46
|
+
export function relatedNodes(graph, node, context) {
|
|
47
|
+
const withBody = context.content === 'fullScope';
|
|
48
|
+
const hierarchyName = graph.hierarchy?.name;
|
|
49
|
+
const parentsOf = (n) => graph.hierarchy === undefined
|
|
50
|
+
? []
|
|
51
|
+
: n.resolvedParents
|
|
52
|
+
.filter((p) => p.source === 'hierarchy' && p.hierarchy === hierarchyName)
|
|
53
|
+
.map((p) => graph.nodes.get(p.title))
|
|
54
|
+
.filter((p) => p !== undefined);
|
|
55
|
+
const childrenOf = (n) => graph.hierarchyChildren.get(n.title) ?? [];
|
|
56
|
+
const found = new Map();
|
|
57
|
+
const add = (other, relation) => {
|
|
58
|
+
if (other.title === node.title)
|
|
59
|
+
return;
|
|
60
|
+
const existing = found.get(other.title);
|
|
61
|
+
if (!existing)
|
|
62
|
+
found.set(other.title, relatedView(other, relation, withBody));
|
|
63
|
+
else if (!existing.relations.includes(relation))
|
|
64
|
+
existing.relations.push(relation);
|
|
65
|
+
};
|
|
66
|
+
const reach = (next, relation) => {
|
|
67
|
+
const seen = new Set([node.title]);
|
|
68
|
+
let level = next(node);
|
|
69
|
+
while (level.length > 0) {
|
|
70
|
+
const following = [];
|
|
71
|
+
for (const n of level) {
|
|
72
|
+
if (seen.has(n.title))
|
|
73
|
+
continue;
|
|
74
|
+
seen.add(n.title);
|
|
75
|
+
add(n, relation);
|
|
76
|
+
following.push(...next(n));
|
|
77
|
+
}
|
|
78
|
+
level = following;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const scope = new Set(context.scope);
|
|
82
|
+
if (scope.has('parent'))
|
|
83
|
+
for (const p of parentsOf(node))
|
|
84
|
+
add(p, 'parent');
|
|
85
|
+
if (scope.has('siblings'))
|
|
86
|
+
for (const p of parentsOf(node))
|
|
87
|
+
for (const s of childrenOf(p))
|
|
88
|
+
add(s, 'sibling');
|
|
89
|
+
if (scope.has('children'))
|
|
90
|
+
for (const c of childrenOf(node))
|
|
91
|
+
add(c, 'child');
|
|
92
|
+
if (scope.has('ancestors'))
|
|
93
|
+
reach(parentsOf, 'ancestor');
|
|
94
|
+
if (scope.has('descendants'))
|
|
95
|
+
reach(childrenOf, 'descendant');
|
|
96
|
+
return [...found.values()];
|
|
97
|
+
}
|
|
98
|
+
/** The judge input context for rules that share `context`: the node view and its related nodes. */
|
|
99
|
+
export function judgeContext(graph, node, context) {
|
|
100
|
+
const related = relatedNodes(graph, node, context);
|
|
101
|
+
return {
|
|
102
|
+
node: judgeNodeView(node, context.content !== 'none'),
|
|
103
|
+
...(related.length > 0 && { related }),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type HydratedProvider, type ModelProviderConfig, type SemanticJudgeProvider } from '../types.js';
|
|
2
|
+
type Env = Record<string, string | undefined>;
|
|
3
|
+
/**
|
|
4
|
+
* Bundled providers: usable as a judge by name, without a `modelProviders` entry. A user entry of the
|
|
5
|
+
* same name replaces one wholesale; fields it leaves out take the type defaults in the constructors.
|
|
6
|
+
* Every value here is explicit: no bundle reads the environment.
|
|
7
|
+
*/
|
|
8
|
+
export declare const BUNDLED_PROVIDERS: Record<string, ModelProviderConfig>;
|
|
9
|
+
/** Semantic evaluation is misconfigured (no provider, unknown provider, missing key). Reported as a plain CLI error. */
|
|
10
|
+
export declare class JudgeConfigError extends Error {
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Hydrate a provider definition into concrete values, in one place. Per field: the explicit value
|
|
14
|
+
* wins, then the named environment variable if it is set (a set-but-empty variable is an error, not
|
|
15
|
+
* a silent fall-through), then the definition's own default. The key is special: it must resolve.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hydrateProvider(name: string, def: ModelProviderConfig, env?: Env): HydratedProvider;
|
|
18
|
+
/**
|
|
19
|
+
* Instantiate one provider from a definition. The API is always explicit and is never inferred from
|
|
20
|
+
* API keys in the environment, so a key set for another tool is never spent on judging. Constructors
|
|
21
|
+
* apply their own API defaults for fields left undefined after hydration.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createModelProvider(name: string, def: ModelProviderConfig, env?: Env): SemanticJudgeProvider;
|
|
24
|
+
/**
|
|
25
|
+
* The provider a CLI run asks for: none without `--semantic`; the named one with `--semantic <provider>`;
|
|
26
|
+
* else the configured one.
|
|
27
|
+
*/
|
|
28
|
+
export declare function judgeForRun(config: Parameters<typeof createJudge>[0], space: Parameters<typeof createJudge>[1], semantic?: boolean | string): SemanticJudgeProvider | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The provider for a run: the one named by `name` (the `--semantic <provider>` override), else the space's
|
|
31
|
+
* `semantic.provider`, else the top-level one. The name resolves against `modelProviders` first, then
|
|
32
|
+
* the bundled providers. Throws {@link JudgeConfigError} when it cannot be built.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createJudge(config: {
|
|
35
|
+
modelProviders?: Record<string, ModelProviderConfig>;
|
|
36
|
+
semantic?: {
|
|
37
|
+
provider?: string;
|
|
38
|
+
};
|
|
39
|
+
}, space?: {
|
|
40
|
+
semantic?: {
|
|
41
|
+
provider?: string;
|
|
42
|
+
};
|
|
43
|
+
}, name?: string, env?: Env): SemanticJudgeProvider;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { AnthropicJudgeProvider, RECOMMENDED_ANTHROPIC_MODEL } from './provider/anthropic';
|
|
2
|
+
import { FakeJudgeProvider } from './provider/fake';
|
|
3
|
+
import { JEV_LATEST, JevJudgeProvider } from './provider/jev';
|
|
4
|
+
import { OpenAIJudgeProvider, RECOMMENDED_OPENAI_MODEL } from './provider/openai';
|
|
5
|
+
import { OpenAIDecisionsJudgeProvider, RECOMMENDED_OPENAI_DECISIONS_MODEL } from './provider/openai-decisions';
|
|
6
|
+
import { MODEL_PROVIDER_APIS, } from '../types.js';
|
|
7
|
+
const OPENROUTER_COMPLETIONS_ENDPOINT = 'https://openrouter.ai/api/v1';
|
|
8
|
+
const RECOMMENDED_OPENROUTER_OPENAI_DECISIONS_MODEL = `openai/${RECOMMENDED_OPENAI_DECISIONS_MODEL}-decisions`;
|
|
9
|
+
const RECOMMENDED_OPENROUTER_OPENAI_MODEL = `openai/${RECOMMENDED_OPENAI_MODEL}`;
|
|
10
|
+
const ANTHROPIC_KEY_ENV = 'ANTHROPIC_API_KEY';
|
|
11
|
+
const OPENAI_KEY_ENV = 'OPENAI_API_KEY';
|
|
12
|
+
const OPENROUTER_KEY_ENV = 'OPENROUTER_API_KEY';
|
|
13
|
+
const ANTHROPIC_BASE = {
|
|
14
|
+
api: 'anthropic-messages',
|
|
15
|
+
model: RECOMMENDED_ANTHROPIC_MODEL,
|
|
16
|
+
apiKeyEnv: ANTHROPIC_KEY_ENV,
|
|
17
|
+
};
|
|
18
|
+
const OPENAI_BASE = {
|
|
19
|
+
api: 'openai-completions',
|
|
20
|
+
model: RECOMMENDED_OPENAI_MODEL,
|
|
21
|
+
apiKeyEnv: OPENAI_KEY_ENV,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Bundled providers: usable as a judge by name, without a `modelProviders` entry. A user entry of the
|
|
25
|
+
* same name replaces one wholesale; fields it leaves out take the type defaults in the constructors.
|
|
26
|
+
* Every value here is explicit: no bundle reads the environment.
|
|
27
|
+
*/
|
|
28
|
+
export const BUNDLED_PROVIDERS = {
|
|
29
|
+
'typesafe-jev': { api: 'typesafe-systemone', model: JEV_LATEST, apiKeyEnv: 'TYPESAFE_API_KEY' },
|
|
30
|
+
'openrouter-jev': { api: 'openrouter-decisions', model: `~typesafe/${JEV_LATEST}`, apiKeyEnv: OPENROUTER_KEY_ENV },
|
|
31
|
+
'openrouter-openai-decisions': {
|
|
32
|
+
api: 'openrouter-decisions',
|
|
33
|
+
model: RECOMMENDED_OPENROUTER_OPENAI_DECISIONS_MODEL,
|
|
34
|
+
apiKeyEnv: OPENROUTER_KEY_ENV,
|
|
35
|
+
},
|
|
36
|
+
'openai-decisions': {
|
|
37
|
+
api: 'openai-decisions',
|
|
38
|
+
model: RECOMMENDED_OPENAI_DECISIONS_MODEL,
|
|
39
|
+
apiKeyEnv: OPENAI_KEY_ENV,
|
|
40
|
+
},
|
|
41
|
+
anthropic: ANTHROPIC_BASE,
|
|
42
|
+
'anthropic-haiku-5-5': { api: 'anthropic-messages', model: 'claude-haiku-5-5', apiKeyEnv: ANTHROPIC_KEY_ENV },
|
|
43
|
+
'anthropic-env': {
|
|
44
|
+
...ANTHROPIC_BASE,
|
|
45
|
+
modelEnv: 'ANTHROPIC_MODEL',
|
|
46
|
+
},
|
|
47
|
+
openai: OPENAI_BASE,
|
|
48
|
+
'openai-env': {
|
|
49
|
+
...OPENAI_BASE,
|
|
50
|
+
modelEnv: 'OPENAI_MODEL',
|
|
51
|
+
endpointEnv: 'OPENAI_BASE_URL',
|
|
52
|
+
},
|
|
53
|
+
'openrouter-openai': {
|
|
54
|
+
api: 'openai-completions',
|
|
55
|
+
endpoint: OPENROUTER_COMPLETIONS_ENDPOINT,
|
|
56
|
+
model: RECOMMENDED_OPENROUTER_OPENAI_MODEL,
|
|
57
|
+
apiKeyEnv: OPENROUTER_KEY_ENV,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
/** Each API reads its key from this variable unless `apiKeyEnv` names another one. Model and endpoint never read env vars. */
|
|
61
|
+
const DEFAULT_KEY_ENV = {
|
|
62
|
+
'anthropic-messages': 'ANTHROPIC_API_KEY',
|
|
63
|
+
'openai-completions': 'OPENAI_API_KEY',
|
|
64
|
+
'openai-decisions': 'OPENAI_API_KEY',
|
|
65
|
+
'openrouter-decisions': 'OPENROUTER_API_KEY',
|
|
66
|
+
'typesafe-systemone': 'TYPESAFE_API_KEY',
|
|
67
|
+
};
|
|
68
|
+
/** Semantic evaluation is misconfigured (no provider, unknown provider, missing key). Reported as a plain CLI error. */
|
|
69
|
+
export class JudgeConfigError extends Error {
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Hydrate a provider definition into concrete values, in one place. Per field: the explicit value
|
|
73
|
+
* wins, then the named environment variable if it is set (a set-but-empty variable is an error, not
|
|
74
|
+
* a silent fall-through), then the definition's own default. The key is special: it must resolve.
|
|
75
|
+
*/
|
|
76
|
+
export function hydrateProvider(name, def, env = process.env) {
|
|
77
|
+
if (!MODEL_PROVIDER_APIS.includes(def.api)) {
|
|
78
|
+
throw new JudgeConfigError(`Model provider "${name}" has unknown api "${def.api}". Use one of: ${MODEL_PROVIDER_APIS.join(', ')}.`);
|
|
79
|
+
}
|
|
80
|
+
const fromEnv = (value, envName) => {
|
|
81
|
+
if (value !== undefined)
|
|
82
|
+
return value;
|
|
83
|
+
if (!envName)
|
|
84
|
+
return undefined;
|
|
85
|
+
const envValue = env[envName];
|
|
86
|
+
if (envValue !== undefined && envValue.trim() === '') {
|
|
87
|
+
throw new JudgeConfigError(`Model provider "${name}" has ${envName} set but empty; set a value or unset the variable.`);
|
|
88
|
+
}
|
|
89
|
+
return envValue;
|
|
90
|
+
};
|
|
91
|
+
const model = fromEnv(def.model, def.modelEnv);
|
|
92
|
+
const endpoint = fromEnv(def.endpoint, def.endpointEnv);
|
|
93
|
+
const keyEnv = def.apiKeyEnv ?? DEFAULT_KEY_ENV[def.api];
|
|
94
|
+
const apiKey = def.apiKey ?? (keyEnv ? env[keyEnv] : undefined);
|
|
95
|
+
return { api: def.api, model, endpoint, apiKey, keyEnv };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Instantiate one provider from a definition. The API is always explicit and is never inferred from
|
|
99
|
+
* API keys in the environment, so a key set for another tool is never spent on judging. Constructors
|
|
100
|
+
* apply their own API defaults for fields left undefined after hydration.
|
|
101
|
+
*/
|
|
102
|
+
export function createModelProvider(name, def, env = process.env) {
|
|
103
|
+
const h = hydrateProvider(name, def, env);
|
|
104
|
+
// OpenAI-compatible endpoints (Ollama, vLLM, ...) may need no key; the others must resolve one.
|
|
105
|
+
// A key never borrows another provider's variable: only the named apiKeyEnv (or literal apiKey) counts.
|
|
106
|
+
if (!h.apiKey && h.api !== 'openai-completions' && h.api !== 'fake') {
|
|
107
|
+
throw new JudgeConfigError(`Model provider "${name}" (${h.api}) has no API key. Set ${h.keyEnv} in the environment, or "apiKey" / "apiKeyEnv" on the provider.`);
|
|
108
|
+
}
|
|
109
|
+
switch (h.api) {
|
|
110
|
+
case 'fake':
|
|
111
|
+
return new FakeJudgeProvider();
|
|
112
|
+
case 'anthropic-messages':
|
|
113
|
+
return new AnthropicJudgeProvider(h);
|
|
114
|
+
case 'openrouter-decisions':
|
|
115
|
+
case 'typesafe-systemone':
|
|
116
|
+
return new JevJudgeProvider(h);
|
|
117
|
+
case 'openai-decisions':
|
|
118
|
+
return new OpenAIDecisionsJudgeProvider(h);
|
|
119
|
+
case 'openai-completions':
|
|
120
|
+
return new OpenAIJudgeProvider(h);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The provider a CLI run asks for: none without `--semantic`; the named one with `--semantic <provider>`;
|
|
125
|
+
* else the configured one.
|
|
126
|
+
*/
|
|
127
|
+
export function judgeForRun(config, space, semantic) {
|
|
128
|
+
if (!semantic)
|
|
129
|
+
return undefined;
|
|
130
|
+
return createJudge(config, space, typeof semantic === 'string' ? semantic : undefined);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The provider for a run: the one named by `name` (the `--semantic <provider>` override), else the space's
|
|
134
|
+
* `semantic.provider`, else the top-level one. The name resolves against `modelProviders` first, then
|
|
135
|
+
* the bundled providers. Throws {@link JudgeConfigError} when it cannot be built.
|
|
136
|
+
*/
|
|
137
|
+
export function createJudge(config, space = {}, name, env = process.env) {
|
|
138
|
+
const chosen = name ?? space.semantic?.provider ?? config.semantic?.provider;
|
|
139
|
+
if (!chosen) {
|
|
140
|
+
throw new JudgeConfigError('--semantic needs a model provider. Set "semantic.provider" in config.json, or pass --semantic <provider>.');
|
|
141
|
+
}
|
|
142
|
+
const def = config.modelProviders?.[chosen] ?? BUNDLED_PROVIDERS[chosen];
|
|
143
|
+
if (!def) {
|
|
144
|
+
const known = [...Object.keys(BUNDLED_PROVIDERS), ...Object.keys(config.modelProviders ?? {})];
|
|
145
|
+
throw new JudgeConfigError(`Model provider "${chosen}" is not configured.${known.length ? ` Known providers: ${known.join(', ')}.` : ''}`);
|
|
146
|
+
}
|
|
147
|
+
return createModelProvider(chosen, def, env);
|
|
148
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { createJudge, createModelProvider, JudgeConfigError } from './factory';
|
|
2
|
+
export { buildJudgeSystemPrompt, buildJudgeUserPrompt, parseJudgeResponse } from './prompt';
|
|
3
|
+
export type { AnthropicJudgeProviderOptions } from './provider/anthropic';
|
|
4
|
+
export { AnthropicJudgeProvider } from './provider/anthropic';
|
|
5
|
+
export type { FakeAnswer, FakeJudgeHandler } from './provider/fake';
|
|
6
|
+
export { FakeJudgeProvider } from './provider/fake';
|
|
7
|
+
export type { JevJudgeProviderOptions } from './provider/jev';
|
|
8
|
+
export { JevJudgeProvider } from './provider/jev';
|
|
9
|
+
export type { OpenAIJudgeProviderOptions } from './provider/openai';
|
|
10
|
+
export { OpenAIJudgeProvider } from './provider/openai';
|
|
11
|
+
export { PerRuleJudgeProvider } from './provider/per-rule';
|
|
12
|
+
export type { JudgeConfig, ModelProviderConfig, SemanticJudgeInput, SemanticJudgeProvider, SemanticJudgeResult, SemanticJudgeVerdict, SemanticRuleSpec, } from '../types.js';
|
|
13
|
+
export { DEFAULT_VERDICTS, isJudgeError, semanticRuleSpec } from './verdicts';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createJudge, createModelProvider, JudgeConfigError } from './factory';
|
|
2
|
+
export { buildJudgeSystemPrompt, buildJudgeUserPrompt, parseJudgeResponse } from './prompt';
|
|
3
|
+
export { AnthropicJudgeProvider } from './provider/anthropic';
|
|
4
|
+
export { FakeJudgeProvider } from './provider/fake';
|
|
5
|
+
export { JevJudgeProvider } from './provider/jev';
|
|
6
|
+
export { OpenAIJudgeProvider } from './provider/openai';
|
|
7
|
+
export { PerRuleJudgeProvider } from './provider/per-rule';
|
|
8
|
+
export { DEFAULT_VERDICTS, isJudgeError, semanticRuleSpec } from './verdicts';
|