typeglish 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +13 -0
  3. package/README.md +301 -0
  4. package/dist/chunk-3NQ3VYO6.js +566 -0
  5. package/dist/chunk-3NQ3VYO6.js.map +1 -0
  6. package/dist/chunk-KRYC4OTL.js +3573 -0
  7. package/dist/chunk-KRYC4OTL.js.map +1 -0
  8. package/dist/chunk-NLQIL5WD.js +25 -0
  9. package/dist/chunk-NLQIL5WD.js.map +1 -0
  10. package/dist/chunk-PR4QN5HX.js +39 -0
  11. package/dist/chunk-PR4QN5HX.js.map +1 -0
  12. package/dist/chunk-PXAMTGYY.js +3 -0
  13. package/dist/chunk-PXAMTGYY.js.map +1 -0
  14. package/dist/chunk-VMACILYN.js +140 -0
  15. package/dist/chunk-VMACILYN.js.map +1 -0
  16. package/dist/chunk-Y64EAEYB.js +10919 -0
  17. package/dist/chunk-Y64EAEYB.js.map +1 -0
  18. package/dist/chunk-YH4ZLQ4G.js +4260 -0
  19. package/dist/chunk-YH4ZLQ4G.js.map +1 -0
  20. package/dist/chunk-ZKMHZHID.js +56 -0
  21. package/dist/chunk-ZKMHZHID.js.map +1 -0
  22. package/dist/cli.d.ts +1 -0
  23. package/dist/cli.js +447 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/compile-DViQS2oG.d.ts +310 -0
  26. package/dist/diagnostics-BWJ_oMd8.d.ts +49 -0
  27. package/dist/exhaustiveness-VBVWCX5W.js +123 -0
  28. package/dist/exhaustiveness-VBVWCX5W.js.map +1 -0
  29. package/dist/index-Df_QnTgq.d.ts +313 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.js +7 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/mcp.d.ts +11 -0
  34. package/dist/mcp.js +36378 -0
  35. package/dist/mcp.js.map +1 -0
  36. package/dist/monaco.css +46 -0
  37. package/dist/monaco.d.ts +43 -0
  38. package/dist/monaco.js +971 -0
  39. package/dist/monaco.js.map +1 -0
  40. package/dist/node.d.ts +21 -0
  41. package/dist/node.js +8 -0
  42. package/dist/node.js.map +1 -0
  43. package/dist/spell-node.d.ts +8 -0
  44. package/dist/spell-node.js +23 -0
  45. package/dist/spell-node.js.map +1 -0
  46. package/dist/spell.d.ts +18 -0
  47. package/dist/spell.js +4 -0
  48. package/dist/spell.js.map +1 -0
  49. package/dist/z3-MWU3GSVT.js +5 -0
  50. package/dist/z3-MWU3GSVT.js.map +1 -0
  51. package/package.json +160 -0
@@ -0,0 +1,310 @@
1
+ import { S as Severity, R as RubricDim, a as SpellChecker, D as Diagnostic } from './diagnostics-BWJ_oMd8.js';
2
+
3
+ /** Half-open [start, end) char offsets of a node within its source clause. */
4
+ interface Span {
5
+ start: number;
6
+ end: number;
7
+ }
8
+
9
+ type Frame = 'emit' | 'suppress' | 'route' | 'communicate-tell' | 'communicate-ask' | 'confirm' | 'capture' | 'execute' | 'query' | 'greet' | 'start' | 'stop' | 'permit';
10
+
11
+ interface PredicateIR {
12
+ /** Canonical head: a canon verb ('transfer'), an antonym-normalized positive pole
13
+ * ('emit' for 'withhold', lexicalPolarity −1), or the folded surface verb as fallback. */
14
+ head: string;
15
+ /** The verb as written, particle included ('hand off', 'read back'). */
16
+ surface: string;
17
+ frame?: Frame;
18
+ particle?: string;
19
+ /** Normalized object NP — article-stripped, last word singularized ('the calls' → 'call'). */
20
+ theme?: string;
21
+ roles: Partial<Record<'goal' | 'source' | 'instrument' | 'beneficiary', string>>;
22
+ /** Bound $IMPORT tool key — only via a light verb + manifest/snake_case object. */
23
+ tool?: string;
24
+ /** −1 when the surface verb is the negative pole of an antonym pair (withhold = ¬emit). */
25
+ lexicalPolarity: 1 | -1;
26
+ confidence: 'lexicon' | 'heuristic';
27
+ }
28
+
29
+ /** A boolean condition over named atoms (flat AND / OR / NOT — the shape conditions take today). */
30
+ type Cond = {
31
+ t: 'true';
32
+ } | {
33
+ t: 'false';
34
+ } | {
35
+ t: 'atom';
36
+ key: string;
37
+ text: string;
38
+ } | {
39
+ t: 'not';
40
+ f: Cond;
41
+ } | {
42
+ t: 'and';
43
+ fs: Cond[];
44
+ } | {
45
+ t: 'or';
46
+ fs: Cond[];
47
+ };
48
+ /** Deontic force of a directive, normalized from its modal. Obligate/prohibit are the hard pair the
49
+ * consistency check keys on; the soft forms are defeasible and never hard-conflict. */
50
+ type Deontic = 'obligate' | 'prohibit' | 'permit' | 'recommend' | 'discourage';
51
+ /** A numeric bound parsed from a directive's action — `at most 3 sentences`, `at least 5 words`. The
52
+ * SMT theory propositional reasoning can't reach: each bound alone is satisfiable, but two on the
53
+ * same quantity may be jointly empty (`≤ 3` ∧ `≥ 5`), which only integer arithmetic decides. */
54
+ type NumericOp = 'le' | 'ge' | 'lt' | 'gt' | 'eq';
55
+ interface NumericConstraint {
56
+ /** The measured unit, singularized (`sentence`, `word`). Two bounds conflict only on a shared quantity. */
57
+ quantity: string;
58
+ op: NumericOp;
59
+ n: number;
60
+ text: string;
61
+ }
62
+ interface DirectiveIR {
63
+ kind: 'directive';
64
+ deontic: Deontic;
65
+ /** Normalized subject the directive scopes to; '' is the implicit agent ("you"). */
66
+ subject: string;
67
+ /** The action as a normalized atom key plus its original text. */
68
+ action: {
69
+ key: string;
70
+ text: string;
71
+ };
72
+ /** A numeric bound parsed from the action (`at most 3 sentences`), or null. */
73
+ numeric: NumericConstraint | null;
74
+ /** The parsed predicate (verb + theme + roles + tool binding), or null when the head isn't a
75
+ * catalog verb. Lexicon-confidence canon/tool parses also shape `action.key`. */
76
+ predicate: PredicateIR | null;
77
+ /** Guard under which the directive applies (TRUE when unconditional). */
78
+ cond: Cond;
79
+ line: number;
80
+ span: Span;
81
+ }
82
+ interface DeclarationIR {
83
+ kind: 'declaration';
84
+ name: string;
85
+ value: string;
86
+ /** True for a negated copula (`IS NOT`) — a negative fact rather than a binding. */
87
+ negated: boolean;
88
+ line: number;
89
+ span: Span;
90
+ }
91
+ type IRNode = DirectiveIR | DeclarationIR;
92
+ interface IR {
93
+ nodes: IRNode[];
94
+ }
95
+
96
+ type ParamType = 'string' | 'number' | 'integer' | 'boolean';
97
+ interface ToolParam {
98
+ name: string;
99
+ type: ParamType;
100
+ /** A `list of <type>s` open array, or — with `enum` set — an `any of a, b` pick-many set. */
101
+ array?: boolean;
102
+ required: boolean;
103
+ description?: string;
104
+ /** The closed set: `one of a, b` (pick one) or, with `array`, `any of a, b` (pick many). */
105
+ enum?: string[];
106
+ /** `default to <value>`. A param with a default is optional by definition. */
107
+ default?: string | number | boolean;
108
+ }
109
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
110
+ /** How the tool EXECUTES — the declarative HTTP binding. Authored as `request: METHOD https://…` or
111
+ * `request: METHOD <service> /path`; either way it is stored RESOLVED (absolute URL, service headers merged
112
+ * in), so the runner never needs the service table. `@{param}` pointers interpolate from the model's
113
+ * input; `@{env.NAME}` from the runner's environment (secrets never live in the file). A tool WITHOUT a
114
+ * binding is still legal — schema-only, executed by the host runtime (the $IMPORT-style case). */
115
+ interface ToolBinding {
116
+ method: HttpMethod;
117
+ url: string;
118
+ headers: {
119
+ name: string;
120
+ value: string;
121
+ }[];
122
+ /** The $SERVICE this binding resolved through, when it used the `METHOD service /path` form. */
123
+ service?: string;
124
+ line: number;
125
+ }
126
+ interface ToolDef {
127
+ name: string;
128
+ description: string;
129
+ params: ToolParam[];
130
+ binding?: ToolBinding;
131
+ /** The 0-based line of the block's `$TOOL name` opener. */
132
+ line: number;
133
+ }
134
+ /** A shared external backend: `base:` + `headers:`, referenced by tool bindings. */
135
+ interface ServiceDef {
136
+ name: string;
137
+ base: string;
138
+ headers: {
139
+ name: string;
140
+ value: string;
141
+ }[];
142
+ line: number;
143
+ }
144
+ interface ToolConflict {
145
+ lines: number[];
146
+ message: string;
147
+ code: string;
148
+ severity: Severity;
149
+ }
150
+ /** Parse every $TOOL block (resolving `request: METHOD service /path` through the $SERVICE table) and every
151
+ * $SERVICE block. Indentation-scoped and fence-aware, mirroring toolBlockMask exactly, so what parses here is
152
+ * precisely what render() strips and what the scopes exclude. Pure. */
153
+ declare function parseTools(rawLines: string[]): {
154
+ tools: ToolDef[];
155
+ services: ServiceDef[];
156
+ conflicts: ToolConflict[];
157
+ };
158
+ /** The tool's parameters as a JSON-Schema object — the `input_schema` (Anthropic) / `parameters` (OpenAI)
159
+ * shape, so a compiled agent bundle is directly consumable by a function-calling API. */
160
+ declare function toolInputSchema(tool: ToolDef): {
161
+ type: 'object';
162
+ properties: Record<string, Record<string, unknown>>;
163
+ required: string[];
164
+ };
165
+
166
+ type Expr = {
167
+ kind: 'read';
168
+ name: string;
169
+ } | {
170
+ kind: 'not';
171
+ arg: Expr;
172
+ } | {
173
+ kind: 'and';
174
+ left: Expr;
175
+ right: Expr;
176
+ } | {
177
+ kind: 'or';
178
+ left: Expr;
179
+ right: Expr;
180
+ } | {
181
+ kind: 'is';
182
+ name: string;
183
+ value: string;
184
+ negate: boolean;
185
+ } | {
186
+ kind: 'oneOf';
187
+ name: string;
188
+ values: string[];
189
+ } | {
190
+ kind: 'cmp';
191
+ name: string;
192
+ op: '>' | '>=' | '<' | '<=';
193
+ n: number;
194
+ };
195
+ type Bag = Record<string, string | number | boolean>;
196
+ interface ExprParse {
197
+ ast: Expr | null;
198
+ error: string | null;
199
+ }
200
+ /** Parse a guard/predicate expression. Returns the AST, or a human-readable `error` (never both). Pure. */
201
+ declare function parseExpr(text: string): ExprParse;
202
+ /** Evaluate a guard against the variable bag. Total and deterministic — a missing variable reads as
203
+ * falsy/absent, never throws (out-of-domain values simply don't match). Pure. */
204
+ declare function evalExpr(e: Expr, bag: Bag): boolean;
205
+ interface Atom {
206
+ name: string;
207
+ test: 'bool' | 'value' | 'numeric';
208
+ values: string[];
209
+ }
210
+ /** Every leaf atom in the expression, so the checker can verify each input is declared and used at the right
211
+ * type (a bare read wants a bool; a value comparison wants an enum member; a `>`/`<` wants a number). Pure. */
212
+ declare function exprAtoms(e: Expr): Atom[];
213
+
214
+ interface CompileOptions {
215
+ /** Rubric dimensions — lets the checker flag a constraint that nothing measures. */
216
+ rubric?: RubricDim[];
217
+ /** The Instructions body, compiled into the XML after the System body. */
218
+ instructions?: string;
219
+ /** An injected dictionary predicate (from the edge — nspell in the browser/CLI). Turns on the unknown-word
220
+ * check; when omitted the pure core ships no dictionary and that check is skipped. */
221
+ isWord?: SpellChecker;
222
+ /** Extra ADDRESSEE nouns that resolve to `you` (the glish.tgc `$CONFIG addressee` dial, already parsed —
223
+ * config-schema.ts parseAddresseeConfig). The declared rules (aliases, roles) need no opt-in. */
224
+ addressees?: readonly string[];
225
+ /** The project's `glish.tgc` raw source — the config-schema extension layer. When present, config diagnostics
226
+ * resolve the glish-extended schema + cross-field rules; when absent, the built-in schema only. */
227
+ glishConfig?: string;
228
+ }
229
+ /** Everything a `.tg` file declares BEYOND the prompt — what makes a single file a whole agent. The prompt
230
+ * itself is `xml`; this is the rest: the compile-time settings and the inline tool definitions, ready for a
231
+ * runner (`toolInputSchema` converts a tool to the Anthropic/OpenAI schema shape). */
232
+ /** The resolved MODEL config: the `$CONFIG model <id>` block's id + numeric params, with `via` resolved to its
233
+ * `$SERVICE` transport (base + headers; the runner fills the secret from env, never the file). This is the
234
+ * agent's whole model definition — endpoint + params — living in TG, not hardcoded in the harness. */
235
+ interface ResolvedModel {
236
+ id: string;
237
+ maxTokens?: number;
238
+ temperature?: number;
239
+ service?: {
240
+ name: string;
241
+ base: string;
242
+ headers: {
243
+ name: string;
244
+ value: string;
245
+ }[];
246
+ };
247
+ }
248
+ interface AgentBundle {
249
+ /** Compile-time settings from `$CONFIG` — the simple, header-only kinds (modality, language, …). */
250
+ config: Record<string, string>;
251
+ /** The resolved model config (id + params + transport) — from `$CONFIG model <id>` + params. */
252
+ model?: ResolvedModel;
253
+ /** Inline `$TOOL name` definitions — interface + optional HTTP binding. */
254
+ tools: ToolDef[];
255
+ }
256
+ interface CompileResult {
257
+ /** The lowered logical form: directives (with guards / numeric bounds) and declarations. */
258
+ ir: IR;
259
+ /** Every problem found, each with a line/column range and an optional quick-fix. */
260
+ diagnostics: Diagnostic[];
261
+ /** False when a hard logic error (contradiction / conflicting declaration) is present. */
262
+ consistent: boolean;
263
+ /** False when a structural error (missing/duplicate step, dangling reference, undeclared tool) is present.
264
+ * Orthogonal to `consistent` — a prompt can be logically consistent yet structurally broken. */
265
+ wellFormed: boolean;
266
+ /** The compiled, XML-tagged prompt the agent runs. */
267
+ xml: string;
268
+ /** The agent bundle: `$CONFIG` settings + inline tool definitions. With `xml`, everything a runner needs. */
269
+ agent: AgentBundle;
270
+ }
271
+ /**
272
+ * Compile TypeGlish source: parse → lower to IR → run every diagnostic → decide consistency → emit XML.
273
+ * Synchronous and self-contained. The numeric (Z3) tier is layered on by Node callers via checkNumeric +
274
+ * {@link numericDiagnostics}; the browser routes that one async check through the server.
275
+ */
276
+ declare function compile(source: string, opts?: CompileOptions): CompileResult;
277
+ /**
278
+ * Fold numeric (Z3/SMT) conflicts into the diagnostics stream. The numeric tier runs out-of-process — in
279
+ * Node directly, or through the server for the browser — so its results arrive separately and merge here,
280
+ * sharing the line-range + cross-reference formatting with every other diagnostic. Kept z3-free on purpose.
281
+ */
282
+ declare function numericDiagnostics(conflicts: {
283
+ lines: number[];
284
+ message: string;
285
+ }[], source: string): Diagnostic[];
286
+ /**
287
+ * Fold the solver's STRUCTURAL conflicts — predicate-chain exhaustiveness gaps Z3 finds — into the
288
+ * diagnostics stream. Same out-of-process path as {@link numericDiagnostics}, but carrying `structure/<code>`
289
+ * and the conflict's own severity, so a solver-found gap reads identically to a sync structural one and folds
290
+ * into `wellFormed`. Kept z3-free: the solver runs elsewhere; this only shapes its results.
291
+ */
292
+ declare function structuralDiagnostics(conflicts: {
293
+ lines: number[];
294
+ message: string;
295
+ code: string;
296
+ severity: Severity;
297
+ }[], source: string): Diagnostic[];
298
+
299
+ interface PreparedTemplate {
300
+ readonly source: string;
301
+ /** Apply the bag → the resolved source (bound switches/guards collapsed; the rest left for render). */
302
+ resolve(bag: Bag): string;
303
+ /** Apply the bag AND render → the model-facing prompt. Unbound switches become conditional prose. */
304
+ render(bag: Bag): string;
305
+ }
306
+ /** Parse the state-independent structure of a template ONCE; the returned object resolves/renders per bag
307
+ * without re-parsing. Use this on the hot path where one prompt serves many turns. */
308
+ declare function prepareTemplate(source: string): PreparedTemplate;
309
+
310
+ export { type AgentBundle as A, type Bag as B, type CompileOptions as C, type Deontic as D, type Expr as E, type HttpMethod as H, type IR as I, type ParamType as P, type ResolvedModel as R, type Span as S, type ToolBinding as T, type CompileResult as a, type IRNode as b, type PreparedTemplate as c, type ToolDef as d, type ToolParam as e, compile as f, evalExpr as g, exprAtoms as h, parseTools as i, prepareTemplate as j, type PredicateIR as k, numericDiagnostics as n, parseExpr as p, structuralDiagnostics as s, toolInputSchema as t };
@@ -0,0 +1,49 @@
1
+ interface InlineToken {
2
+ text: string;
3
+ cls?: string;
4
+ }
5
+
6
+ type Severity = 'info' | 'warn' | 'error';
7
+ interface Diagnostic {
8
+ source: string;
9
+ severity: Severity;
10
+ line: number;
11
+ start: number;
12
+ end: number;
13
+ message: string;
14
+ fix?: {
15
+ title: string;
16
+ replacement: string;
17
+ };
18
+ }
19
+ interface RubricDim {
20
+ key: string;
21
+ description: string;
22
+ }
23
+ type SpellChecker = (word: string) => boolean;
24
+ /** Collect static diagnostics. Skips comments, blanks, headings, and fenced code. An injected `isWord`
25
+ * dictionary (from the edge) turns on the unknown-word check; without it the pure core ships no dictionary. */
26
+ declare function runDiagnostics(rawLines: string[], _rubric: RubricDim[], opts?: {
27
+ isWord?: SpellChecker;
28
+ addressees?: readonly string[];
29
+ glishConfig?: string;
30
+ }): Diagnostic[];
31
+ /** Apply a quick-fix's text replacement at the diagnostic's range. */
32
+ declare function applyFix(value: string, d: Diagnostic): string;
33
+ interface RenderSpan {
34
+ text: string;
35
+ cls?: string;
36
+ }
37
+ /**
38
+ * Merge a line's markdown tokens with its diagnostic ranges into final overlay spans.
39
+ * Each output span carries the markdown class of its source token plus a `diag-<sev>`
40
+ * class where a diagnostic covers it — splitting at every boundary so underlines land on
41
+ * exactly the offending characters while the text stays byte-for-byte identical.
42
+ */
43
+ declare function composeSpans(tokens: InlineToken[], diags: {
44
+ start: number;
45
+ end: number;
46
+ severity: Severity;
47
+ }[]): RenderSpan[];
48
+
49
+ export { type Diagnostic as D, type RubricDim as R, type Severity as S, type SpellChecker as a, applyFix as b, composeSpans as c, runDiagnostics as r };
@@ -0,0 +1,123 @@
1
+ import { buildStructure, parseSwitches, predicateEncodable, parseExpr } from './chunk-YH4ZLQ4G.js';
2
+ import { z3 } from './chunk-VMACILYN.js';
3
+ import './chunk-Y64EAEYB.js';
4
+ import './chunk-PR4QN5HX.js';
5
+
6
+ // src/core/solver/exhaustiveness.ts
7
+ function constFor(e, name, type) {
8
+ const existing = e.vars.get(name);
9
+ if (existing) return existing;
10
+ let v;
11
+ if (type.kind === "number") v = e.ctx.Real.const(name);
12
+ else if (type.kind === "enum") {
13
+ v = e.ctx.Int.const(name);
14
+ e.bounds.push(e.ctx.And(v.ge(0), v.lt(type.members.length)));
15
+ } else v = e.ctx.Bool.const(name);
16
+ e.vars.set(name, v);
17
+ return v;
18
+ }
19
+ function encode(e, expr, seen) {
20
+ switch (expr.kind) {
21
+ case "not": {
22
+ const a = encode(e, expr.arg, seen);
23
+ return a === null ? null : e.ctx.Not(a);
24
+ }
25
+ case "and":
26
+ case "or": {
27
+ const l = encode(e, expr.left, seen);
28
+ const r = encode(e, expr.right, seen);
29
+ if (l === null || r === null) return null;
30
+ return expr.kind === "and" ? e.ctx.And(l, r) : e.ctx.Or(l, r);
31
+ }
32
+ case "read": {
33
+ if (e.defines.has(expr.name)) {
34
+ if (seen.has(expr.name)) return null;
35
+ const ast = parseExpr(e.defines.get(expr.name)?.expr ?? "").ast;
36
+ return ast === null ? null : encode(e, ast, /* @__PURE__ */ new Set([...seen, expr.name]));
37
+ }
38
+ const t = e.types.get(expr.name);
39
+ return t?.kind === "bool" ? constFor(e, expr.name, t) : null;
40
+ }
41
+ case "is": {
42
+ const t = e.types.get(expr.name);
43
+ if (t?.kind !== "enum") return null;
44
+ const idx = t.members.indexOf(expr.value);
45
+ const eq = idx < 0 ? e.ctx.Bool.val(false) : constFor(e, expr.name, t).eq(idx);
46
+ return expr.negate ? e.ctx.Not(eq) : eq;
47
+ }
48
+ case "oneOf": {
49
+ const t = e.types.get(expr.name);
50
+ if (t?.kind !== "enum") return null;
51
+ const v = constFor(e, expr.name, t);
52
+ const eqs = expr.values.map((val) => {
53
+ const i = t.members.indexOf(val);
54
+ return i < 0 ? e.ctx.Bool.val(false) : v.eq(i);
55
+ });
56
+ return eqs.length ? e.ctx.Or(...eqs) : e.ctx.Bool.val(false);
57
+ }
58
+ case "cmp": {
59
+ const t = e.types.get(expr.name);
60
+ if (t?.kind !== "number") return null;
61
+ const v = constFor(e, expr.name, t);
62
+ return expr.op === ">" ? v.gt(expr.n) : expr.op === ">=" ? v.ge(expr.n) : expr.op === "<" ? v.lt(expr.n) : v.le(expr.n);
63
+ }
64
+ }
65
+ }
66
+ function counterexample(solver, e) {
67
+ try {
68
+ const model = solver.model();
69
+ const parts = [];
70
+ for (const [name, v] of e.vars) {
71
+ const t = e.types.get(name);
72
+ const raw = model.eval(v, true).toString();
73
+ if (t?.kind === "enum") parts.push(`@{${name}}=${t.members[Number(raw)] ?? raw}`);
74
+ else parts.push(`@{${name}}=${raw}`);
75
+ }
76
+ return parts.length ? parts.join(", ") : "some combination of inputs";
77
+ } catch {
78
+ return "some combination of inputs";
79
+ }
80
+ }
81
+ async function predicateExhaustiveness(source) {
82
+ const lines = source.split("\n");
83
+ const doc = buildStructure(lines);
84
+ const candidates = parseSwitches(lines).filter(
85
+ (b) => b.kind === "predicate" && b.error === null && !b.arms.some((a) => a.kind === "else")
86
+ );
87
+ if (candidates.length === 0) return [];
88
+ const encodable = candidates.filter((b) => predicateEncodable(b, doc.inputTypes, doc.defines));
89
+ if (encodable.length === 0) return [];
90
+ const ctx = await z3();
91
+ const out = [];
92
+ for (const block of encodable) {
93
+ const e = { ctx, types: doc.inputTypes, defines: doc.defines, vars: /* @__PURE__ */ new Map(), bounds: [] };
94
+ const encoded = [];
95
+ let ok = true;
96
+ for (const arm of block.arms) {
97
+ if (arm.kind !== "case" || arm.predicate === void 0) continue;
98
+ const ast = parseExpr(arm.predicate).ast;
99
+ const enc = ast === null ? null : encode(e, ast, /* @__PURE__ */ new Set());
100
+ if (enc === null) {
101
+ ok = false;
102
+ break;
103
+ }
104
+ encoded.push(enc);
105
+ }
106
+ if (!ok || encoded.length === 0) continue;
107
+ const solver = new ctx.Solver();
108
+ for (const b of e.bounds) solver.add(b);
109
+ solver.add(ctx.Not(ctx.Or(...encoded)));
110
+ if (await solver.check() !== "sat") continue;
111
+ out.push({
112
+ lines: [block.headerLine],
113
+ message: `This chain isn't exhaustive \u2014 no arm handles ${counterexample(solver, e)}. Add an arm or an ELSE.`,
114
+ code: "non-exhaustive-switch",
115
+ severity: "error"
116
+ });
117
+ }
118
+ return out;
119
+ }
120
+
121
+ export { predicateExhaustiveness };
122
+ //# sourceMappingURL=exhaustiveness-VBVWCX5W.js.map
123
+ //# sourceMappingURL=exhaustiveness-VBVWCX5W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/solver/exhaustiveness.ts"],"names":[],"mappings":"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","file":"exhaustiveness-VBVWCX5W.js","sourcesContent":["// ── Z3-backed predicate-chain exhaustiveness ────────────────────────────────────────────────────────────\n// A predicate chain (mixed-condition IF / ELSE IF arms) chooses the first true arm over arbitrary boolean conditions. Enumeration can't prove\n// such a set total (the state space is exponential, and numeric ranges are infinite), so the structural checker\n// conservatively requires an ELSE. This tier does better: encode each arm condition as an SMT formula over the\n// declared input domains and ask Z3 whether their disjunction is a TAUTOLOGY — i.e. whether `domains ∧ ¬(C₁ ∨ … ∨\n// Cₙ)` is UNSAT. UNSAT ⇒ provably total ⇒ no ELSE needed. SAT ⇒ the model is an uncovered state (the\n// counterexample we report). Runs server-side (Node/CLI or the /api/logic route), like the numeric tier.\n//\n// Scope (this slice): predicate chains with NO `ELSE`, whose every arm condition is over encodable inputs —\n// bool, enum, or number, plus `$DEFINE` names (inlined to their base expression). A condition touching a string\n// or untyped input isn't encodable, so that switch is left to the structural tier's require-ELSE rule.\n\nimport { type Expr, parseExpr } from '../expr';\nimport { type InputType, type StructConflict, buildStructure, predicateEncodable } from '../structure';\nimport { parseSwitches } from '../switch';\nimport { z3 } from './z3';\n\n// The z3-solver high-level API is heavily generic; this boundary stays `any`-typed, exactly like z3.ts. The\n// safety is in the InputType-driven encoding on the way in and the tests on the way out.\ntype Z3 = any; // eslint-disable-line @typescript-eslint/no-explicit-any\n\ninterface EncCtx {\n ctx: Z3;\n types: Map<string, InputType>;\n defines: Map<string, { expr: string }>;\n vars: Map<string, Z3>; // one interned const per input actually used\n bounds: Z3[]; // enum domain constraints (0 ≤ v < |members|), added to the solver\n}\n\n/** The interned Z3 const for an input, created with its domain constraint the first time it's seen. */\nfunction constFor(e: EncCtx, name: string, type: InputType): Z3 {\n const existing = e.vars.get(name);\n if (existing) return existing;\n let v: Z3;\n if (type.kind === 'number') v = e.ctx.Real.const(name);\n else if (type.kind === 'enum') {\n v = e.ctx.Int.const(name); // enum modelled as an index into its members\n e.bounds.push(e.ctx.And(v.ge(0), v.lt(type.members.length)));\n } else v = e.ctx.Bool.const(name); // bool (string never reaches here — encode() returns null for it first)\n e.vars.set(name, v);\n return v;\n}\n\n/** Encode an expression to a Z3 bool, or `null` if any leaf is unencodable (string / untyped / a type mismatch).\n * `$DEFINE` reads are inlined to their base expression; `seen` guards the (checker-forbidden) cyclic case. */\nfunction encode(e: EncCtx, expr: Expr, seen: Set<string>): Z3 | null {\n switch (expr.kind) {\n case 'not': {\n const a = encode(e, expr.arg, seen);\n return a === null ? null : e.ctx.Not(a);\n }\n case 'and':\n case 'or': {\n const l = encode(e, expr.left, seen);\n const r = encode(e, expr.right, seen);\n if (l === null || r === null) return null;\n return expr.kind === 'and' ? e.ctx.And(l, r) : e.ctx.Or(l, r);\n }\n case 'read': {\n if (e.defines.has(expr.name)) {\n if (seen.has(expr.name)) return null; // a cycle — the checker flags it; bail rather than loop\n const ast = parseExpr(e.defines.get(expr.name)?.expr ?? '').ast;\n return ast === null ? null : encode(e, ast, new Set([...seen, expr.name]));\n }\n const t = e.types.get(expr.name);\n return t?.kind === 'bool' ? constFor(e, expr.name, t) : null; // a bare read needs a bool\n }\n case 'is': {\n const t = e.types.get(expr.name);\n if (t?.kind !== 'enum') return null; // `is <value>` needs an enum\n const idx = t.members.indexOf(expr.value);\n const eq = idx < 0 ? e.ctx.Bool.val(false) : constFor(e, expr.name, t).eq(idx); // impossible value never matches\n return expr.negate ? e.ctx.Not(eq) : eq;\n }\n case 'oneOf': {\n const t = e.types.get(expr.name);\n if (t?.kind !== 'enum') return null;\n const v = constFor(e, expr.name, t);\n const eqs = expr.values.map((val) => {\n const i = t.members.indexOf(val);\n return i < 0 ? e.ctx.Bool.val(false) : v.eq(i);\n });\n return eqs.length ? e.ctx.Or(...eqs) : e.ctx.Bool.val(false);\n }\n case 'cmp': {\n const t = e.types.get(expr.name);\n if (t?.kind !== 'number') return null; // `>`/`<` needs a number\n const v = constFor(e, expr.name, t);\n return expr.op === '>' ? v.gt(expr.n) : expr.op === '>=' ? v.ge(expr.n) : expr.op === '<' ? v.lt(expr.n) : v.le(expr.n);\n }\n }\n}\n\n/** Read the SAT model back into a readable uncovered state (`$tier=free, $flagged=false`), best-effort. */\nfunction counterexample(solver: Z3, e: EncCtx): string {\n try {\n const model = solver.model();\n const parts: string[] = [];\n for (const [name, v] of e.vars) {\n const t = e.types.get(name);\n const raw = model.eval(v, true).toString();\n if (t?.kind === 'enum') parts.push(`@{${name}}=${t.members[Number(raw)] ?? raw}`);\n else parts.push(`@{${name}}=${raw}`);\n }\n return parts.length ? parts.join(', ') : 'some combination of inputs';\n } catch {\n return 'some combination of inputs';\n }\n}\n\n/** Every predicate chain (no ELSE, all conditions encodable) that Z3 can't prove total, as a\n * `structure/non-exhaustive-switch` conflict naming an uncovered state. A switch it CAN prove total yields\n * nothing (the mandatory-ELSE requirement is lifted); an unencodable one is skipped (left to the sync tier). */\nexport async function predicateExhaustiveness(source: string): Promise<StructConflict[]> {\n const lines = source.split('\\n');\n const doc = buildStructure(lines);\n const candidates = parseSwitches(lines).filter(\n (b) => b.kind === 'predicate' && b.error === null && !b.arms.some((a) => a.kind === 'else'),\n );\n if (candidates.length === 0) return [];\n\n const encodable = candidates.filter((b) => predicateEncodable(b, doc.inputTypes, doc.defines)); // the sync tier's exact deferral set\n if (encodable.length === 0) return [];\n\n const ctx = await z3();\n const out: StructConflict[] = [];\n for (const block of encodable) {\n const e: EncCtx = { ctx, types: doc.inputTypes, defines: doc.defines, vars: new Map(), bounds: [] };\n const encoded: Z3[] = [];\n let ok = true; // predicateEncodable already vetted this block; the null-branch is a defensive backstop\n for (const arm of block.arms) {\n if (arm.kind !== 'case' || arm.predicate === undefined) continue;\n const ast = parseExpr(arm.predicate).ast;\n const enc = ast === null ? null : encode(e, ast, new Set());\n if (enc === null) {\n ok = false;\n break;\n }\n encoded.push(enc);\n }\n if (!ok || encoded.length === 0) continue;\n\n const solver = new ctx.Solver();\n for (const b of e.bounds) solver.add(b);\n solver.add(ctx.Not(ctx.Or(...encoded))); // a state no arm covers\n if ((await solver.check()) !== 'sat') continue; // UNSAT ⇒ the CASEs are total ⇒ no ELSE needed\n\n out.push({\n lines: [block.headerLine],\n message: `This chain isn't exhaustive — no arm handles ${counterexample(solver, e)}. Add an arm or an ELSE.`,\n code: 'non-exhaustive-switch',\n severity: 'error',\n });\n }\n return out;\n}\n"]}