deepclause-pi 0.3.0 → 0.4.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/dist/config.d.ts CHANGED
@@ -1,4 +1,17 @@
1
1
  export type ContextMode = "turn" | "branch" | "isolated";
2
+ export interface JevJudgeConfig {
3
+ /** Whether the Jev (TypeSafe System One) backend may be used. */
4
+ enabled: boolean;
5
+ /** Model alias or pinned version passed to TypeSafe. */
6
+ model: string;
7
+ /** Environment variable holding the TypeSafe API key. */
8
+ apiKeyEnv: string;
9
+ }
10
+ export interface JudgmentConfig {
11
+ /** Default backend name: "llm", "jev", or a name registered by an extension. */
12
+ default: string;
13
+ jev: JevJudgeConfig;
14
+ }
2
15
  export interface DeepClauseConfig {
3
16
  version: 1;
4
17
  contextMode: ContextMode;
@@ -7,7 +20,16 @@ export interface DeepClauseConfig {
7
20
  maxTokens: number;
8
21
  verbose: boolean;
9
22
  modelToolEnabled: boolean;
23
+ judgment: JudgmentConfig;
10
24
  }
11
25
  export declare const DEFAULT_CONFIG: DeepClauseConfig;
12
26
  export declare function loadConfig(path: string): Promise<DeepClauseConfig>;
13
27
  export declare function setModelToolEnabled(configPath: string, enabled: boolean): Promise<DeepClauseConfig>;
28
+ export interface JudgeConfigPatch {
29
+ /** Default backend name ("llm" or "jev"). */
30
+ default?: string;
31
+ /** Partial update of the Jev backend settings. */
32
+ jev?: Partial<JevJudgeConfig>;
33
+ }
34
+ /** Merge a judgment patch into the workspace config, preserving every other field. */
35
+ export declare function setJudgeConfig(configPath: string, patch: JudgeConfigPatch): Promise<DeepClauseConfig>;
package/dist/config.js CHANGED
@@ -7,6 +7,10 @@ export const DEFAULT_CONFIG = {
7
7
  maxTokens: 16_384,
8
8
  verbose: false,
9
9
  modelToolEnabled: false,
10
+ judgment: {
11
+ default: "llm",
12
+ jev: { enabled: false, model: "jev-latest", apiKeyEnv: "TYPESAFE_API_KEY" },
13
+ },
10
14
  };
11
15
  const isContextMode = (value) => value === "turn" || value === "branch" || value === "isolated";
12
16
  export async function loadConfig(path) {
@@ -40,6 +44,40 @@ export async function loadConfig(path) {
40
44
  maxTokens: positiveInteger("maxTokens", DEFAULT_CONFIG.maxTokens),
41
45
  verbose: config.verbose === true,
42
46
  modelToolEnabled: config.modelToolEnabled === true,
47
+ judgment: parseJudgmentConfig(config.judgment),
48
+ };
49
+ }
50
+ function parseJudgmentConfig(value) {
51
+ if (value === undefined)
52
+ return DEFAULT_CONFIG.judgment;
53
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
54
+ throw new Error("judgment must be a JSON object");
55
+ }
56
+ const judgment = value;
57
+ const defaultBackend = judgment.default ?? DEFAULT_CONFIG.judgment.default;
58
+ if (typeof defaultBackend !== "string" || !defaultBackend.trim()) {
59
+ throw new Error("judgment.default must be a non-empty backend name");
60
+ }
61
+ const jevValue = judgment.jev ?? {};
62
+ if (!jevValue || typeof jevValue !== "object" || Array.isArray(jevValue)) {
63
+ throw new Error("judgment.jev must be a JSON object");
64
+ }
65
+ const jev = jevValue;
66
+ const model = jev.model ?? DEFAULT_CONFIG.judgment.jev.model;
67
+ const apiKeyEnv = jev.apiKeyEnv ?? DEFAULT_CONFIG.judgment.jev.apiKeyEnv;
68
+ if (typeof model !== "string" || !model.trim()) {
69
+ throw new Error("judgment.jev.model must be a non-empty string");
70
+ }
71
+ if (typeof apiKeyEnv !== "string" || !apiKeyEnv.trim()) {
72
+ throw new Error("judgment.jev.apiKeyEnv must be a non-empty environment variable name");
73
+ }
74
+ return {
75
+ default: defaultBackend.trim(),
76
+ jev: {
77
+ enabled: jev.enabled === true,
78
+ model: model.trim(),
79
+ apiKeyEnv: apiKeyEnv.trim(),
80
+ },
43
81
  };
44
82
  }
45
83
  export async function setModelToolEnabled(configPath, enabled) {
@@ -57,3 +95,25 @@ export async function setModelToolEnabled(configPath, enabled) {
57
95
  await writeFile(configPath, `${JSON.stringify({ ...existing, modelToolEnabled: enabled }, null, 2)}\n`, "utf8");
58
96
  return loadConfig(configPath);
59
97
  }
98
+ /** Merge a judgment patch into the workspace config, preserving every other field. */
99
+ export async function setJudgeConfig(configPath, patch) {
100
+ let existing = {};
101
+ try {
102
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
103
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
104
+ existing = parsed;
105
+ }
106
+ catch (error) {
107
+ if (error.code !== "ENOENT") {
108
+ throw new Error(`Invalid DeepClause config: ${error instanceof Error ? error.message : String(error)}`);
109
+ }
110
+ }
111
+ const record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
112
+ const nextJudgment = {
113
+ ...record(existing.judgment),
114
+ ...(patch.default !== undefined ? { default: patch.default } : {}),
115
+ jev: { ...record(record(existing.judgment).jev), ...(patch.jev ?? {}) },
116
+ };
117
+ await writeFile(configPath, `${JSON.stringify({ ...existing, judgment: nextJudgment }, null, 2)}\n`, "utf8");
118
+ return loadConfig(configPath);
119
+ }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export interface ParsedRun {
6
6
  contextMode?: ContextMode;
7
7
  verbose: boolean;
8
8
  debug: boolean;
9
+ judge?: string;
9
10
  }
10
11
  interface ParsedPlan {
11
12
  request: string;
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import { StringEnum } from "@earendil-works/pi-ai";
5
5
  import { Type } from "typebox";
6
6
  import { buildInitialMessages } from "./context.js";
7
- import { loadConfig, setModelToolEnabled } from "./config.js";
7
+ import { loadConfig, setJudgeConfig, setModelToolEnabled } from "./config.js";
8
8
  import { renderDml, renderSequence } from "./diagram/extract.js";
9
9
  import { polishDiagram, resolveGrade } from "./diagram/grade.js";
10
10
  import { findChrome, validateMermaid } from "./diagram/validate.js";
@@ -68,10 +68,12 @@ export function parseRun(input) {
68
68
  let contextMode;
69
69
  let verbose = false;
70
70
  let debug = false;
71
+ let judge;
71
72
  const args = [];
72
73
  for (let index = 0; index < tokens.length; index++) {
73
74
  const token = tokens[index];
74
75
  const contextValue = token.startsWith("--context=") ? token.slice("--context=".length) : undefined;
76
+ const judgeValue = token.startsWith("--judge=") ? token.slice("--judge=".length) : undefined;
75
77
  if (token === "--verbose" || token === "-v") {
76
78
  verbose = true;
77
79
  }
@@ -92,11 +94,22 @@ export function parseRun(input) {
92
94
  }
93
95
  contextMode = value;
94
96
  }
97
+ else if (judgeValue !== undefined) {
98
+ if (!judgeValue.trim())
99
+ throw new Error("--judge requires a non-empty backend name");
100
+ judge = judgeValue.trim();
101
+ }
102
+ else if (token === "--judge") {
103
+ const value = tokens[++index];
104
+ if (!value || !value.trim())
105
+ throw new Error("--judge requires a non-empty backend name");
106
+ judge = value.trim();
107
+ }
95
108
  else {
96
109
  args.push(token);
97
110
  }
98
111
  }
99
- return { target, args, contextMode, verbose, debug };
112
+ return { target, args, contextMode, verbose, debug, judge };
100
113
  }
101
114
  export function parsePlan(input) {
102
115
  const tokens = splitArguments(input);
@@ -242,6 +255,17 @@ async function listDmlFiles(directory, prefix = "") {
242
255
  function modelLabel(ctx) {
243
256
  return ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "none selected";
244
257
  }
258
+ function judgeBackendsEnabled(config) {
259
+ return config.judgment.jev.enabled ? ", jev" : "";
260
+ }
261
+ function jevStatus(config) {
262
+ const jev = config.judgment.jev;
263
+ if (!jev.enabled)
264
+ return "disabled";
265
+ return process.env[jev.apiKeyEnv]
266
+ ? `enabled (${jev.model})`
267
+ : `enabled but ${jev.apiKeyEnv} is not set`;
268
+ }
245
269
  async function bundledViewerTemplate() {
246
270
  return readFile(fileURLToPath(new URL("./assets/viewer.template.html", import.meta.url)), "utf8");
247
271
  }
@@ -727,7 +751,7 @@ export default function deepClauseExtension(pi) {
727
751
  const viewer = displayPath(ctx.cwd, build.viewerPath);
728
752
  return {
729
753
  content: [{ type: "text", text: `Created spec graph (${view}). Viewer: ${viewer}${opened ? " (opened in your browser)" : ""}` }],
730
- details: { success: true, view, viewer, opened },
754
+ details: { success: true, view, viewer, viewerPath: build.viewerPath, opened },
731
755
  };
732
756
  }
733
757
  catch (error) {
@@ -844,6 +868,8 @@ export default function deepClauseExtension(pi) {
844
868
  `Skills: ${path.relative(ctx.cwd, paths.skills)}`,
845
869
  `Plans: ${path.relative(ctx.cwd, paths.plans)}`,
846
870
  `Context: ${config.contextMode} (verbose default: ${config.verbose})`,
871
+ `Judgment: ${config.judgment.default} (backends: llm${judgeBackendsEnabled(config)})`,
872
+ `Jev: ${jevStatus(config)}`,
847
873
  `Model tool (${DC_RUN_TOOL}): ${config.modelToolEnabled && pi.getActiveTools().includes(DC_RUN_TOOL) ? "enabled" : "disabled"}`,
848
874
  `Model tool (${DC_DIAGRAM_TOOL}): ${pi.getActiveTools().includes(DC_DIAGRAM_TOOL) ? "enabled" : "disabled"}`,
849
875
  "Ask pi for a presentation-grade or specification-grade diagram of any .dml file;",
@@ -854,10 +880,13 @@ export default function deepClauseExtension(pi) {
854
880
  " /dc-check <change|spec> validate specs and deltas deterministically",
855
881
  " /dc-archive <change> merge a change delta into specs/ and archive it",
856
882
  " /dc-apply <change> [--abort] execute tasks.dml; --abort discards an interrupted apply",
857
- " /dc-run <skill|path> [args] [--context=turn|branch|isolated]",
883
+ " /dc-run <skill|path> [args] [--context=turn|branch|isolated] [--judge=llm|jev]",
858
884
  " /dc-run <skill|path> --verbose show lifecycle events",
859
885
  " /dc-run <skill|path> --debug show full event payloads and SDK diagnostics",
860
886
  " /dc-tool enable|disable|status control the model-callable dc_run tool",
887
+ " /dc-judge [enable|disable|status] select the judgment backend (llm|jev)",
888
+ " /dc-judge default llm|jev set the default judgment backend",
889
+ " /dc-judge model <name> | key-env <ENV_VAR>",
861
890
  " /dc-cancel",
862
891
  ].join("\n");
863
892
  ctx.ui.notify(message, "info");
@@ -949,6 +978,73 @@ export default function deepClauseExtension(pi) {
949
978
  }
950
979
  },
951
980
  });
981
+ pi.registerCommand("dc-judge", {
982
+ description: "Enable, disable, or select the semantic judgment backend (llm|jev)",
983
+ handler: async (rawArgs, ctx) => {
984
+ const tokens = rawArgs.trim().split(/\s+/).filter(Boolean);
985
+ const action = (tokens[0] ?? "status").toLowerCase();
986
+ const paths = await initializeWorkspace(ctx.cwd);
987
+ const describe = (config) => {
988
+ const jev = config.judgment.jev;
989
+ const keyPresent = Boolean(process.env[jev.apiKeyEnv]);
990
+ return [
991
+ `judgment backend: ${config.judgment.default}`,
992
+ `registered backends: llm${jev.enabled ? ", jev" : ""}`,
993
+ `jev: ${jev.enabled ? "enabled" : "disabled"} | model=${jev.model} | ${jev.apiKeyEnv}=${keyPresent ? "set" : "not set"}`,
994
+ ].join("\n");
995
+ };
996
+ try {
997
+ if (action === "status") {
998
+ ctx.ui.notify(describe(await loadConfig(paths.config)), "info");
999
+ return;
1000
+ }
1001
+ if (action === "enable" || action === "on" || action === "disable" || action === "off") {
1002
+ const enabled = action === "enable" || action === "on";
1003
+ const config = await setJudgeConfig(paths.config, { jev: { enabled } });
1004
+ const jev = config.judgment.jev;
1005
+ const note = enabled && !process.env[jev.apiKeyEnv]
1006
+ ? `\n${jev.apiKeyEnv} is not set; export it before using --judge=jev.`
1007
+ : "";
1008
+ ctx.ui.notify(`Jev backend ${enabled ? "enabled" : "disabled"} for this workspace.${note}`, enabled ? "warning" : "info");
1009
+ return;
1010
+ }
1011
+ if (action === "default") {
1012
+ const name = tokens[1];
1013
+ if (name !== "llm" && name !== "jev") {
1014
+ ctx.ui.notify("Usage: /dc-judge default llm|jev", "warning");
1015
+ return;
1016
+ }
1017
+ await setJudgeConfig(paths.config, { default: name });
1018
+ ctx.ui.notify(`Default judgment backend set to '${name}'`, "info");
1019
+ return;
1020
+ }
1021
+ if (action === "model") {
1022
+ const model = tokens[1];
1023
+ if (!model) {
1024
+ ctx.ui.notify("Usage: /dc-judge model <name>", "warning");
1025
+ return;
1026
+ }
1027
+ await setJudgeConfig(paths.config, { jev: { model } });
1028
+ ctx.ui.notify(`Jev model set to '${model}'`, "info");
1029
+ return;
1030
+ }
1031
+ if (action === "key-env") {
1032
+ const envName = tokens[1];
1033
+ if (!envName) {
1034
+ ctx.ui.notify("Usage: /dc-judge key-env <ENV_VAR>", "warning");
1035
+ return;
1036
+ }
1037
+ await setJudgeConfig(paths.config, { jev: { apiKeyEnv: envName } });
1038
+ ctx.ui.notify(`Jev API key environment variable set to '${envName}'`, "info");
1039
+ return;
1040
+ }
1041
+ ctx.ui.notify("Usage: /dc-judge [enable|disable|status|default llm|jev|model <name>|key-env <ENV_VAR>]", "warning");
1042
+ }
1043
+ catch (error) {
1044
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1045
+ }
1046
+ },
1047
+ });
952
1048
  pi.registerCommand("dc-list", {
953
1049
  description: "List DeepClause DML skills and generated plans",
954
1050
  handler: async (_args, ctx) => {
@@ -1209,7 +1305,7 @@ export default function deepClauseExtension(pi) {
1209
1305
  renderExecution();
1210
1306
  };
1211
1307
  try {
1212
- const result = await executeDml(filePath, parsed.args, initialMessages, { ...config, verbose: config.verbose || parsed.debug }, pi, ctx, controller, {
1308
+ const result = await executeDml(filePath, parsed.args, initialMessages, { ...config, verbose: config.verbose || parsed.debug, judgeBackend: parsed.judge }, pi, ctx, controller, {
1213
1309
  onEvent,
1214
1310
  onDiagnostic,
1215
1311
  onInput: async (prompt, signal) => {
package/dist/runtime.d.ts CHANGED
@@ -41,4 +41,6 @@ export interface ExecutionResult {
41
41
  errors: string[];
42
42
  usage: LLMUsage;
43
43
  }
44
- export declare function executeDml(filePath: string, args: string[], initialMessages: MemoryMessage[], config: DeepClauseConfig, pi: ExtensionAPI, ctx: ExtensionContext, controller: AbortController, callbacks: ExecutionCallbacks, runPiAgentStep?: (request: PiAgentStepRequest, signal: AbortSignal) => Promise<PiAgentStepResult>, verifyCommands?: string[], applyChangeJsonPath?: string): Promise<ExecutionResult>;
44
+ export declare function executeDml(filePath: string, args: string[], initialMessages: MemoryMessage[], config: DeepClauseConfig & {
45
+ judgeBackend?: string;
46
+ }, pi: ExtensionAPI, ctx: ExtensionContext, controller: AbortController, callbacks: ExecutionCallbacks, runPiAgentStep?: (request: PiAgentStepRequest, signal: AbortSignal) => Promise<PiAgentStepResult>, verifyCommands?: string[], applyChangeJsonPath?: string): Promise<ExecutionResult>;
package/dist/runtime.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { realpath, readFile, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { createDeepClause } from "deepclause-sdk";
3
+ import { createDeepClause, createJevJudgeBackend, createLLMJudgeBackend } from "deepclause-sdk";
4
4
  import { PI_AGENT_STEP_TOOL } from "./planner.js";
5
5
  export const PI_WORKSPACE_LIST_TOOL = "pi_workspace_list";
6
6
  export const PI_BASH_TOOL = "pi_bash";
@@ -265,12 +265,33 @@ export async function executeDml(filePath, args, initialMessages, config, pi, ct
265
265
  if (!model)
266
266
  throw new Error("Select a pi model before running DeepClause");
267
267
  const backend = createPiBackend(ctx, config.maxTokens, callbacks.onDiagnostic ?? (() => { }));
268
+ // Judgment backends. `llm` reuses pi's active model and credentials; `jev`
269
+ // is opt-in and only registered when enabled and its key is present.
270
+ const judgeBackends = {
271
+ llm: createLLMJudgeBackend({ llmBackend: backend, model: model.id }),
272
+ };
273
+ const jev = config.judgment.jev;
274
+ if (jev.enabled) {
275
+ const apiKey = process.env[jev.apiKeyEnv];
276
+ if (apiKey) {
277
+ judgeBackends.jev = createJevJudgeBackend({ apiKey, model: jev.model });
278
+ }
279
+ else {
280
+ callbacks.onDiagnostic?.(`judgment backend 'jev' is enabled but ${jev.apiKeyEnv} is not set; falling back to 'llm'`);
281
+ }
282
+ }
283
+ const requestedJudge = config.judgeBackend ?? config.judgment.default;
284
+ if (!judgeBackends[requestedJudge]) {
285
+ throw new Error(`Judgment backend '${requestedJudge}' is not available. Enable it in .pi/deepclause/config.json or use --judge=llm.`);
286
+ }
268
287
  const sdk = await createDeepClause({
269
288
  model: model.id,
270
289
  maxTokens: config.maxTokens,
271
290
  streaming: true,
272
291
  debug: config.verbose,
273
292
  llmBackend: backend,
293
+ judgeBackends,
294
+ defaultJudge: requestedJudge,
274
295
  });
275
296
  registerPiRuntimeTools(sdk, pi, ctx.cwd, controller.signal, async (command, signal) => {
276
297
  if (!ctx.hasUI)
@@ -371,6 +392,7 @@ export async function executeDml(filePath, args, initialMessages, config, pi, ct
371
392
  gasLimit: config.gasLimit,
372
393
  signal: controller.signal,
373
394
  initialMessages,
395
+ judgeBackend: config.judgeBackend,
374
396
  onUserInput: (prompt) => callbacks.onInput(prompt, controller.signal),
375
397
  })) {
376
398
  callbacks.onEvent(event);
package/docs/SPECKIT.md CHANGED
@@ -16,6 +16,16 @@ Spec-driven changes for pi, without leaving the session.
16
16
  /dc-archive <slug> merge into specs/
17
17
  ```
18
18
 
19
+ ## Why DML and DeepClause for spec-driven development
20
+
21
+ Most spec tools are a Markdown convention plus a program that parses it. The convention is the good idea; the program is where it gets fragile, because parsing, validating and merging structured text is usually written as regexes and imperative branches. OpenSpec, for example, needs a few thousand lines of TypeScript for what is essentially parsing requirements, checking coverage, and reconciling deltas — and its own docs warn about silent failures such as a scenario written with three hashes instead of four.
22
+
23
+ Spec work is logic. A requirement is a term; *every requirement has at least one scenario* is a rule; merging a delta is matching and rewriting; *which scenarios are uncovered?* and *do two in-flight changes touch the same requirement?* are queries. DML is a Prolog dialect, so these are expressed directly instead of emulated. Structure is parsed once into terms, so validation is a decision procedure with line numbers, merging preserves untouched blocks, and coverage and conflict checks are single queries — deterministic, zero tokens, reproducible in CI.
24
+
25
+ That determinism is the point. The value of agreeing on a spec is that the agreement is *checkable*; if the check is another model call, it is just another opinion. DML lets the model do what it is good at — drafting requirements, designing, implementing — and keeps correctness in logic. The same runtime supplies what raw Prolog lacks: `task/N` and `prompt/N` for bounded model calls with typed results, `exec/2` for tools, streaming events, cancellation, and usage accounting. Backtracking across model calls is what makes verify → repair → retry loops natural, and CLP(FD)/(Q)/(R) cover hard constraints instead of asking the model to do arithmetic.
26
+
27
+ DeepClause is what makes that practical inside pi: DML programs run with pi's active model, credentials, session context and approvals, so the spec engine, the task plan (`tasks.dml`), and the execution loop are one system in the session you already work in, rather than a separate CLI. You keep human-readable Markdown specs, but the thing enforcing them is a proof, not a prompt.
28
+
19
29
  ## Requirements
20
30
 
21
31
  - pi with this extension installed (see the [README](../README.md#install)).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepclause-pi",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Pi-hosted runtime for DeepClause DML programs",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "prepublishOnly": "npm run check"
44
44
  },
45
45
  "dependencies": {
46
- "deepclause-sdk": "npm:deepclause-sdk@0.0.87",
46
+ "deepclause-sdk": "npm:deepclause-sdk@0.0.89",
47
47
  "typebox": "1.3.7"
48
48
  },
49
49
  "peerDependencies": {
@@ -85,7 +85,7 @@ attempt(TasksPath, Statuses0, Change, Id, Props, N, Max, Feedback, Statuses) :-
85
85
  format(string(Progress), "task ~w attempt ~w/~w", [Id, N, Max]),
86
86
  output(Progress),
87
87
  execute(Change, Id, Props, Feedback, Summary),
88
- verify(Props, Summary, Verdict),
88
+ verify_task(Props, Summary, Verdict),
89
89
  ( Verdict = ok
90
90
  -> sp_set_status(Statuses0, Id, done(N), Statuses),
91
91
  sp_write_statuses(TasksPath, Statuses),
@@ -123,7 +123,7 @@ build_instruction(Id, Do, Expected, Feedback, Instruction) :-
123
123
  "Task ~w. ~w~nExpected result: ~w~n~nThe previous attempt failed verification: ~w~nFix only what is needed; do not redo the whole task.",
124
124
  [Id, Do, Expected, Feedback]).
125
125
 
126
- verify(Props, Summary, Verdict) :-
126
+ verify_task(Props, Summary, Verdict) :-
127
127
  catch(get_dict(checks, Props, Checks), _, Checks = []),
128
128
  findall(Message, (member(Check, Checks), run_check(Check, Message), Message \= ok), Failures),
129
129
  ( Summary == ""
@@ -64,9 +64,12 @@ sp_sections(Lines, Sections) :-
64
64
  sp_skip_plain(Lines, Rest),
65
65
  sp_sections_from(Rest, Sections).
66
66
 
67
- sp_skip_plain([line(Line, Number)|Rest], [line(Line, Number)|Rest]) :- sp_heading(Line, Level, _), Level >= 1, Level =< 3, !.
68
- sp_skip_plain([_|Rest], Out) :- sp_skip_plain(Rest, Out).
69
67
  sp_skip_plain([], []).
68
+ sp_skip_plain([Item|Rest], Out) :-
69
+ ( Item = line(Line, _), sp_heading(Line, Level, _), Level >= 1, Level =< 3
70
+ -> Out = [Item|Rest]
71
+ ; sp_skip_plain(Rest, Out)
72
+ ).
70
73
 
71
74
  sp_sections_from([], []).
72
75
  sp_sections_from([line(Line, N)|Rest], [section(Level, Text, N, Body)|More]) :-
@@ -77,12 +80,11 @@ sp_sections_from([line(Line, N)|Rest], [section(Level, Text, N, Body)|More]) :-
77
80
  sp_sections_from(Tail, More).
78
81
 
79
82
  sp_take_body([], [], []).
80
- sp_take_body([line(Line, Number)|Rest], [], [line(Line, Number)|Rest]) :-
81
- sp_heading(Line, Level, _),
82
- Level >= 1,
83
- Level =< 3,
84
- !.
85
- sp_take_body([Item|Rest], [Item|Body], Tail) :- sp_take_body(Rest, Body, Tail).
83
+ sp_take_body([Item|Rest], Body, Tail) :-
84
+ ( Item = line(Line, _), sp_heading(Line, Level, _), Level >= 1, Level =< 3
85
+ -> Body = [], Tail = [Item|Rest]
86
+ ; Body = [Item|Body1], sp_take_body(Rest, Body1, Tail)
87
+ ).
86
88
 
87
89
  %% Split a requirement body (level-4 headings inline) into description + scenarios.
88
90
  sp_split_scenarios(Body, DescLines, ScenarioSections) :-
@@ -90,11 +92,11 @@ sp_split_scenarios(Body, DescLines, ScenarioSections) :-
90
92
  sp_scenario_sections(Rest, ScenarioSections).
91
93
 
92
94
  sp_take_plain([], [], []).
93
- sp_take_plain([line(Line, Number)|Rest], [], [line(Line, Number)|Rest]) :-
94
- sp_heading(Line, Level, _),
95
- Level >= 4,
96
- !.
97
- sp_take_plain([Item|Rest], [Item|Body], Tail) :- sp_take_plain(Rest, Body, Tail).
95
+ sp_take_plain([Item|Rest], Body, Tail) :-
96
+ ( Item = line(Line, _), sp_heading(Line, Level, _), Level >= 4
97
+ -> Body = [], Tail = [Item|Rest]
98
+ ; Body = [Item|Body1], sp_take_plain(Rest, Body1, Tail)
99
+ ).
98
100
 
99
101
  sp_scenario_sections([], []).
100
102
  sp_scenario_sections([line(Line, N)|Rest], [section(4, Text, N, Body)|More]) :-
@@ -103,11 +105,11 @@ sp_scenario_sections([line(Line, N)|Rest], [section(4, Text, N, Body)|More]) :-
103
105
  sp_scenario_sections(Tail, More).
104
106
 
105
107
  sp_take_until_level4([], [], []).
106
- sp_take_until_level4([line(Line, Number)|Rest], [], [line(Line, Number)|Rest]) :-
107
- sp_heading(Line, Level, _),
108
- Level >= 4,
109
- !.
110
- sp_take_until_level4([Item|Rest], [Item|Body], Tail) :- sp_take_until_level4(Rest, Body, Tail).
108
+ sp_take_until_level4([Item|Rest], Body, Tail) :-
109
+ ( Item = line(Line, _), sp_heading(Line, Level, _), Level >= 4
110
+ -> Body = [], Tail = [Item|Rest]
111
+ ; Body = [Item|Body1], sp_take_until_level4(Rest, Body1, Tail)
112
+ ).
111
113
 
112
114
  %% ---------------------------------------------------------------------------
113
115
  %% Term extraction
@@ -164,10 +166,11 @@ sp_parse_delta(Text, Deltas) :-
164
166
  sp_collect_ops(Rest, Deltas).
165
167
 
166
168
  sp_drop_to_op([], []).
167
- sp_drop_to_op([section(2, Header, Number, Body)|Rest], [section(2, Header, Number, Body)|Rest]) :-
168
- sp_delta_header(Header, _),
169
- !.
170
- sp_drop_to_op([_|Rest], Out) :- sp_drop_to_op(Rest, Out).
169
+ sp_drop_to_op([Item|Rest], Out) :-
170
+ ( Item = section(2, Header, _, _), sp_delta_header(Header, _)
171
+ -> Out = [Item|Rest]
172
+ ; sp_drop_to_op(Rest, Out)
173
+ ).
171
174
 
172
175
  sp_collect_ops([], []).
173
176
  sp_collect_ops([section(2, Header, _, _)|Rest], Deltas) :-
@@ -184,10 +187,11 @@ sp_collect_ops([section(2, Header, _, _)|Rest], Deltas) :-
184
187
  ).
185
188
 
186
189
  sp_take_until_op([], [], []).
187
- sp_take_until_op([section(2, Header, Number, Body)|Rest], [], [section(2, Header, Number, Body)|Rest]) :-
188
- sp_delta_header(Header, _),
189
- !.
190
- sp_take_until_op([Item|Rest], [Item|Group], Tail) :- sp_take_until_op(Rest, Group, Tail).
190
+ sp_take_until_op([Item|Rest], Group, Tail) :-
191
+ ( Item = section(2, Header, _, _), sp_delta_header(Header, _)
192
+ -> Group = [], Tail = [Item|Rest]
193
+ ; Group = [Item|Group1], sp_take_until_op(Rest, Group1, Tail)
194
+ ).
191
195
 
192
196
  %% ---------------------------------------------------------------------------
193
197
  %% Validation
package/src/config.ts CHANGED
@@ -2,6 +2,21 @@ import { readFile, writeFile } from "node:fs/promises";
2
2
 
3
3
  export type ContextMode = "turn" | "branch" | "isolated";
4
4
 
5
+ export interface JevJudgeConfig {
6
+ /** Whether the Jev (TypeSafe System One) backend may be used. */
7
+ enabled: boolean;
8
+ /** Model alias or pinned version passed to TypeSafe. */
9
+ model: string;
10
+ /** Environment variable holding the TypeSafe API key. */
11
+ apiKeyEnv: string;
12
+ }
13
+
14
+ export interface JudgmentConfig {
15
+ /** Default backend name: "llm", "jev", or a name registered by an extension. */
16
+ default: string;
17
+ jev: JevJudgeConfig;
18
+ }
19
+
5
20
  export interface DeepClauseConfig {
6
21
  version: 1;
7
22
  contextMode: ContextMode;
@@ -10,6 +25,7 @@ export interface DeepClauseConfig {
10
25
  maxTokens: number;
11
26
  verbose: boolean;
12
27
  modelToolEnabled: boolean;
28
+ judgment: JudgmentConfig;
13
29
  }
14
30
 
15
31
  export const DEFAULT_CONFIG: DeepClauseConfig = {
@@ -20,6 +36,10 @@ export const DEFAULT_CONFIG: DeepClauseConfig = {
20
36
  maxTokens: 16_384,
21
37
  verbose: false,
22
38
  modelToolEnabled: false,
39
+ judgment: {
40
+ default: "llm",
41
+ jev: { enabled: false, model: "jev-latest", apiKeyEnv: "TYPESAFE_API_KEY" },
42
+ },
23
43
  };
24
44
 
25
45
  const isContextMode = (value: unknown): value is ContextMode =>
@@ -55,6 +75,42 @@ export async function loadConfig(path: string): Promise<DeepClauseConfig> {
55
75
  maxTokens: positiveInteger("maxTokens", DEFAULT_CONFIG.maxTokens),
56
76
  verbose: config.verbose === true,
57
77
  modelToolEnabled: config.modelToolEnabled === true,
78
+ judgment: parseJudgmentConfig(config.judgment),
79
+ };
80
+ }
81
+
82
+ function parseJudgmentConfig(value: unknown): JudgmentConfig {
83
+ if (value === undefined) return DEFAULT_CONFIG.judgment;
84
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
85
+ throw new Error("judgment must be a JSON object");
86
+ }
87
+ const judgment = value as Record<string, unknown>;
88
+ const defaultBackend = judgment.default ?? DEFAULT_CONFIG.judgment.default;
89
+ if (typeof defaultBackend !== "string" || !defaultBackend.trim()) {
90
+ throw new Error("judgment.default must be a non-empty backend name");
91
+ }
92
+
93
+ const jevValue = judgment.jev ?? {};
94
+ if (!jevValue || typeof jevValue !== "object" || Array.isArray(jevValue)) {
95
+ throw new Error("judgment.jev must be a JSON object");
96
+ }
97
+ const jev = jevValue as Record<string, unknown>;
98
+ const model = jev.model ?? DEFAULT_CONFIG.judgment.jev.model;
99
+ const apiKeyEnv = jev.apiKeyEnv ?? DEFAULT_CONFIG.judgment.jev.apiKeyEnv;
100
+ if (typeof model !== "string" || !model.trim()) {
101
+ throw new Error("judgment.jev.model must be a non-empty string");
102
+ }
103
+ if (typeof apiKeyEnv !== "string" || !apiKeyEnv.trim()) {
104
+ throw new Error("judgment.jev.apiKeyEnv must be a non-empty environment variable name");
105
+ }
106
+
107
+ return {
108
+ default: defaultBackend.trim(),
109
+ jev: {
110
+ enabled: jev.enabled === true,
111
+ model: model.trim(),
112
+ apiKeyEnv: apiKeyEnv.trim(),
113
+ },
58
114
  };
59
115
  }
60
116
 
@@ -72,3 +128,38 @@ export async function setModelToolEnabled(configPath: string, enabled: boolean):
72
128
  await writeFile(configPath, `${JSON.stringify({ ...existing, modelToolEnabled: enabled }, null, 2)}\n`, "utf8");
73
129
  return loadConfig(configPath);
74
130
  }
131
+
132
+ export interface JudgeConfigPatch {
133
+ /** Default backend name ("llm" or "jev"). */
134
+ default?: string;
135
+ /** Partial update of the Jev backend settings. */
136
+ jev?: Partial<JevJudgeConfig>;
137
+ }
138
+
139
+ /** Merge a judgment patch into the workspace config, preserving every other field. */
140
+ export async function setJudgeConfig(
141
+ configPath: string,
142
+ patch: JudgeConfigPatch,
143
+ ): Promise<DeepClauseConfig> {
144
+ let existing: Record<string, unknown> = {};
145
+ try {
146
+ const parsed: unknown = JSON.parse(await readFile(configPath, "utf8"));
147
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) existing = parsed as Record<string, unknown>;
148
+ } catch (error) {
149
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
150
+ throw new Error(`Invalid DeepClause config: ${error instanceof Error ? error.message : String(error)}`);
151
+ }
152
+ }
153
+
154
+ const record = (value: unknown): Record<string, unknown> =>
155
+ value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
156
+
157
+ const nextJudgment = {
158
+ ...record(existing.judgment),
159
+ ...(patch.default !== undefined ? { default: patch.default } : {}),
160
+ jev: { ...record(record(existing.judgment).jev), ...(patch.jev ?? {}) },
161
+ };
162
+
163
+ await writeFile(configPath, `${JSON.stringify({ ...existing, judgment: nextJudgment }, null, 2)}\n`, "utf8");
164
+ return loadConfig(configPath);
165
+ }
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@ import { StringEnum } from "@earendil-works/pi-ai";
6
6
  import type { DMLEvent } from "deepclause-sdk";
7
7
  import { Type } from "typebox";
8
8
  import { buildInitialMessages } from "./context.js";
9
- import { loadConfig, setModelToolEnabled, type ContextMode } from "./config.js";
9
+ import { loadConfig, setJudgeConfig, setModelToolEnabled, type ContextMode, type DeepClauseConfig } from "./config.js";
10
10
  import { renderDml, renderSequence } from "./diagram/extract.js";
11
11
  import { polishDiagram, resolveGrade, type DiagramGrade } from "./diagram/grade.js";
12
12
  import { findChrome, validateMermaid, type MermaidView } from "./diagram/validate.js";
@@ -57,6 +57,7 @@ export interface ParsedRun {
57
57
  contextMode?: ContextMode;
58
58
  verbose: boolean;
59
59
  debug: boolean;
60
+ judge?: string;
60
61
  }
61
62
 
62
63
  interface ParsedPlan {
@@ -125,11 +126,13 @@ export function parseRun(input: string): ParsedRun {
125
126
  let contextMode: ContextMode | undefined;
126
127
  let verbose = false;
127
128
  let debug = false;
129
+ let judge: string | undefined;
128
130
  const args: string[] = [];
129
131
 
130
132
  for (let index = 0; index < tokens.length; index++) {
131
133
  const token = tokens[index]!;
132
134
  const contextValue = token.startsWith("--context=") ? token.slice("--context=".length) : undefined;
135
+ const judgeValue = token.startsWith("--judge=") ? token.slice("--judge=".length) : undefined;
133
136
  if (token === "--verbose" || token === "-v") {
134
137
  verbose = true;
135
138
  } else if (token === "--debug" || token === "-d") {
@@ -146,11 +149,18 @@ export function parseRun(input: string): ParsedRun {
146
149
  throw new Error("--context must be turn, branch, or isolated");
147
150
  }
148
151
  contextMode = value;
152
+ } else if (judgeValue !== undefined) {
153
+ if (!judgeValue.trim()) throw new Error("--judge requires a non-empty backend name");
154
+ judge = judgeValue.trim();
155
+ } else if (token === "--judge") {
156
+ const value = tokens[++index];
157
+ if (!value || !value.trim()) throw new Error("--judge requires a non-empty backend name");
158
+ judge = value.trim();
149
159
  } else {
150
160
  args.push(token);
151
161
  }
152
162
  }
153
- return { target, args, contextMode, verbose, debug };
163
+ return { target, args, contextMode, verbose, debug, judge };
154
164
  }
155
165
 
156
166
  export function parsePlan(input: string): ParsedPlan {
@@ -288,6 +298,18 @@ function modelLabel(ctx: ExtensionCommandContext): string {
288
298
  return ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "none selected";
289
299
  }
290
300
 
301
+ function judgeBackendsEnabled(config: DeepClauseConfig): string {
302
+ return config.judgment.jev.enabled ? ", jev" : "";
303
+ }
304
+
305
+ function jevStatus(config: DeepClauseConfig): string {
306
+ const jev = config.judgment.jev;
307
+ if (!jev.enabled) return "disabled";
308
+ return process.env[jev.apiKeyEnv]
309
+ ? `enabled (${jev.model})`
310
+ : `enabled but ${jev.apiKeyEnv} is not set`;
311
+ }
312
+
291
313
  async function bundledViewerTemplate(): Promise<string> {
292
314
  return readFile(fileURLToPath(new URL("./assets/viewer.template.html", import.meta.url)), "utf8");
293
315
  }
@@ -789,7 +811,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
789
811
  const viewer = displayPath(ctx.cwd, build.viewerPath);
790
812
  return {
791
813
  content: [{ type: "text", text: `Created spec graph (${view}). Viewer: ${viewer}${opened ? " (opened in your browser)" : ""}` }],
792
- details: { success: true, view, viewer, opened },
814
+ details: { success: true, view, viewer, viewerPath: build.viewerPath, opened },
793
815
  };
794
816
  } catch (error) {
795
817
  const message = error instanceof Error ? error.message : String(error);
@@ -926,6 +948,8 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
926
948
  `Skills: ${path.relative(ctx.cwd, paths.skills)}`,
927
949
  `Plans: ${path.relative(ctx.cwd, paths.plans)}`,
928
950
  `Context: ${config.contextMode} (verbose default: ${config.verbose})`,
951
+ `Judgment: ${config.judgment.default} (backends: llm${judgeBackendsEnabled(config)})`,
952
+ `Jev: ${jevStatus(config)}`,
929
953
  `Model tool (${DC_RUN_TOOL}): ${config.modelToolEnabled && pi.getActiveTools().includes(DC_RUN_TOOL) ? "enabled" : "disabled"}`,
930
954
  `Model tool (${DC_DIAGRAM_TOOL}): ${pi.getActiveTools().includes(DC_DIAGRAM_TOOL) ? "enabled" : "disabled"}`,
931
955
  "Ask pi for a presentation-grade or specification-grade diagram of any .dml file;",
@@ -936,10 +960,13 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
936
960
  " /dc-check <change|spec> validate specs and deltas deterministically",
937
961
  " /dc-archive <change> merge a change delta into specs/ and archive it",
938
962
  " /dc-apply <change> [--abort] execute tasks.dml; --abort discards an interrupted apply",
939
- " /dc-run <skill|path> [args] [--context=turn|branch|isolated]",
963
+ " /dc-run <skill|path> [args] [--context=turn|branch|isolated] [--judge=llm|jev]",
940
964
  " /dc-run <skill|path> --verbose show lifecycle events",
941
965
  " /dc-run <skill|path> --debug show full event payloads and SDK diagnostics",
942
966
  " /dc-tool enable|disable|status control the model-callable dc_run tool",
967
+ " /dc-judge [enable|disable|status] select the judgment backend (llm|jev)",
968
+ " /dc-judge default llm|jev set the default judgment backend",
969
+ " /dc-judge model <name> | key-env <ENV_VAR>",
943
970
  " /dc-cancel",
944
971
  ].join("\n");
945
972
  ctx.ui.notify(message, "info");
@@ -1032,6 +1059,78 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
1032
1059
  },
1033
1060
  });
1034
1061
 
1062
+ pi.registerCommand("dc-judge", {
1063
+ description: "Enable, disable, or select the semantic judgment backend (llm|jev)",
1064
+ handler: async (rawArgs, ctx) => {
1065
+ const tokens = rawArgs.trim().split(/\s+/).filter(Boolean);
1066
+ const action = (tokens[0] ?? "status").toLowerCase();
1067
+ const paths = await initializeWorkspace(ctx.cwd);
1068
+
1069
+ const describe = (config: DeepClauseConfig): string => {
1070
+ const jev = config.judgment.jev;
1071
+ const keyPresent = Boolean(process.env[jev.apiKeyEnv]);
1072
+ return [
1073
+ `judgment backend: ${config.judgment.default}`,
1074
+ `registered backends: llm${jev.enabled ? ", jev" : ""}`,
1075
+ `jev: ${jev.enabled ? "enabled" : "disabled"} | model=${jev.model} | ${jev.apiKeyEnv}=${keyPresent ? "set" : "not set"}`,
1076
+ ].join("\n");
1077
+ };
1078
+
1079
+ try {
1080
+ if (action === "status") {
1081
+ ctx.ui.notify(describe(await loadConfig(paths.config)), "info");
1082
+ return;
1083
+ }
1084
+ if (action === "enable" || action === "on" || action === "disable" || action === "off") {
1085
+ const enabled = action === "enable" || action === "on";
1086
+ const config = await setJudgeConfig(paths.config, { jev: { enabled } });
1087
+ const jev = config.judgment.jev;
1088
+ const note = enabled && !process.env[jev.apiKeyEnv]
1089
+ ? `\n${jev.apiKeyEnv} is not set; export it before using --judge=jev.`
1090
+ : "";
1091
+ ctx.ui.notify(`Jev backend ${enabled ? "enabled" : "disabled"} for this workspace.${note}`, enabled ? "warning" : "info");
1092
+ return;
1093
+ }
1094
+ if (action === "default") {
1095
+ const name = tokens[1];
1096
+ if (name !== "llm" && name !== "jev") {
1097
+ ctx.ui.notify("Usage: /dc-judge default llm|jev", "warning");
1098
+ return;
1099
+ }
1100
+ await setJudgeConfig(paths.config, { default: name });
1101
+ ctx.ui.notify(`Default judgment backend set to '${name}'`, "info");
1102
+ return;
1103
+ }
1104
+ if (action === "model") {
1105
+ const model = tokens[1];
1106
+ if (!model) {
1107
+ ctx.ui.notify("Usage: /dc-judge model <name>", "warning");
1108
+ return;
1109
+ }
1110
+ await setJudgeConfig(paths.config, { jev: { model } });
1111
+ ctx.ui.notify(`Jev model set to '${model}'`, "info");
1112
+ return;
1113
+ }
1114
+ if (action === "key-env") {
1115
+ const envName = tokens[1];
1116
+ if (!envName) {
1117
+ ctx.ui.notify("Usage: /dc-judge key-env <ENV_VAR>", "warning");
1118
+ return;
1119
+ }
1120
+ await setJudgeConfig(paths.config, { jev: { apiKeyEnv: envName } });
1121
+ ctx.ui.notify(`Jev API key environment variable set to '${envName}'`, "info");
1122
+ return;
1123
+ }
1124
+ ctx.ui.notify(
1125
+ "Usage: /dc-judge [enable|disable|status|default llm|jev|model <name>|key-env <ENV_VAR>]",
1126
+ "warning",
1127
+ );
1128
+ } catch (error) {
1129
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1130
+ }
1131
+ },
1132
+ });
1133
+
1035
1134
  pi.registerCommand("dc-list", {
1036
1135
  description: "List DeepClause DML skills and generated plans",
1037
1136
  handler: async (_args, ctx) => {
@@ -1302,7 +1401,7 @@ export default function deepClauseExtension(pi: ExtensionAPI) {
1302
1401
  filePath,
1303
1402
  parsed.args,
1304
1403
  initialMessages,
1305
- { ...config, verbose: config.verbose || parsed.debug },
1404
+ { ...config, verbose: config.verbose || parsed.debug, judgeBackend: parsed.judge },
1306
1405
  pi,
1307
1406
  ctx,
1308
1407
  controller,
package/src/runtime.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { realpath, readFile, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { createDeepClause } from "deepclause-sdk";
3
+ import { createDeepClause, createJevJudgeBackend, createLLMJudgeBackend } from "deepclause-sdk";
4
4
  import type {
5
5
  DMLEvent,
6
+ JudgeBackend,
6
7
  LLMBackend,
7
8
  LLMBackendMessage,
8
9
  LLMUsage,
@@ -327,7 +328,7 @@ export async function executeDml(
327
328
  filePath: string,
328
329
  args: string[],
329
330
  initialMessages: MemoryMessage[],
330
- config: DeepClauseConfig,
331
+ config: DeepClauseConfig & { judgeBackend?: string },
331
332
  pi: ExtensionAPI,
332
333
  ctx: ExtensionContext,
333
334
  controller: AbortController,
@@ -339,12 +340,38 @@ export async function executeDml(
339
340
  const model = ctx.model;
340
341
  if (!model) throw new Error("Select a pi model before running DeepClause");
341
342
  const backend = createPiBackend(ctx, config.maxTokens, callbacks.onDiagnostic ?? (() => {}));
343
+
344
+ // Judgment backends. `llm` reuses pi's active model and credentials; `jev`
345
+ // is opt-in and only registered when enabled and its key is present.
346
+ const judgeBackends: Record<string, JudgeBackend> = {
347
+ llm: createLLMJudgeBackend({ llmBackend: backend, model: model.id }),
348
+ };
349
+ const jev = config.judgment.jev;
350
+ if (jev.enabled) {
351
+ const apiKey = process.env[jev.apiKeyEnv];
352
+ if (apiKey) {
353
+ judgeBackends.jev = createJevJudgeBackend({ apiKey, model: jev.model });
354
+ } else {
355
+ callbacks.onDiagnostic?.(
356
+ `judgment backend 'jev' is enabled but ${jev.apiKeyEnv} is not set; falling back to 'llm'`,
357
+ );
358
+ }
359
+ }
360
+ const requestedJudge = config.judgeBackend ?? config.judgment.default;
361
+ if (!judgeBackends[requestedJudge]) {
362
+ throw new Error(
363
+ `Judgment backend '${requestedJudge}' is not available. Enable it in .pi/deepclause/config.json or use --judge=llm.`,
364
+ );
365
+ }
366
+
342
367
  const sdk = await createDeepClause({
343
368
  model: model.id,
344
369
  maxTokens: config.maxTokens,
345
370
  streaming: true,
346
371
  debug: config.verbose,
347
372
  llmBackend: backend,
373
+ judgeBackends,
374
+ defaultJudge: requestedJudge,
348
375
  });
349
376
  registerPiRuntimeTools(
350
377
  sdk,
@@ -458,6 +485,7 @@ export async function executeDml(
458
485
  gasLimit: config.gasLimit,
459
486
  signal: controller.signal,
460
487
  initialMessages,
488
+ judgeBackend: config.judgeBackend,
461
489
  onUserInput: (prompt) => callbacks.onInput(prompt, controller.signal),
462
490
  })) {
463
491
  callbacks.onEvent(event);