deepclause-pi 0.3.0 → 0.5.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 CHANGED
@@ -155,15 +155,15 @@ Give me a specification-grade diagram of src/report.dml
155
155
 
156
156
  Pi calls the always-active `dc_diagram` model tool, which:
157
157
 
158
- 1. extracts a deterministic Mermaid seed from the DML in-process,
159
- 2. has the active pi model rewrite it in the requested grade,
158
+ 1. extracts a deterministic Mermaid seed from the DML in-process (the specification seed adds a `LOGIC` section for decision predicates and a `RULES` section for fact tables),
159
+ 2. has the active pi model rewrite it in the requested grade, preserving the seed's conditions, thresholds and outcomes,
160
160
  3. validates each attempt (structural checks, plus the real Mermaid parser when Chrome is available),
161
161
  4. writes the sidecar under `.pi/deepclause/diagrams/`, rebuilds the self-contained offline `viewer.html`, and opens it.
162
162
 
163
163
  Grades:
164
164
 
165
- - **presentation** — about 8-12 nodes, plain language, headline numbers; for slides and overviews.
166
- - **specification** — function names, task/tool roles, post-conditions; for engineers.
165
+ - **presentation** — about 8-12 nodes, plain language, headline numbers and the headline decision; for slides and overviews.
166
+ - **specification** — function names, task/tool roles, post-conditions, plus the core decision logic: each decision predicate with its conditions, thresholds and outcomes, and the rule fact tables; for engineers.
167
167
 
168
168
  Selecting `both` produces both sidecars. The DML source may be anywhere (workspace-relative or absolute); only the generated viewer is confined to `.pi/deepclause/`. `dc_diagram` never executes the DML and needs no shell approval. Regenerating a grade replaces only that grade's sidecar (`<name>.presentation.mmd` / `<name>.specification.mmd`). The Mermaid bundle is vendored so the viewer works offline.
169
169
 
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
+ }
@@ -1,5 +1,7 @@
1
1
  export interface RenderOptions {
2
2
  hideOutput?: boolean;
3
+ /** Include core decision-logic blocks and rule facts. Defaults to true. */
4
+ includeLogic?: boolean;
3
5
  }
4
6
  export declare function renderDml(file: string, src: string, opts?: RenderOptions): string;
5
7
  export declare function renderSequence(file: string, src: string): string;
@@ -325,7 +325,7 @@ function extractClauses(src) {
325
325
  depth++;
326
326
  else if (")]}".includes(c))
327
327
  depth--;
328
- else if (c === "." && depth === 0 && /\s|$/.test(src.charAt(b + 1)))
328
+ else if (c === "." && depth === 0 && (b + 1 >= src.length || /\s/.test(src.charAt(b + 1))))
329
329
  break;
330
330
  }
331
331
  if (!clauses.has(name))
@@ -376,6 +376,158 @@ function agenticPredicates(clauses) {
376
376
  visit(n, new Set());
377
377
  return agentic;
378
378
  }
379
+ // ---------- core logic extraction --------------------------------------------
380
+ const JUDGE_PREDICATES = new Set([
381
+ "judge", "choose", "rate", "verify", "probability", "holds", "with_judgment", "require_judgment",
382
+ ]);
383
+ const MECHANICAL_HELPERS = new Set([
384
+ "dget", "get_dict", "num", "to_num", "present", "lower", "lower_string", "upper",
385
+ "state_text", "norm_sign", "as_list", "truthy", "falsy", "format", "trunc", "basename",
386
+ ]);
387
+ const DECISION_RE = /(?:>=|=<|<|>|=:=|\bis\b|\bbetween\b|\bmod\b|\\\+)/;
388
+ const AGGREGATE_RE = /\b(findall|forall|setof|bagof|aggregate_all|maplist)\b/;
389
+ /** All `name(` tokens in a clause body, with strings and chars removed. */
390
+ function headNamesIn(text) {
391
+ const bare = text
392
+ .replace(/"(?:[^"\\]|\\.)*"/g, '""')
393
+ .replace(/'(?:[^'\\]|\\.)*'/g, "''");
394
+ const names = [];
395
+ for (const m of bare.matchAll(/([a-z][a-zA-Z0-9_]*)\s*\(/g)) {
396
+ if (m[1])
397
+ names.push(m[1]);
398
+ }
399
+ return names;
400
+ }
401
+ function predicateArity(clauses) {
402
+ return splitTop(clauses[0]?.args ?? "", ",").filter(Boolean).length;
403
+ }
404
+ function isFactClauses(clauses) {
405
+ return clauses.every((clause) => !clause.body.trim());
406
+ }
407
+ /**
408
+ * A predicate carries core logic when it branches, applies thresholds or
409
+ * generators, or consults a semantic judgment. Mechanical plumbing helpers are
410
+ * excluded so the logic section stays about decisions, not string handling.
411
+ */
412
+ function isDecisionPredicate(name, clauses) {
413
+ if (MECHANICAL_HELPERS.has(name))
414
+ return false;
415
+ if (clauses.length > 1)
416
+ return true;
417
+ const body = clauses[0]?.body ?? "";
418
+ if (!body.trim())
419
+ return false;
420
+ if (DECISION_RE.test(body))
421
+ return true;
422
+ if (AGGREGATE_RE.test(body))
423
+ return true;
424
+ for (const h of headNamesIn(body))
425
+ if (JUDGE_PREDICATES.has(h))
426
+ return true;
427
+ return false;
428
+ }
429
+ const escLabel = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "'");
430
+ /** Join the summaries of a conjunction/disjunction into one readable line. */
431
+ function summarizeBody(text, max = 96) {
432
+ const parts = splitGoals(text)
433
+ .map(({ goal }) => summarizeGoal(goal, max))
434
+ .filter(Boolean);
435
+ return [...new Set(parts)].slice(-2).join("; ");
436
+ }
437
+ /** One short, readable line for a single body goal. */
438
+ function summarizeGoal(goal, max = 96) {
439
+ const g = stripOuterParens(goal).trim();
440
+ if (!g)
441
+ return "";
442
+ const br = parseArrow(g);
443
+ if (br) {
444
+ const then = summarizeBody(br.then, Math.max(24, max - 12));
445
+ const otherwise = br.else.trim() ? summarizeBody(br.else, Math.max(24, max - 12)) : "";
446
+ return `if ${trunc(br.cond.replace(/\s+/g, " "), max)} -> ${then || "…"}${otherwise ? ` ; else: ${otherwise}` : ""}`;
447
+ }
448
+ return summarizeSingle(g, max);
449
+ }
450
+ function summarizeSingle(g, max) {
451
+ const h = headName(g);
452
+ if (!h)
453
+ return trunc(g.replace(/\s+/g, " "), max);
454
+ if (["format", "write", "print", "output", "answer"].includes(h)) {
455
+ const s = firstString(g);
456
+ if (!s)
457
+ return `${h}(...)`;
458
+ const decision = /(?:decision|answer|verdict|conclusion):.*/i.exec(s);
459
+ return trunc(decision ? decision[0] : s, Math.max(max, 120));
460
+ }
461
+ if (JUDGE_PREDICATES.has(h)) {
462
+ const label = h === "require_judgment" ? "require calibrated" : h;
463
+ const s = firstString(g);
464
+ return s ? `${label}: ${trunc(s, Math.max(16, max - label.length - 2))}` : `${label}(...)`;
465
+ }
466
+ if (["findall", "forall", "setof", "bagof", "aggregate_all"].includes(h))
467
+ return `${h}(...)`;
468
+ const args = () => splitTop(g.slice(g.indexOf("(") + 1, g.lastIndexOf(")")), ",").map((p) => p.trim());
469
+ if (h === "get_dict")
470
+ return args()[0] || "get_dict";
471
+ if (h === "dget" || h === "num" || h === "to_num")
472
+ return args()[1] || h;
473
+ const inside = g.slice(g.indexOf("(") + 1, g.lastIndexOf(")")).replace(/\s+/g, " ").trim();
474
+ return inside ? `${h}(${trunc(inside, Math.max(12, max - h.length - 3))})` : h;
475
+ }
476
+ /** Render one clause as an escaped `<br/>`-joined list of its meaningful goals. */
477
+ function describeClause(clause) {
478
+ if (!clause.body.trim())
479
+ return escLabel(trunc(clause.args.replace(/\s+/g, " "), 80));
480
+ const seen = new Set();
481
+ const lines = [];
482
+ for (const { goal } of splitGoals(clause.body)) {
483
+ const line = summarizeGoal(goal);
484
+ if (!line)
485
+ continue;
486
+ const escaped = escLabel(line);
487
+ if (seen.has(escaped))
488
+ continue;
489
+ seen.add(escaped);
490
+ lines.push(escaped);
491
+ }
492
+ return lines.join("<br/>");
493
+ }
494
+ function factSummary(name, clauses) {
495
+ const sample = clauses
496
+ .slice(0, 6)
497
+ .map((clause) => escLabel(trunc(clause.args.replace(/\s+/g, " "), 32)))
498
+ .join(" · ");
499
+ return `${name}/${predicateArity(clauses)} — ${clauses.length} facts: ${sample}${clauses.length > 6 ? " …" : ""}`;
500
+ }
501
+ /** Transitive closure of decision predicates and fact tables reachable from the flow. */
502
+ function collectLogic(clauses, roots) {
503
+ const logic = new Map();
504
+ const facts = new Map();
505
+ const seen = new Set();
506
+ const visit = (name) => {
507
+ if (seen.has(name))
508
+ return;
509
+ seen.add(name);
510
+ const bodies = clauses.get(name);
511
+ if (!bodies)
512
+ return;
513
+ if (isFactClauses(bodies)) {
514
+ if (bodies.length >= 2)
515
+ facts.set(name, bodies);
516
+ return;
517
+ }
518
+ if (isDecisionPredicate(name, bodies))
519
+ logic.set(name, bodies);
520
+ for (const clause of bodies) {
521
+ for (const inner of headNamesIn(clause.body)) {
522
+ if (inner !== name && clauses.has(inner) && !BUILTIN.has(inner))
523
+ visit(inner);
524
+ }
525
+ }
526
+ };
527
+ for (const root of roots)
528
+ visit(root);
529
+ return { logic, facts };
530
+ }
379
531
  // ---------- rendering --------------------------------------------------------
380
532
  function parseArrow(goal) {
381
533
  const t = stripOuterParens(goal);
@@ -461,6 +613,9 @@ export function renderDml(file, src, opts = {}) {
461
613
  stack: [],
462
614
  sawAnswer: false,
463
615
  phaseOpen: false,
616
+ includeLogic: opts.includeLogic !== false,
617
+ logicNodes: new Map(),
618
+ factRefs: new Set(),
464
619
  };
465
620
  const nid = () => `n${++state.n}`;
466
621
  const node = (shape, label, klass) => {
@@ -469,6 +624,24 @@ export function renderDml(file, src, opts = {}) {
469
624
  return id;
470
625
  };
471
626
  const edge = (a, b, label) => state.lines.push(` ${a} ${label ? `-->|${label}|` : "-->"} ${b}`);
627
+ /** Record user predicates referenced by a condition so their logic still gets a block. */
628
+ const registerLogicRefs = (text, nodeId) => {
629
+ for (const name of headNamesIn(text)) {
630
+ const bodies = clauses.get(name);
631
+ if (!bodies || BUILTIN.has(name) || name === "agent_main")
632
+ continue;
633
+ if (isFactClauses(bodies)) {
634
+ if (bodies.length >= 2)
635
+ state.factRefs.add(name);
636
+ continue;
637
+ }
638
+ if (agentic.has(name))
639
+ continue;
640
+ const referenced = state.logicNodes.get(name) ?? [];
641
+ referenced.push(nodeId);
642
+ state.logicNodes.set(name, referenced);
643
+ }
644
+ };
472
645
  function renderGoals(goals, ctx = "", topLevel = false) {
473
646
  let entry = null;
474
647
  let exit = null;
@@ -506,6 +679,8 @@ export function renderDml(file, src, opts = {}) {
506
679
  const br = parseArrow(goal);
507
680
  if (br) {
508
681
  const d = node(["{", "}"], `if ${trunc(br.cond, 46)}`, "det");
682
+ if (state.includeLogic)
683
+ registerLogicRefs(br.cond, d);
509
684
  const t = renderGoals(splitGoals(br.then), ctx);
510
685
  const e = br.else.trim() ? renderGoals(splitGoals(br.else), ctx) : null;
511
686
  const merge = nid();
@@ -546,15 +721,25 @@ export function renderDml(file, src, opts = {}) {
546
721
  if (h && clauses.has(h)) {
547
722
  const bodies = clauses.get(h);
548
723
  const isFact = bodies.every((c) => !c.body.trim());
549
- if (isFact)
724
+ if (isFact) {
725
+ if (bodies.length >= 2)
726
+ state.factRefs.add(h);
550
727
  return null;
728
+ }
551
729
  if (agentic.has(h) && !state.stack.includes(h)) {
552
730
  state.stack.push(h);
553
731
  const seg = renderGoals(splitGoals(bodies[0]?.body ?? ""), h);
554
732
  state.stack.pop();
555
733
  return seg.entry ? seg : null;
556
734
  }
557
- return { entry: node(["[[", "]]"], `${h}()`, "det"), exit: null };
735
+ const label = state.includeLogic ? `${h}/${predicateArity(bodies)}` : `${h}()`;
736
+ const id = node(["[[", "]]"], label, "det");
737
+ if (state.includeLogic) {
738
+ const referenced = state.logicNodes.get(h) ?? [];
739
+ referenced.push(id);
740
+ state.logicNodes.set(h, referenced);
741
+ }
742
+ return { entry: id, exit: null };
558
743
  }
559
744
  return null;
560
745
  }
@@ -579,6 +764,41 @@ export function renderDml(file, src, opts = {}) {
579
764
  tools.forEach((t, i) => state.lines.push(` tool${i}[["${t.name}(${trunc(t.args, 26)})"]]:::tool`));
580
765
  state.lines.push(" end");
581
766
  }
767
+ if (state.includeLogic) {
768
+ const { logic, facts } = collectLogic(clauses, [...state.logicNodes.keys(), ...state.factRefs]);
769
+ if (logic.size || facts.size) {
770
+ const edges = [];
771
+ if (logic.size) {
772
+ state.lines.push(' subgraph LOGIC["core decision logic"]');
773
+ state.lines.push(" direction TB");
774
+ let index = 0;
775
+ for (const [name, bodies] of logic) {
776
+ const sg = `lg${++index}`;
777
+ state.lines.push(` subgraph ${sg}["${name}/${predicateArity(bodies)}"]`);
778
+ state.lines.push(" direction TB");
779
+ bodies.forEach((clause, ci) => {
780
+ const description = describeClause(clause) || "(clause)";
781
+ state.lines.push(` ${sg}c${ci}["${ci + 1}) ${description}"]:::det`);
782
+ });
783
+ state.lines.push(" end");
784
+ for (const nodeId of state.logicNodes.get(name) ?? []) {
785
+ edges.push(` ${nodeId} -.->|"core logic"| ${sg}`);
786
+ }
787
+ }
788
+ state.lines.push(" end");
789
+ }
790
+ if (facts.size) {
791
+ state.lines.push(' subgraph RULES["rule facts"]');
792
+ state.lines.push(" direction LR");
793
+ let index = 0;
794
+ for (const [name, bodies] of facts) {
795
+ state.lines.push(` rf${++index}["${factSummary(name, bodies)}"]:::det`);
796
+ }
797
+ state.lines.push(" end");
798
+ }
799
+ state.lines.push(...edges);
800
+ }
801
+ }
582
802
  state.lines.push(" classDef start fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;", " classDef terminal fill:#e3f2fd,stroke:#1565c0;", " classDef llm fill:#fff8e1,stroke:#f9a825,stroke-width:2px;", " classDef det fill:#eceff1,stroke:#546e7a;", " classDef host fill:#f3e5f5,stroke:#6a1b9a;", " classDef out fill:#fafafa,stroke:#bdbdbd;", " classDef tool fill:#ede7f6,stroke:#4527a0;");
583
803
  return state.lines.join("\n");
584
804
  }
@@ -1,7 +1,7 @@
1
1
  import type { CheckResult, MermaidView } from "./validate.js";
2
2
  export type DiagramGrade = "presentation" | "specification";
3
3
  export type RequestedGrade = DiagramGrade | "both";
4
- export declare const GRADE_SYSTEM_PROMPT = "You are a meticulous Mermaid v11 diagram editor. You output only valid Mermaid source, never wrapped in code fences, and you keep all facts accurate.";
4
+ export declare const GRADE_SYSTEM_PROMPT = "You are a meticulous Mermaid v11 diagram editor. You output only valid Mermaid source, never wrapped in code fences, and you keep all facts and decision logic accurate. Never invent or drop a rule, threshold, or outcome.";
5
5
  export declare const GRADE_PROMPTS: Record<DiagramGrade, string>;
6
6
  /**
7
7
  * Map free-form user/tool wording onto a supported grade. Returns undefined when
@@ -1,7 +1,7 @@
1
- export const GRADE_SYSTEM_PROMPT = "You are a meticulous Mermaid v11 diagram editor. You output only valid Mermaid source, never wrapped in code fences, and you keep all facts accurate.";
1
+ export const GRADE_SYSTEM_PROMPT = "You are a meticulous Mermaid v11 diagram editor. You output only valid Mermaid source, never wrapped in code fences, and you keep all facts and decision logic accurate. Never invent or drop a rule, threshold, or outcome.";
2
2
  export const GRADE_PROMPTS = {
3
- presentation: "Produce a PRESENTATION-GRADE version: about 8-12 nodes, plain non-technical language, NO function names or framework jargon, highlight the headline numbers and the main steps, and add 1-2 short callouts. Keep it accurate but simple enough for a general audience.",
4
- specification: "Produce a SPECIFICATION-GRADE (detailed engineering) version: keep the technical detail (function names, task/tool roles, post-conditions, seed data), but improve labels, grouping and readability so an engineer can follow it precisely.",
3
+ presentation: "Produce a PRESENTATION-GRADE version: about 8-12 nodes, plain non-technical language, NO function names or framework jargon, highlight the headline numbers, the main steps and the headline decision, and add 1-2 short callouts. Keep it accurate but simple enough for a general audience; you may compress the detailed logic but must not change the final decision or omit a rule that flips it.",
4
+ specification: "Produce a SPECIFICATION-GRADE (detailed engineering) version: keep the technical detail (function names, task/tool roles, post-conditions, seed data) AND the domain decision logic. Preserve the decision predicates from the seed's `LOGIC` section — each with its exact conditions, thresholds and outcomes — and every `RULES` fact table. You may group or collapse pure arithmetic, date and formatting helpers, but never drop or alter a domain rule, threshold or outcome. Expand each retained logic block from the DML source. Improve labels, grouping and readability so an engineer can trace every domain decision precisely.",
5
5
  };
6
6
  /**
7
7
  * Map free-form user/tool wording onto a supported grade. Returns undefined when
@@ -29,7 +29,7 @@ function buildPrompt(grade, view, source, seed, feedback) {
29
29
  return [
30
30
  GRADE_PROMPTS[grade],
31
31
  "",
32
- `Rules: output ONLY Mermaid v11 source (no code fences); keep it a '${syntax}' diagram; keep the facts accurate; do not use reserved words such as 'end' as node ids or class names.`,
32
+ `Rules: output ONLY Mermaid v11 source (no code fences); keep it a '${syntax}' diagram; keep the facts and decision logic accurate; preserve the seed's domain LOGIC blocks (conditions, thresholds, outcomes) and RULES facts instead of collapsing them into generic nodes, while grouping pure arithmetic/date/format helpers; do not use reserved words such as 'end' as node ids or class names.`,
33
33
  "",
34
34
  "DML source:",
35
35
  source,
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;
@@ -22,5 +23,14 @@ export declare function parsePlan(input: string): ParsedPlan;
22
23
  * A clean tree is what lets the next /dc-apply take a rollback snapshot.
23
24
  */
24
25
  export declare function offerCommit(pi: ExtensionAPI, ctx: ExtensionContext, action: string, change: string): Promise<void>;
26
+ /**
27
+ * Ask the user a DeepClause question.
28
+ *
29
+ * Pi's interactive text-input dialog renders only its title: the placeholder
30
+ * argument is ignored by `ExtensionInputComponent`. Passing the question as the
31
+ * placeholder (as the extension used to) made it invisible, so put the whole
32
+ * question in the title and label which run is asking.
33
+ */
34
+ export declare function requestDeepClauseInput(ctx: Pick<ExtensionContext, "ui">, label: string, prompt: string, signal: AbortSignal): Promise<string | undefined>;
25
35
  export default function deepClauseExtension(pi: ExtensionAPI): void;
26
36
  export {};