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,566 @@
1
+ import { reportFull, MissingSolverError } from './chunk-NLQIL5WD.js';
2
+ import { report, compile, resolve, runDiagnostics, numericDiagnostics, CHARSET_DOC, renderLines, ruleCoverage } from './chunk-KRYC4OTL.js';
3
+ import { PRIM_TYPES, REFINEMENT_KEYWORDS, BUILTIN_CONFIG_SCHEMA, buildStructure, extractSuite, parseSwitches } from './chunk-YH4ZLQ4G.js';
4
+ import { DOC_TABLES, OPERATOR_GROUPS, GUARDS, toolBlockMask, examplesMask, lower, lineOrdinals, configSectionMask, isAnnotationLine, TAG_ONLY_LINE, proseExemptLines, parseLine, displayLine, hasTrailingAnnotation, STATEMENT_SNIPPETS, BLOCK_SNIPPETS, DIRECTIVE_SNIPPETS } from './chunk-Y64EAEYB.js';
5
+ import { createHash } from 'crypto';
6
+ import { sep, relative, basename, dirname, join } from 'path';
7
+ import { existsSync, readFileSync } from 'fs';
8
+
9
+ // src/core/codes.ts
10
+ var DIAGNOSTIC_CODES = {
11
+ "clarity/dead-rule": { severity: "warn", summary: "A rule whose condition can never hold, so it never fires." },
12
+ "clarity/duplicate": { severity: "warn", summary: "Two rules say the same thing; one is redundant." },
13
+ "config/bad-number": { severity: "warn", summary: "A numeric config param (max_tokens, temperature) is not a number." },
14
+ "config/dangling-service": { severity: "error", summary: "A config param's `via` names a $SERVICE that is not defined." },
15
+ "config/missing-conditional": { severity: "warn", summary: "A glish cross-field rule ($REQUIRE X when Y is Z) is unmet: the condition holds but the required kind is absent." },
16
+ "config/missing-param": { severity: "warn", summary: "A config kind is missing a required param (e.g. model's via) \u2014 a nudge, not a block." },
17
+ "config/out-of-range": { severity: "warn", summary: "A config param value violates its declared refinement (a numeric range, character length, or pattern)." },
18
+ "config/unknown-model": { severity: "warn", summary: "The $CONFIG model is not a recognized model id." },
19
+ "config/unknown-param": { severity: "info", summary: "A config param is not in the kind's schema." },
20
+ "grammar/double-word": { severity: "warn", summary: 'A word repeated back-to-back, like "the the".' },
21
+ "grammar/spacing": { severity: "info", summary: "Irregular spacing around punctuation." },
22
+ "grammar/standalone-i": { severity: "warn", summary: 'Lowercase "i" as a pronoun; write "I".' },
23
+ "import/cycle": { severity: "error", summary: "Two $IMPORT files reference each other in a loop." },
24
+ "import/missing-export": { severity: "error", summary: "An imported section or anchor does not exist in the target file." },
25
+ "import/missing-file": { severity: "error", summary: "An $IMPORT file path resolves to no file." },
26
+ "import/missing-section": { severity: "error", summary: "A {file.section} reference names a section the file lacks." },
27
+ "import/nested": { severity: "error", summary: "An imported file itself imports another; nesting is not allowed." },
28
+ "import/unfilled-requirement": { severity: "warn", summary: "An imported file needs a variable the import site did not fill." },
29
+ "import/unused-file": { severity: "warn", summary: "An $IMPORT file is declared but never referenced." },
30
+ "logic/action-count": { severity: "error", summary: "One action is given two different counts (call five tools vs call six tools)." },
31
+ "logic/cardinality": { severity: "error", summary: "Two counts for one set cannot both hold (there is only one X vs there are many Xs)." },
32
+ "logic/contradiction": { severity: "error", summary: "Two rules provably conflict, like MUST X versus NEVER X." },
33
+ "logic/definite-ambiguous": { severity: "error", summary: 'A definite "the <symbol>" is bound to two or more different declared symbols.' },
34
+ "logic/definite-undeclared": { severity: "error", summary: 'A definite "the <symbol>" names a symbol kind the file never declares.' },
35
+ "logic/exclusivity": { severity: "error", summary: 'An "only" rule is exclusive, but another rule adds a member outside it.' },
36
+ "logic/insufficient-set": { severity: "error", summary: "A directive needs more items than the declared set holds (call five tools vs there is only one tool)." },
37
+ "logic/majority": { severity: "error", summary: 'Two "most" claims about the same set cannot both hold.' },
38
+ "logic/numeric": { severity: "error", summary: "A numeric impossibility (at most 3 vs at least 5), proven by Z3." },
39
+ "logic/order": { severity: "error", summary: "Steps require an impossible ordering." },
40
+ "logic/quantifier": { severity: "error", summary: "Quantified statements provably conflict (all / some / no, or a universal vs a specific case)." },
41
+ "logic/quantifier-strength": { severity: "error", summary: "One quantity per fact \u2014 a quantified claim restates one it already entails (all vs some)." },
42
+ "logic/redeclaration": { severity: "error", summary: "A name is bound to two conflicting values." },
43
+ "logic/syllogism": { severity: "error", summary: "A transitive quantifier chain is contradictory (all A are B, all B are C, no A is C)." },
44
+ "prompt/determinable-expect": { severity: "info", summary: "A judge-scored expectation that has an exact deterministic form." },
45
+ "prompt/hedging": { severity: "info", summary: "A hedge (try to, if possible) softens an instruction; commit to a modal." },
46
+ "prompt/unmeasurable": { severity: "warn", summary: "A rule with no concrete, checkable action or bound." },
47
+ "prompt/untested-rule": { severity: "info", summary: "No $TEST case exercises this rule, so its behavioral impact cannot be measured." },
48
+ "prompt/vague": { severity: "info", summary: "Vague wording the model can read many ways." },
49
+ "security/injection": { severity: "warn", summary: "Text resembling a prompt-injection pattern." },
50
+ "security/leaked-secret": { severity: "error", summary: "A credential literal in the prompt; secrets never live in the file." },
51
+ "spelling/typo": { severity: "warn", summary: "A likely misspelling, with a suggested correction." },
52
+ "spelling/unknown-word": { severity: "info", summary: "A word the dictionary does not recognize." },
53
+ "structure/bad-indent": { severity: "error", summary: "Indentation does not mirror the section nesting (2 spaces per level)." },
54
+ "structure/bare-tool-ref": { severity: "error", summary: "An exact tool name in bare prose; point it with @[name], or quote it to speak the name as text." },
55
+ "structure/curly-quote": { severity: "error", summary: "A curly quote or apostrophe; only ASCII quotes bind." },
56
+ "structure/duplicate-term": { severity: "warn", summary: "A definition term repeats within one list." },
57
+ "structure/em-dash": { severity: "error", summary: "An em or en dash splices a line; split it or use a comma." },
58
+ "structure/half-quoted-literal": { severity: "error", summary: 'A literal-zone tag missing one of its quotes; the name is quoted whole: <"name">.' },
59
+ "structure/heading-outside-section": { severity: "warn", summary: "A # heading sits outside the XML sections the prompt uses." },
60
+ "structure/inline-marker": { severity: "error", summary: 'A "::" definition marker used inline; it opens an entry at a line head \u2014 nest it as a "- " sub-definition, or quote it as a sample.' },
61
+ "structure/loose-content": { severity: "warn", summary: "Content outside any section once XML tags are used." },
62
+ "structure/misplaced-annotation": { severity: "warn", summary: "A @@ note trailing after content instead of above it." },
63
+ "structure/missing-period": { severity: "error", summary: "An unterminated statement; end with . ! ? or a lead-in colon." },
64
+ "structure/one-of-colon": { severity: "error", summary: '"one of" took a colon; the quantifier never does.' },
65
+ "structure/phantom-column": { severity: "error", summary: "A run of two or more interior spaces doing alignment structure." },
66
+ "structure/retired-brace-ref": { severity: "error", summary: "Bare {braces} / [squares] are prose now: use @{name} to reference a declared name or import." },
67
+ "structure/retired-literal-sigil": { severity: "error", summary: 'The old <=name> literal-zone spelling; the sigil is the quote pair now: <"name">.' },
68
+ "structure/special-character": { severity: "error", summary: "A typographic or decorative codepoint outside the ASCII plane." },
69
+ "structure/stacked-annotation": { severity: "error", summary: "A @@ note directly above another @@ note." },
70
+ "structure/tab": { severity: "error", summary: "A tab; the indent unit is 2 spaces." },
71
+ "structure/trailing-space": { severity: "error", summary: "Trailing whitespace at the end of a line." },
72
+ "structure/unattached-annotation": { severity: "error", summary: "A @@ note whose next line is not a statement." },
73
+ "structure/undefined-ref": { severity: "error", summary: "An @{name} pointer resolving to nothing declared." },
74
+ "structure/undefined-tool-inline": { severity: "error", summary: "A tool used inline but never imported or defined." },
75
+ "structure/wrapped-definition": { severity: "error", summary: "A hard-wrapped definition continuation; one statement per line." },
76
+ "structure/wrapped-fragment": { severity: "warn", summary: "A line that reads as a hard-wrapped continuation of the one above." },
77
+ "style/quoted-prose": { severity: "warn", summary: "A whole-line quote hides a statement from the checker." },
78
+ "style/wordy": { severity: "info", summary: "Wordy phrasing a tighter form would carry." },
79
+ "testfile/duplicate-case": { severity: "error", summary: "A case id is defined both inline and in the sibling .test.tg." },
80
+ "testfile/empty": { severity: "warn", summary: "A discovered .test.tg declares no $TEST or $EXAMPLE blocks." },
81
+ "testfile/missing-prompt": { severity: "error", summary: "A .test.tg has no sibling .tg prompt to test." },
82
+ "typeglish/if-then": { severity: "warn", summary: "A conditional (IF / WHEN / UNLESS / WHILE / \u2026) opened without its THEN." }
83
+ };
84
+ function blocks(code) {
85
+ return DIAGNOSTIC_CODES[code]?.severity === "error";
86
+ }
87
+ ({
88
+ rows: Object.entries(DIAGNOSTIC_CODES).sort(([a], [b]) => a.localeCompare(b)).map(([code, info]) => [`\`${code}\``, info.severity, info.summary])
89
+ });
90
+
91
+ // src/core/agent-doc.ts
92
+ var cell = (s) => s.replace(/\|/g, "\\|");
93
+ var template = (s) => s.replace(/\$\{\d+:([^}]*)\}/g, "<$1>").replace(/\\\$/g, "$").replace(/\\\}/g, "}");
94
+ function mdTable(t) {
95
+ const out = [`## ${t.title}`, ""];
96
+ if (t.lead !== void 0) out.push(t.lead, "");
97
+ out.push(`| ${t.headers.map(cell).join(" | ")} |`);
98
+ out.push(`|${t.headers.map(() => "---").join("|")}|`);
99
+ for (const r of t.rows) out.push(`| ${r.map(cell).join(" | ")} |`);
100
+ return out.join("\n");
101
+ }
102
+ function operators() {
103
+ const out = [
104
+ "## Operators",
105
+ "",
106
+ "**Every operator is case-insensitive** (`must` = `MUST` = `Must`). Case is never the operator/prose distinction \u2014 quote a word to keep it literal prose.",
107
+ ""
108
+ ];
109
+ for (const g of OPERATOR_GROUPS) {
110
+ out.push(`- **${g.label}** \u2014 ${g.tokens.map((t) => `\`${t}\``).join(" ")}`);
111
+ out.push(` ${g.description} Example: ${g.example}`);
112
+ }
113
+ return out.join("\n");
114
+ }
115
+ function conditionalKeywords() {
116
+ const byRole = (role) => Object.entries(GUARDS).filter(([, g]) => g.role === role).map(([k]) => k);
117
+ const openers = Object.entries(GUARDS).filter(([, g]) => g.role === "guard" && g.negate !== true).map(([k]) => k);
118
+ const negated = Object.entries(GUARDS).filter(([, g]) => g.role === "guard" && g.negate === true).map(([k]) => k);
119
+ return [
120
+ "## Conditional keywords (the prose plane)",
121
+ "",
122
+ "A bare-prose conditional is a statement the MODEL weighs at runtime; a `$IF` chain (see the table above) is resolved by the COMPILER and losing arms never reach the model. The keywords and what each builds:",
123
+ "",
124
+ `- Open a guard: ${openers.map((k) => `\`${k}\``).join(" ")}`,
125
+ `- Open a NEGATED guard (\`UNLESS x\` = \`IF NOT x\`): ${negated.map((k) => `\`${k}\``).join(" ")}`,
126
+ `- Consequence separator: ${byRole("separator").map((k) => `\`${k}\``).join(" ")}`,
127
+ `- Else-branch: ${byRole("else").map((k) => `\`${k}\``).join(" ")}`,
128
+ `- Iterate: ${byRole("iterate").map((k) => `\`${k}\``).join(" ")}`
129
+ ].join("\n");
130
+ }
131
+ function types() {
132
+ return [
133
+ "## Types (machine-plane domains)",
134
+ "",
135
+ "One shared English type grammar for `$TOOL` params and `$REQUIRE variable` domains:",
136
+ "",
137
+ `- Base types (one spelling each): ${[...PRIM_TYPES].map((t) => `\`${t}\``).join(" ")}`,
138
+ "- Composites: `one of a, b` (pick one) \xB7 `any of a, b` (pick many) \xB7 `list of strings` (open array) \xB7 `optional \u2026` \xB7 `default to 10`",
139
+ `- Refinements: ${REFINEMENT_KEYWORDS.map((k) => `\`${k}\``).join(" ")} \u2014 \`number between 0 and 2\`, \`string up to 80 characters\`, \`string matching /^[A-Z]{2}$/\``
140
+ ].join("\n");
141
+ }
142
+ function configKinds() {
143
+ const out = [
144
+ "## Built-in `$CONFIG` kinds",
145
+ "",
146
+ "A project `glish.tgc` (discovered by walking up from the `.tg` file) can extend these; unknown kinds are open, not errors.",
147
+ ""
148
+ ];
149
+ for (const [kind, schema] of Object.entries(BUILTIN_CONFIG_SCHEMA)) {
150
+ const value = schema.values !== void 0 ? `one of ${schema.values.join(", ")}` : schema.placeholder ?? "value";
151
+ out.push(`- \`$CONFIG ${kind} ${value}\` \u2014 ${schema.detail}`);
152
+ for (const [param, p] of Object.entries(schema.params ?? {})) {
153
+ out.push(` - \`- ${param}:: \u2026\` \u2014 ${p.detail}${p.required === true ? " (required)" : ""}`);
154
+ }
155
+ }
156
+ return out.join("\n");
157
+ }
158
+ function snippets(title, list) {
159
+ return [`### ${title}`, "", ...list.map((s) => `- \`${template(s.snippet ?? s.insert).replace(/\n/g, "` \u23CE `")}\` \u2014 ${s.detail}`)];
160
+ }
161
+ function authoringTemplates() {
162
+ return [
163
+ "## Authoring templates",
164
+ "",
165
+ "Fill-in shapes (`<placeholder>` marks the blanks; `\u23CE` separates lines of one template):",
166
+ "",
167
+ ...snippets("Statements", STATEMENT_SNIPPETS),
168
+ "",
169
+ ...snippets("Blocks", BLOCK_SNIPPETS),
170
+ "",
171
+ ...snippets("Commands", DIRECTIVE_SNIPPETS)
172
+ ].join("\n");
173
+ }
174
+ function gatingCodes() {
175
+ const rows = Object.entries(DIAGNOSTIC_CODES).filter(([code]) => blocks(code)).sort(([a], [b]) => a < b ? -1 : 1);
176
+ return [
177
+ "## Reading checker output \u2014 the codes that BLOCK compilation",
178
+ "",
179
+ "Every diagnostic carries `{code, severity, range, message}` and often a machine-applicable `fix`. Errors below block `build`; warnings/info are advisory (list them with `typeglish --explain <category>`; explain any code with `typeglish --explain <code>`).",
180
+ "",
181
+ "| Code | What it proves |",
182
+ "|---|---|",
183
+ ...rows.map(([code, info]) => `| \`${code}\` | ${cell(info.summary)} |`)
184
+ ].join("\n");
185
+ }
186
+ var CANONICAL_EXAMPLE = `# Role
187
+ @@ role: general Acme support, with hard pricing and escalation rails
188
+ You are a customer support agent for Acme, a SaaS analytics company.
189
+
190
+ # Constraints
191
+ @@ no_prices: quoted prices go stale and bind the company \u2014 never state one
192
+ - NEVER state a specific price.
193
+ @@ pricing_route: pricing intent belongs to sales
194
+ - ALWAYS direct pricing questions to the sales team.
195
+ @@ brevity: three sentences keeps support replies scannable
196
+ - MUST keep every response to at most 3 sentences.
197
+ @@ order_number: the order number unlocks account context
198
+ - ALWAYS ask for the order number.
199
+ @@ anger_escalation: a heated customer gets a human, not a chatbot
200
+ - IF the customer is angry THEN escalate to a human agent.
201
+ `;
202
+ function renderAgentReference() {
203
+ const parts = [
204
+ "# TypeGlish \u2014 the agent language reference",
205
+ "",
206
+ "> GENERATED from the language SSOTs (`src/core/lexicon.ts` and friends) \u2014 run `npm run gen:agent-docs`; never hand-edit. `typeglish reference` prints this document.",
207
+ "",
208
+ "TypeGlish is **typed English for prompts**: `.tg` files read as near-plain English, and the compiler checks them like code \u2014 provable defects (contradictions, dangling references, unsatisfiable bounds) are **errors** that block compilation; best-practice violations (vagueness, hedging, redundancy) **warn**. Anything the model reads is prose; operators and declarations are checked, then expanded or stripped. Plain ASCII only \u2014 the checker rewrites anything else.",
209
+ "",
210
+ "## The workflow",
211
+ "",
212
+ "The `.tg` file is SOURCE (keep it in git); what a prompt-taking system receives is a BUILD ARTIFACT.",
213
+ "",
214
+ "1. Author `<name>.tg`.",
215
+ "2. `typeglish check <name>.tg --json` \u2014 fix until 0 errors (`typeglish --explain <code>` explains any code; diagnostics carry a `fix` when one is mechanical).",
216
+ "3. `typeglish score <name>.tg --min B` \u2014 the quality floor.",
217
+ "4. `typeglish build <name>.tg` \u2014 writes `.typeglish/dist/<name>.txt` + the hash manifest (`.typeglish/build-manifest.json`). A file with a blocking error refuses to build.",
218
+ "5. Deploy the artifact **verbatim** \u2014 never the `.tg`, never a hand-edit. To change a deployed prompt: edit the `.tg`, re-run the loop.",
219
+ "",
220
+ mdTable(DOC_TABLES.find((t) => t.id === "statements")),
221
+ "",
222
+ operators(),
223
+ "",
224
+ ...DOC_TABLES.filter((t) => t.id !== "statements").flatMap((t) => [mdTable(t), ""]),
225
+ conditionalKeywords(),
226
+ "",
227
+ types(),
228
+ "",
229
+ configKinds(),
230
+ "",
231
+ mdTable(CHARSET_DOC),
232
+ "",
233
+ authoringTemplates(),
234
+ "",
235
+ gatingCodes(),
236
+ "",
237
+ "## A canonical prompt",
238
+ "",
239
+ "Compiles clean and scores A on the TG score \u2014 the shape to imitate (`@@` lines are authoring annotations for the line below; they never reach the model):",
240
+ "",
241
+ "```typeglish",
242
+ CANONICAL_EXAMPLE.trimEnd(),
243
+ "```",
244
+ ""
245
+ ];
246
+ return parts.join("\n");
247
+ }
248
+ function canonicalize(text) {
249
+ return text.replace(/\r\n/g, "\n").replace(/\n+$/, "");
250
+ }
251
+ function sha256(text) {
252
+ return createHash("sha256").update(text).digest("hex");
253
+ }
254
+ var MANIFEST_FILE = ".typeglish/build-manifest.json";
255
+ var DEFAULT_OUT_DIR = ".typeglish/dist";
256
+ var toPosix = (p) => p.split(sep).join("/");
257
+ function sourceKey(cwd, absFile) {
258
+ const rel = relative(cwd, absFile);
259
+ return rel === "" || rel.startsWith("..") ? basename(absFile) : toPosix(rel);
260
+ }
261
+ function artifactPathFor(srcKey, outDir) {
262
+ return toPosix(`${outDir.replace(/\/+$/, "")}/${srcKey.replace(/\.tg$/i, ".txt")}`);
263
+ }
264
+ async function checkSource(source, policy, compileOpts) {
265
+ try {
266
+ return { report: await reportFull(source, { policy, compile: compileOpts }), checkMode: "full" };
267
+ } catch (err) {
268
+ if (err instanceof MissingSolverError) {
269
+ return { report: report(source, { policy, compile: compileOpts }), checkMode: "sync" };
270
+ }
271
+ throw err;
272
+ }
273
+ }
274
+ async function buildPrompt(source, paths, opts) {
275
+ const policy = opts.strict ? { escalateImportance: "important" } : {};
276
+ const compileOpts = opts.glishConfig === void 0 ? void 0 : { glishConfig: opts.glishConfig };
277
+ const { report: rep, checkMode } = await checkSource(source, policy, compileOpts);
278
+ if (!rep.ok) return { ok: false, report: rep, checkMode };
279
+ const vars = opts.vars ?? null;
280
+ if (vars !== null) {
281
+ const declared = new Set(rep.program.inputs.map((i) => i.name));
282
+ const unknown = Object.keys(vars).filter((k) => !declared.has(k));
283
+ if (unknown.length > 0) {
284
+ const inputs = rep.program.inputs.map((i) => i.name);
285
+ return {
286
+ ok: false,
287
+ report: rep,
288
+ checkMode,
289
+ error: `unknown --vars key${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")} \u2014 declared inputs: ${inputs.length > 0 ? inputs.join(", ") : "(none)"}`
290
+ };
291
+ }
292
+ }
293
+ const artifactBody = vars === null ? rep.xml : compile(resolve(source, vars), compileOpts).xml;
294
+ const canonical = canonicalize(artifactBody);
295
+ const entry = {
296
+ source: paths.source,
297
+ sourceSha256: sha256(canonicalize(source)),
298
+ artifact: paths.artifact,
299
+ artifactSha256: sha256(canonical),
300
+ vars,
301
+ typeglish: opts.version,
302
+ checkMode,
303
+ report: { ok: rep.ok, counts: rep.counts, strict: opts.strict === true },
304
+ builtAt: opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString()
305
+ };
306
+ return {
307
+ ok: true,
308
+ report: rep,
309
+ checkMode,
310
+ artifact: `${canonical}
311
+ `,
312
+ entry,
313
+ ...opts.bundle === true ? { agentJson: `${JSON.stringify(compile(source, compileOpts).agent, null, 2)}
314
+ ` } : {}
315
+ };
316
+ }
317
+ function upsertManifest(existing, entry) {
318
+ const builds = (existing?.builds ?? []).filter((b) => b.artifact !== entry.artifact);
319
+ builds.push(entry);
320
+ builds.sort((a, b) => a.artifact < b.artifact ? -1 : a.artifact > b.artifact ? 1 : 0);
321
+ return { version: 1, builds };
322
+ }
323
+ function parseManifest(text) {
324
+ if (text === null) return null;
325
+ try {
326
+ const parsed = JSON.parse(text);
327
+ if (parsed.version !== 1 || !Array.isArray(parsed.builds)) return null;
328
+ return parsed;
329
+ } catch {
330
+ return null;
331
+ }
332
+ }
333
+ function findUp(fileName, fromDir) {
334
+ for (let cur = fromDir, prev = ""; cur !== prev; prev = cur, cur = dirname(cur)) {
335
+ const candidate = join(cur, fileName);
336
+ if (existsSync(candidate)) return candidate;
337
+ }
338
+ return null;
339
+ }
340
+ function loadGlishConfig(fromDir) {
341
+ const path = findUp("glish.tgc", fromDir);
342
+ if (path === null) return void 0;
343
+ try {
344
+ return readFileSync(path, "utf8");
345
+ } catch {
346
+ return void 0;
347
+ }
348
+ }
349
+
350
+ // src/core/score.ts
351
+ var FACET_WEIGHTS = {
352
+ enforceability: 0.25,
353
+ density: 0.2,
354
+ consistency: 0.2,
355
+ structure: 0.15,
356
+ annotation: 0.15,
357
+ style: 0.1,
358
+ security: 0.1
359
+ };
360
+ var SEV_POINTS = { info: 0.25, warn: 1, error: 2 };
361
+ var RATE_SCALE = 3;
362
+ function toGrade(n) {
363
+ if (n >= 90) return "A";
364
+ if (n >= 80) return "B";
365
+ if (n >= 65) return "C";
366
+ if (n >= 50) return "D";
367
+ return "F";
368
+ }
369
+ function approxTokens(text) {
370
+ const words = text.split(/\s+/).filter(Boolean).length;
371
+ return Math.round(words * 1.33);
372
+ }
373
+ function facetOf(source) {
374
+ if (source.startsWith("logic/") || source.startsWith("clarity/")) return "consistency";
375
+ if (source.startsWith("structure/") || source.startsWith("typeglish/")) return "structure";
376
+ if (source.startsWith("security/")) return "security";
377
+ if (source.startsWith("style/") || source.startsWith("prompt/") || source.startsWith("spelling/") || source.startsWith("grammar/"))
378
+ return "style";
379
+ return null;
380
+ }
381
+ var clamp01 = (x) => Math.max(0, Math.min(1, x));
382
+ function scorePrompt(source, inputs) {
383
+ const rawLines = source.replace(/\r\n/g, "\n").split("\n");
384
+ const render = renderLines(rawLines);
385
+ const structure = inputs.structure ?? buildStructure(rawLines);
386
+ const toolMask = toolBlockMask(rawLines);
387
+ const exampleMask = examplesMask(rawLines);
388
+ const ir = inputs.ir ?? lower(rawLines.map((l, i) => toolMask[i] || exampleMask[i] ? "" : l).join("\n"), { tools: structure.index.tools });
389
+ const diagnostics = inputs.diagnostics;
390
+ const ordinals = lineOrdinals(rawLines);
391
+ const dispL = (i) => displayLine(ordinals, i);
392
+ const cfgMask = configSectionMask(rawLines);
393
+ const contentLines = [];
394
+ rawLines.forEach((raw, i) => {
395
+ const r = render[i];
396
+ if (!r || r.kind !== "content" || r.lineClass === "md-code" || r.lineClass === "md-heading") return;
397
+ if (structure.importLines.has(i) || structure.configLines.has(i) || structure.testLines.has(i) || toolMask[i] || cfgMask[i]) return;
398
+ if (isAnnotationLine(raw) || TAG_ONLY_LINE.test(raw)) return;
399
+ contentLines.push(i);
400
+ });
401
+ const statements = Math.max(1, contentLines.length);
402
+ const gateReasons = diagnostics.filter(
403
+ (d) => d.severity === "error" && (d.source.startsWith("logic/") || d.source.startsWith("structure/") || d.source === "security/leaked-secret")
404
+ ).map((d) => `L${dispL(d.line)} ${d.source}: ${d.message}`);
405
+ const gate = { pass: gateReasons.length === 0, reasons: gateReasons };
406
+ const byFacet = /* @__PURE__ */ new Map();
407
+ for (const d of diagnostics) {
408
+ const facet = facetOf(d.source);
409
+ if (!facet) continue;
410
+ const list = byFacet.get(facet) ?? [];
411
+ list.push({ source: d.source, line: d.line, start: d.start, end: d.end, points: SEV_POINTS[d.severity], message: d.message, fix: d.fix });
412
+ byFacet.set(facet, list);
413
+ }
414
+ const deductionFacet = (key) => {
415
+ const deductions = byFacet.get(key) ?? [];
416
+ const rate = deductions.reduce((s, x) => s + x.points, 0) / statements;
417
+ return { key, score: Math.round(100 * (1 - clamp01(rate * RATE_SCALE))), weight: 0, deductions };
418
+ };
419
+ const deontic = ir.nodes.filter((n) => n.kind === "directive");
420
+ const hardRules = deontic.filter((n) => n.deontic === "obligate" || n.deontic === "prohibit");
421
+ const irLines = new Set(deontic.map((n) => n.line));
422
+ const unmeasurable = diagnostics.filter((d) => d.source === "prompt/unmeasurable");
423
+ const exempt = proseExemptLines(rawLines);
424
+ const candidateLines = /* @__PURE__ */ new Set([...irLines, ...unmeasurable.map((d) => d.line)]);
425
+ for (const i of contentLines) {
426
+ if (exempt.has(i)) continue;
427
+ if (exampleMask[i]) continue;
428
+ if (parseLine(rawLines[i] ?? "").list !== "none") candidateLines.add(i);
429
+ }
430
+ let enforceability = null;
431
+ if (candidateLines.size > 0) {
432
+ const irVisible = irLines.size / candidateLines.size;
433
+ const proofGrade = hardRules.length === 0 ? 1 : hardRules.filter((n) => n.predicate?.confidence === "lexicon" || n.numeric !== null).length / hardRules.length;
434
+ const measurable = 1 - unmeasurable.length / Math.max(1, hardRules.length + unmeasurable.length);
435
+ const score = Math.round(100 * clamp01(0.5 * irVisible + 0.3 * proofGrade + 0.2 * measurable));
436
+ const measures = {
437
+ rules: deontic.length,
438
+ hardRules: hardRules.length,
439
+ irVisibleRate: irVisible,
440
+ proofGradeRate: proofGrade,
441
+ measurableRate: measurable
442
+ };
443
+ const suite = extractSuite(source);
444
+ if (suite.testCases.length > 0) measures.coveredRate = ruleCoverage(ir, suite).coveredRate;
445
+ enforceability = { key: "enforceability", score, weight: 0, deductions: [], measures };
446
+ }
447
+ const chainBlocks = parseSwitches(rawLines).filter((b) => b.error === null);
448
+ const laterArmLines = new Set(chainBlocks.flatMap((b) => (b.form === "block" ? b.arms : b.arms.slice(1)).map((a) => a.headerLine)));
449
+ const eligible = contentLines.filter((i) => !exampleMask[i] && !laterArmLines.has(i));
450
+ let annotation = null;
451
+ if (eligible.length > 0) {
452
+ const isAnnotated = (line) => {
453
+ if (hasTrailingAnnotation(rawLines[line] ?? "")) return true;
454
+ for (let j = line - 1; j >= 0; j -= 1) {
455
+ const raw = rawLines[j] ?? "";
456
+ if (isAnnotationLine(raw)) return true;
457
+ if (raw.trim() === "" || /^\s*\/\//.test(raw)) continue;
458
+ return false;
459
+ }
460
+ return false;
461
+ };
462
+ const annotated = eligible.filter(isAnnotated).length;
463
+ const annotatedRate = annotated / eligible.length;
464
+ annotation = {
465
+ key: "annotation",
466
+ score: Math.round(100 * annotatedRate),
467
+ weight: 0,
468
+ deductions: [],
469
+ measures: { eligibleStatements: eligible.length, annotatedStatements: annotated, annotatedRate }
470
+ };
471
+ }
472
+ let density = null;
473
+ if (inputs.semantic) {
474
+ const lines = inputs.semantic.lines;
475
+ if (lines.length > 0) {
476
+ const residualMean = lines.reduce((s, l) => s + l.residual, 0) / lines.length;
477
+ const removableRate = lines.filter((l) => l.proposedRemovable).length / lines.length;
478
+ const score = Math.round(100 * clamp01(0.6 * residualMean + 0.4 * (1 - removableRate)));
479
+ density = {
480
+ key: "density",
481
+ score,
482
+ weight: 0,
483
+ advisory: true,
484
+ deductions: [],
485
+ measures: { residualMean, removableRate, linesAudited: lines.length }
486
+ };
487
+ }
488
+ }
489
+ const facets = [
490
+ ...enforceability ? [enforceability] : [],
491
+ deductionFacet("consistency"),
492
+ deductionFacet("structure"),
493
+ ...annotation ? [annotation] : [],
494
+ deductionFacet("style"),
495
+ deductionFacet("security"),
496
+ ...density ? [density] : []
497
+ ];
498
+ const totalWeight = facets.reduce((s, f) => s + FACET_WEIGHTS[f.key], 0);
499
+ let number = 0;
500
+ for (const f of facets) {
501
+ f.weight = totalWeight === 0 ? 0 : FACET_WEIGHTS[f.key] / totalWeight;
502
+ number += f.weight * f.score;
503
+ }
504
+ number = Math.round(number);
505
+ const ledger = facets.flatMap((f) => f.deductions).sort((a, b) => b.points - a.points || a.line - b.line);
506
+ const perSection = structure.sections.map((s) => {
507
+ const inRange = (line) => line >= s.startLine && line < s.endLine;
508
+ const secDeductions = ledger.filter((d) => inRange(d.line));
509
+ const secStatements = Math.max(1, contentLines.filter(inRange).length);
510
+ const rate = secDeductions.reduce((sum, d) => sum + d.points, 0) / secStatements;
511
+ return {
512
+ tag: s.tag,
513
+ name: s.name,
514
+ startLine: s.startLine,
515
+ endLine: s.endLine,
516
+ score: Math.round(100 * (1 - clamp01(rate * RATE_SCALE))),
517
+ deductions: secDeductions.length
518
+ };
519
+ });
520
+ return {
521
+ grade: gate.pass ? toGrade(number) : "F",
522
+ number,
523
+ gate,
524
+ facets,
525
+ ledger,
526
+ statements,
527
+ perSection,
528
+ tokens: approxTokens(rawLines.join(" "))
529
+ };
530
+ }
531
+
532
+ // src/lib/score.ts
533
+ var GRADE_FLOOR = { A: 90, B: 80, C: 65, D: 50, F: 0 };
534
+ function parseMin(v) {
535
+ const upper = v.toUpperCase();
536
+ if (/^[ABCDF]$/.test(upper)) return GRADE_FLOOR[upper];
537
+ const n = Number.parseInt(v, 10);
538
+ if (Number.isFinite(n)) return n;
539
+ throw new Error(`--min expects a grade (A\u2013F) or a number, got "${v}"`);
540
+ }
541
+ async function scoreSource(source, opts = {}) {
542
+ let isWord;
543
+ let spell = false;
544
+ try {
545
+ const { loadDictionary } = await import('./spell-node.js');
546
+ isWord = (await loadDictionary()).isWord;
547
+ spell = true;
548
+ } catch {
549
+ }
550
+ const diagnostics = runDiagnostics(source.split("\n"), [], { isWord, glishConfig: opts.glishConfig });
551
+ let checkNumeric;
552
+ try {
553
+ checkNumeric = (await import('./z3-MWU3GSVT.js')).checkNumeric;
554
+ } catch {
555
+ }
556
+ let z3 = false;
557
+ if (checkNumeric !== void 0) {
558
+ diagnostics.push(...numericDiagnostics(await checkNumeric(source), source));
559
+ z3 = true;
560
+ }
561
+ return { card: scorePrompt(source, { diagnostics }), tiers: { z3, spell } };
562
+ }
563
+
564
+ export { DEFAULT_OUT_DIR, DIAGNOSTIC_CODES, MANIFEST_FILE, artifactPathFor, blocks, buildPrompt, canonicalize, checkSource, findUp, loadGlishConfig, parseManifest, parseMin, renderAgentReference, scoreSource, sha256, sourceKey, toPosix, upsertManifest };
565
+ //# sourceMappingURL=chunk-3NQ3VYO6.js.map
566
+ //# sourceMappingURL=chunk-3NQ3VYO6.js.map