@skillstech/thunderlang 0.2.0 → 0.4.1

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/index.cjs CHANGED
@@ -294,6 +294,7 @@ __export(index_exports, {
294
294
  toMermaid: () => toMermaid,
295
295
  toOpenAPI: () => toOpenAPI,
296
296
  toPlaywright: () => toPlaywright,
297
+ toPython: () => toPython,
297
298
  toSMV: () => toSMV,
298
299
  toSarif: () => toSarif,
299
300
  toTypeScript: () => toTypeScript,
@@ -2287,7 +2288,7 @@ function notesSummary(ast) {
2287
2288
  byLens
2288
2289
  };
2289
2290
  }
2290
- var COMPILER_VERSION = "0.2.0";
2291
+ var COMPILER_VERSION = "0.4.1";
2291
2292
  var PROOF_SCHEMA_VERSION = "0.1.0";
2292
2293
  var SOURCE_PRODUCT = "skillstech-compiler";
2293
2294
  function buildContractGraph(ast, generatedAt) {
@@ -3753,6 +3754,112 @@ function extractFactsRuby(source, file = "input.rb") {
3753
3754
  }
3754
3755
  return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "ruby", sourceRoot: file, functions, tests, errors };
3755
3756
  }
3757
+ function parseNameColonTypeParams(raw) {
3758
+ return splitTopLevel(raw, ",").map((p) => p.trim()).filter(Boolean).map((p) => {
3759
+ const cleaned = p.replace(/@\w+(\([^)]*\))?/g, "").replace(/^(?:val|var|vararg|implicit|final|lazy)\s+/, "").replace(/=.*$/, "").trim();
3760
+ const mm = cleaned.match(/^([A-Za-z_]\w*)\s*:\s*(.+)$/);
3761
+ if (mm) return { name: mm[1], type: mm[2].trim() };
3762
+ return { name: cleaned.replace(/[^\w].*$/, "") || cleaned, type: null };
3763
+ });
3764
+ }
3765
+ function extractFactsKotlin(source, file = "input.kt") {
3766
+ let m;
3767
+ const functions = [];
3768
+ const tests = [];
3769
+ const seen = /* @__PURE__ */ new Set();
3770
+ const testMethods = /* @__PURE__ */ new Set();
3771
+ const ta = /@Test\b[\s\S]{0,120}?\bfun\s+(?:`([^`]+)`|(\w+))\s*\(/g;
3772
+ while (m = ta.exec(source)) testMethods.add(m[1] || m[2]);
3773
+ const fnRe = /\bfun\s+(?:<[^>]*>\s*)?(?:[A-Za-z_][\w.]*\.)?(?:`([^`]+)`|([A-Za-z_]\w*))\s*\(([^)]*)\)\s*(?::\s*([^{=\n]+))?/g;
3774
+ while (m = fnRe.exec(source)) {
3775
+ const name = m[1] || m[2];
3776
+ if (testMethods.has(name)) {
3777
+ if (!tests.some((t) => t.name === name)) tests.push({ name, file, line: lineOf(source, m.index) });
3778
+ continue;
3779
+ }
3780
+ if (seen.has(name)) continue;
3781
+ seen.add(name);
3782
+ functions.push({ name, file, line: lineOf(source, m.index), parameters: parseNameColonTypeParams(m[3] || ""), returnType: m[4] ? m[4].trim() : null, evidence: [{ kind: "fun", file, line: lineOf(source, m.index) }] });
3783
+ }
3784
+ const errors = [];
3785
+ const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
3786
+ let mm;
3787
+ const ce = /class\s+(\w*(?:Exception|Error))\b/g;
3788
+ while (mm = ce.exec(source)) addErr(mm[1], mm.index);
3789
+ const th = /throw\s+(\w+)\s*\(/g;
3790
+ while (mm = th.exec(source)) addErr(mm[1], mm.index);
3791
+ return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "kotlin", sourceRoot: file, functions, tests, errors };
3792
+ }
3793
+ function extractFactsScala(source, file = "input.scala") {
3794
+ let m;
3795
+ const functions = [];
3796
+ const tests = [];
3797
+ const seen = /* @__PURE__ */ new Set();
3798
+ const fnRe = /\bdef\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*\(([^)]*)\)\s*(?::\s*([^={\n]+))?/g;
3799
+ while (m = fnRe.exec(source)) {
3800
+ const name = m[1];
3801
+ if (seen.has(name)) continue;
3802
+ seen.add(name);
3803
+ functions.push({ name, file, line: lineOf(source, m.index), parameters: parseNameColonTypeParams(m[2] || ""), returnType: m[3] ? m[3].trim() : null, evidence: [{ kind: "def", file, line: lineOf(source, m.index) }] });
3804
+ }
3805
+ const seenT = /* @__PURE__ */ new Set();
3806
+ const addTest = (n, idx) => {
3807
+ const k = String(n).toLowerCase();
3808
+ if (n && !seenT.has(k)) {
3809
+ seenT.add(k);
3810
+ tests.push({ name: n, file, line: lineOf(source, idx) });
3811
+ }
3812
+ };
3813
+ const tr = /\btest\s*\(\s*"([^"]+)"/g;
3814
+ while (m = tr.exec(source)) addTest(m[1], m.index);
3815
+ const inRe = /"([^"]+)"\s+(?:in|should|must)\b/g;
3816
+ while (m = inRe.exec(source)) addTest(m[1], m.index);
3817
+ const errors = [];
3818
+ const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
3819
+ let mm;
3820
+ const ce = /class\s+(\w*(?:Exception|Error))\b/g;
3821
+ while (mm = ce.exec(source)) addErr(mm[1], mm.index);
3822
+ const th = /throw\s+new\s+(\w+)\s*\(/g;
3823
+ while (mm = th.exec(source)) addErr(mm[1], mm.index);
3824
+ return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "scala", sourceRoot: file, functions, tests, errors };
3825
+ }
3826
+ function extractFactsElixir(source, file = "input.ex") {
3827
+ let m;
3828
+ const functions = [];
3829
+ const tests = [];
3830
+ const seen = /* @__PURE__ */ new Set();
3831
+ const seenT = /* @__PURE__ */ new Set();
3832
+ const addTest = (n, idx) => {
3833
+ const k = String(n).toLowerCase();
3834
+ if (n && !seenT.has(k)) {
3835
+ seenT.add(k);
3836
+ tests.push({ name: n, file, line: lineOf(source, idx) });
3837
+ }
3838
+ };
3839
+ const tr = /\btest\s+"([^"]+)"/g;
3840
+ while (m = tr.exec(source)) addTest(m[1], m.index);
3841
+ const fnRe = /^[ \t]*defp?\s+([a-z_]\w*[!?]?)\s*(?:\(([^)]*)\))?/gm;
3842
+ while (m = fnRe.exec(source)) {
3843
+ const name = m[1];
3844
+ if (seen.has(name)) continue;
3845
+ seen.add(name);
3846
+ const parameters = splitTopLevel(m[2] || "", ",").map((p) => p.trim()).filter(Boolean).map((p) => ({ name: p.split(/\\\\/)[0].trim().replace(/^%\{?/, "").replace(/[^\w].*$/, "") || p, type: null }));
3847
+ functions.push({ name, file, line: lineOf(source, m.index), indent: (m[0].match(/^[ \t]*/) || [""])[0].length, parameters, returnType: null, evidence: [{ kind: "def", file, line: lineOf(source, m.index) }] });
3848
+ }
3849
+ const errors = [];
3850
+ const seenErr = /* @__PURE__ */ new Set();
3851
+ const addErr = (n, idx) => {
3852
+ if (n && !seenErr.has(n)) {
3853
+ seenErr.add(n);
3854
+ errors.push({ name: n, file, line: lineOf(source, idx) });
3855
+ }
3856
+ };
3857
+ const modErr = /defmodule\s+([\w.]*(?:Error|Exception))\b/g;
3858
+ while (m = modErr.exec(source)) addErr(m[1].split(".").pop(), m.index);
3859
+ const raiseRe = /raise\s+([A-Z][\w.]*)/g;
3860
+ while (m = raiseRe.exec(source)) addErr(m[1].split(".").pop(), m.index);
3861
+ return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "elixir", sourceRoot: file, functions, tests, errors };
3862
+ }
3756
3863
  var ADAPTERS = {
3757
3864
  typescript: extractFactsTypeScript,
3758
3865
  ts: extractFactsTypeScript,
@@ -3776,10 +3883,18 @@ var ADAPTERS = {
3776
3883
  cc: extractFactsCpp,
3777
3884
  php: extractFactsPhp,
3778
3885
  ruby: extractFactsRuby,
3779
- rb: extractFactsRuby
3886
+ rb: extractFactsRuby,
3887
+ kotlin: extractFactsKotlin,
3888
+ kt: extractFactsKotlin,
3889
+ kts: extractFactsKotlin,
3890
+ scala: extractFactsScala,
3891
+ sc: extractFactsScala,
3892
+ elixir: extractFactsElixir,
3893
+ ex: extractFactsElixir,
3894
+ exs: extractFactsElixir
3780
3895
  };
3781
- var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl"];
3782
- var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php"]);
3896
+ var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl", "kotlin", "scala", "elixir"];
3897
+ var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php", "elixir"]);
3783
3898
  var LANG_DISPLAY = {
3784
3899
  typescript: "TypeScript",
3785
3900
  javascript: "JavaScript",
@@ -3791,7 +3906,10 @@ var LANG_DISPLAY = {
3791
3906
  cpp: "C++",
3792
3907
  php: "PHP",
3793
3908
  ruby: "Ruby",
3794
- perl: "Perl"
3909
+ perl: "Perl",
3910
+ kotlin: "Kotlin",
3911
+ scala: "Scala",
3912
+ elixir: "Elixir"
3795
3913
  };
3796
3914
  function unwrapReturn(ret) {
3797
3915
  if (!ret) return { output: null, error: null };
@@ -3915,6 +4033,9 @@ function languageForFile(file) {
3915
4033
  if (/\.(cpp|cc|cxx|hpp|hh|c|h)$/i.test(file)) return "cpp";
3916
4034
  if (/\.php$/i.test(file)) return "php";
3917
4035
  if (/\.rb$/i.test(file)) return "ruby";
4036
+ if (/\.kts?$/i.test(file)) return "kotlin";
4037
+ if (/\.(scala|sc)$/i.test(file)) return "scala";
4038
+ if (/\.exs?$/i.test(file)) return "elixir";
3918
4039
  if (/\.(mjs|cjs|jsx?)$/i.test(file)) return "javascript";
3919
4040
  return "typescript";
3920
4041
  }
@@ -3922,7 +4043,7 @@ function isPublicFn(fn, language) {
3922
4043
  const name = fn.name || "";
3923
4044
  if (/(?:Internal|Impl|_impl|_helper|_test|Helper)$/.test(name)) return false;
3924
4045
  if (language === "go" || language === "golang") return /^[A-Z]/.test(name) && name !== "Test";
3925
- if (language === "python" || language === "ruby") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
4046
+ if (language === "python" || language === "ruby" || language === "elixir") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
3926
4047
  return !name.startsWith("_") && name !== "init" && name !== "constructor";
3927
4048
  }
3928
4049
  function liftAll(source, { language = "typescript", file = "", publicOnly = true } = {}) {
@@ -3997,7 +4118,10 @@ var LANG_EXT = {
3997
4118
  cpp: "cpp",
3998
4119
  php: "php",
3999
4120
  ruby: "rb",
4000
- perl: "pl"
4121
+ perl: "pl",
4122
+ kotlin: "kt",
4123
+ scala: "scala",
4124
+ elixir: "ex"
4001
4125
  };
4002
4126
  function liftSource(source, { language = "typescript", file = "", seeds = void 0 } = {}) {
4003
4127
  const key = String(language).toLowerCase();
@@ -5024,7 +5148,9 @@ function toPlaywright(ast) {
5024
5148
  const exps = ast.experiences || [];
5025
5149
  const L = [];
5026
5150
  L.push("// Playwright test scaffold generated from experience intent by @skillstech/thunderlang.");
5027
- L.push("// SKELETON: fill in selectors and assertions. It is not a passing test until you do.");
5151
+ L.push("// SKELETON: fill in selectors and assertions. Every test starts with a test.fixme");
5152
+ L.push('// guard so an unimplemented scaffold reports "fixme" instead of passing vacuously;');
5153
+ L.push("// remove the guard from each test once its body is real.");
5028
5154
  L.push("import { test, expect } from '@playwright/test';");
5029
5155
  L.push("");
5030
5156
  if (!exps.length) {
@@ -5039,6 +5165,7 @@ function toPlaywright(ast) {
5039
5165
  }
5040
5166
  for (const j of exp.journeys || []) {
5041
5167
  L.push(` test(${jsStr(j.name || "journey")}, async ({ page }) => {`);
5168
+ L.push(` test.fixme(true, ${jsStr("scaffold: implement the steps, then remove this guard")});`);
5042
5169
  if (!(j.steps || []).length) L.push(" // TODO: no steps declared for this journey");
5043
5170
  for (const step of j.steps || []) {
5044
5171
  L.push(` await test.step(${jsStr(step)}, async () => {`);
@@ -5050,10 +5177,12 @@ function toPlaywright(ast) {
5050
5177
  for (const st of exp.states || []) {
5051
5178
  if (st.hasRecovery) {
5052
5179
  L.push(` test(${jsStr(`failure state "${st.name}" offers a recovery path`)}, async ({ page }) => {`);
5180
+ L.push(` test.fixme(true, ${jsStr("scaffold: implement this state check, then remove this guard")});`);
5053
5181
  L.push(` // TODO: drive the UI into "${st.name}", assert a recovery action is available`);
5054
5182
  L.push(" });");
5055
5183
  } else {
5056
5184
  L.push(` test(${jsStr(`reaches state: ${st.name}`)}, async ({ page }) => {`);
5185
+ L.push(` test.fixme(true, ${jsStr("scaffold: implement this state check, then remove this guard")});`);
5057
5186
  L.push(` // TODO: drive the UI to "${st.name}" and assert it is shown`);
5058
5187
  L.push(" });");
5059
5188
  }
@@ -6228,6 +6357,7 @@ function exprToCode(src, { inputs = [], dialect = "js" } = {}) {
6228
6357
  var exprToJs = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "js" });
6229
6358
  var exprToCSharp = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "csharp" });
6230
6359
  var exprToJava = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "java" });
6360
+ var exprToPython = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "python" });
6231
6361
 
6232
6362
  // src/runtime.mjs
6233
6363
  var RUNTIME_SCHEMA = "intent-runtime-v1";
@@ -7725,7 +7855,7 @@ function toCSharp(ast) {
7725
7855
  try {
7726
7856
  cond = exprToCSharp(r.when, { inputs });
7727
7857
  } catch {
7728
- cond = `false /* TODO: "${r.when}" */`;
7858
+ cond = `false /* TODO: could not translate "${r.when}" */`;
7729
7859
  }
7730
7860
  L.push(` if (${cond}) return ${JSON.stringify(r.result)}; // rule ${r.name}`);
7731
7861
  }
@@ -7734,6 +7864,7 @@ function toCSharp(ast) {
7734
7864
  const inName = (ast.inputs || []).length ? `${subject}Input` : null;
7735
7865
  const outName = (ast.outputs || []).length ? `${subject}Output` : "void";
7736
7866
  L.push(` public static ${outName} Run(${inName ? `${inName} input` : ""})`, " {");
7867
+ for (const r of ast.requires || []) L.push(` // precondition: ${r} , TODO: validate`);
7737
7868
  for (const n of ast.neverRules || []) L.push(` // NEVER: ${n.statement}`);
7738
7869
  for (const g of ast.guarantees || []) L.push(` // GUARANTEE: ${g.statement}`);
7739
7870
  L.push(' throw new NotImplementedException("TODO: implement , the intent above defines what this must do.");', " }");
@@ -7794,7 +7925,7 @@ function toJava(ast) {
7794
7925
  try {
7795
7926
  cond = exprToJava(r.when, { inputs });
7796
7927
  } catch {
7797
- cond = `false /* TODO: "${r.when}" */`;
7928
+ cond = `false /* TODO: could not translate "${r.when}" */`;
7798
7929
  }
7799
7930
  L.push(` if (${cond}) return ${JSON.stringify(r.result)}; // rule ${r.name}`);
7800
7931
  }
@@ -7803,18 +7934,98 @@ function toJava(ast) {
7803
7934
  const inName = (ast.inputs || []).length ? `${subject}Input` : null;
7804
7935
  const outName = (ast.outputs || []).length ? `${subject}Output` : "void";
7805
7936
  L.push(` public static ${outName} run(${inName ? `${inName} input` : ""}) {`);
7937
+ for (const r of ast.requires || []) L.push(` // precondition: ${r} , TODO: validate`);
7806
7938
  for (const n of ast.neverRules || []) L.push(` // NEVER: ${n.statement}`);
7807
7939
  for (const g of ast.guarantees || []) L.push(` // GUARANTEE: ${g.statement}`);
7808
7940
  L.push(' throw new UnsupportedOperationException("TODO: implement , the intent above defines what this must do.");', " }");
7809
7941
  L.push("}", "");
7810
7942
  return L.join("\n");
7811
7943
  }
7944
+ var PY_TYPES = {
7945
+ Email: "str",
7946
+ Url: "str",
7947
+ UserId: "str",
7948
+ AccountId: "str",
7949
+ OrderId: "str",
7950
+ InvoiceId: "str",
7951
+ CustomerId: "str",
7952
+ Secret: "str",
7953
+ Token: "str",
7954
+ Jwt: "str",
7955
+ IdempotencyKey: "str",
7956
+ Currency: "str",
7957
+ Date: "str",
7958
+ DateTime: "str",
7959
+ Money: "float",
7960
+ Percentage: "float",
7961
+ Count: "int",
7962
+ Duration: "int",
7963
+ Flag: "bool"
7964
+ };
7965
+ function pyType(type, domain) {
7966
+ if (!type) return "object";
7967
+ const list = /^List<(.+)>$/.exec(type);
7968
+ if (list) return `list[${pyType(list[1], domain)}]`;
7969
+ const base = type.replace(/\(.*\)/, "");
7970
+ if (PY_TYPES[base]) return PY_TYPES[base];
7971
+ domain.add(base);
7972
+ return base;
7973
+ }
7974
+ var snake = (s) => pascal2(s).replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
7975
+ function toPython(ast) {
7976
+ const subject = pascal2(subjectName(ast) || "Intent");
7977
+ const domain = /* @__PURE__ */ new Set();
7978
+ const L = [
7979
+ `# ${subject} , generated from ThunderLang (intent-codegen-v1). Deterministic, no AI.`,
7980
+ "# The dataclass contract and the decision logic are fully determined by the intent;",
7981
+ "# business logic marked TODO is yours, bound by the guarantees and never-rules below.",
7982
+ ""
7983
+ ];
7984
+ const hasData = (ast.inputs || []).length || (ast.outputs || []).length;
7985
+ if (hasData) L.push("from __future__ import annotations", "from dataclasses import dataclass", "");
7986
+ const cls = (name, fields) => fields.length ? ["@dataclass", `class ${name}:`, ...fields.map((f) => ` ${f.name}: ${pyType(f.type, domain)}`), ""] : [];
7987
+ if ((ast.inputs || []).length) L.push(...cls(`${subject}Input`, ast.inputs));
7988
+ if ((ast.outputs || []).length) L.push(...cls(`${subject}Output`, ast.outputs));
7989
+ for (const d of ast.decisions || []) {
7990
+ const inputs = d.inputs || [];
7991
+ L.push(`# decision ${d.name} , first matching rule wins.`);
7992
+ L.push(`def ${snake(d.name)}(${inputs.join(", ")}) -> str:`);
7993
+ for (const r of d.rules || []) {
7994
+ let cond, note = "";
7995
+ try {
7996
+ cond = exprToPython(r.when, { inputs });
7997
+ } catch {
7998
+ cond = "False";
7999
+ note = ` # TODO: could not translate "${r.when}"`;
8000
+ }
8001
+ L.push(` if ${cond}:${note}`);
8002
+ L.push(` return ${JSON.stringify(r.result)} # rule ${r.name}`);
8003
+ }
8004
+ L.push(` return ${JSON.stringify(d.default ?? "Undecided")} # default`, "");
8005
+ }
8006
+ const inName = (ast.inputs || []).length ? `${subject}Input` : null;
8007
+ const outName = (ast.outputs || []).length ? `${subject}Output` : "None";
8008
+ L.push(`def run(${inName ? `input: ${inName}` : ""}) -> ${outName}:`);
8009
+ for (const r of ast.requires || []) L.push(` # precondition: ${r} , TODO: validate`);
8010
+ for (const n of ast.neverRules || []) L.push(` # NEVER: ${n.statement}`);
8011
+ for (const g of ast.guarantees || []) L.push(` # GUARANTEE: ${g.statement}`);
8012
+ L.push(' raise NotImplementedError("TODO: implement , the intent above defines what this must do.")', "");
8013
+ const stubs = [...domain].filter((t) => !PY_TYPES[t] && /^[A-Z]/.test(t)).sort();
8014
+ if (stubs.length) {
8015
+ L.push("# Domain types referenced by the intent , complete these.");
8016
+ for (const t of stubs) L.push(`class ${t}: pass # TODO: fields`);
8017
+ L.push("");
8018
+ }
8019
+ return L.join("\n");
8020
+ }
7812
8021
  var GENERATORS = {
7813
8022
  typescript: toTypeScript,
7814
8023
  ts: toTypeScript,
7815
8024
  csharp: toCSharp,
7816
8025
  cs: toCSharp,
7817
- java: toJava
8026
+ java: toJava,
8027
+ python: toPython,
8028
+ py: toPython
7818
8029
  };
7819
8030
 
7820
8031
  // src/changes.mjs
@@ -8404,6 +8615,54 @@ function verifyDiff(intentText, { before = null, after, language = "typescript"
8404
8615
  };
8405
8616
  }
8406
8617
 
8618
+ // src/mcp.mjs
8619
+ var import_node_child_process = require("node:child_process");
8620
+ var import_node_fs = require("node:fs");
8621
+ var import_node_path = require("node:path");
8622
+
8623
+ // src/conformance.mjs
8624
+ function buildConformance(ast, { targets = [], results = null } = {}) {
8625
+ const sem = runTests(ast);
8626
+ const cases = (sem.results || []).map((r) => ({
8627
+ key: `${r.target} / ${r.case}`,
8628
+ test: r.target,
8629
+ case: r.case,
8630
+ inputs: r.inputs || {},
8631
+ expected: r.expected,
8632
+ semantic: r.actual,
8633
+ semanticPass: !!r.pass
8634
+ }));
8635
+ const columns = (targets.length ? targets : ast.targets || []).map((t) => String(t).toLowerCase());
8636
+ const rows = cases.map((c) => {
8637
+ const t = {};
8638
+ for (const col of columns) {
8639
+ const tr = results && results[col];
8640
+ if (!tr || !(c.key in tr)) {
8641
+ t[col] = { status: "declared" };
8642
+ continue;
8643
+ }
8644
+ const actual = tr[c.key];
8645
+ t[col] = { status: String(actual) === String(c.expected) ? "pass" : "fail", actual };
8646
+ }
8647
+ return { ...c, targets: t };
8648
+ });
8649
+ const failures = [];
8650
+ for (const row of rows) for (const col of columns) {
8651
+ const tr = row.targets[col];
8652
+ if (tr.status === "fail") failures.push({ target: col, case: row.key, expected: row.expected, actual: tr.actual });
8653
+ }
8654
+ return {
8655
+ schema: "thunder-conformance-v1",
8656
+ mission: ast.mission,
8657
+ total: cases.length,
8658
+ columns,
8659
+ semanticFailures: cases.filter((c) => !c.semanticPass).length,
8660
+ graded: !!results,
8661
+ failures,
8662
+ cases: rows
8663
+ };
8664
+ }
8665
+
8407
8666
  // src/draft.mjs
8408
8667
  var DRAFT_SCHEMA = "intent-draft-v1";
8409
8668
  var SECRET_TYPES2 = /* @__PURE__ */ new Set(["secret", "password", "passwd", "jwt", "token", "apikey", "api_key", "privatekey", "private_key", "credential", "cvv"]);
@@ -8495,6 +8754,75 @@ function draftIntent(brief = {}) {
8495
8754
  // src/mcp.mjs
8496
8755
  var PROTOCOL_VERSION = "2024-11-05";
8497
8756
  var str = { type: "string" };
8757
+ function resolveObligations(ast) {
8758
+ const t = runTests(ast);
8759
+ const testPass = /* @__PURE__ */ new Map();
8760
+ for (const res of t.results || []) {
8761
+ const cur = testPass.get(res.target);
8762
+ testPass.set(res.target, cur === void 0 ? !!res.pass : cur && !!res.pass);
8763
+ }
8764
+ const norm4 = (s) => String(s).toLowerCase().replace(/[^a-z0-9]/g, "");
8765
+ const matchTest = (verifyText) => {
8766
+ const v = norm4(verifyText);
8767
+ if (!v) return null;
8768
+ for (const [name, pass] of testPass) {
8769
+ const n = norm4(name);
8770
+ if (n && (n === v || n.includes(v) || v.includes(n))) return { name, pass };
8771
+ }
8772
+ return null;
8773
+ };
8774
+ const build = (kind, o) => {
8775
+ const verify = o.verify || [];
8776
+ let status = "unverified", provenBy = null;
8777
+ if (verify.length) {
8778
+ let m = null;
8779
+ for (const vt of verify) {
8780
+ const x = matchTest(vt);
8781
+ if (x) {
8782
+ m = x;
8783
+ if (!x.pass) break;
8784
+ }
8785
+ }
8786
+ if (m) {
8787
+ status = m.pass ? "verified" : "failed";
8788
+ provenBy = m.name;
8789
+ } else status = "declared";
8790
+ }
8791
+ return { kind, id: o.id, status, provenBy };
8792
+ };
8793
+ const obligations = [...ast.guarantees.map((g) => build("guarantee", g)), ...ast.neverRules.map((n) => build("prohibition", n))];
8794
+ return { obligations, tests: t, failed: obligations.filter((o) => o.status === "failed").length };
8795
+ }
8796
+ var LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "poetry.lock", "Pipfile.lock", "Cargo.lock", "go.sum", "Gemfile.lock", "composer.lock"];
8797
+ function proofFreshness(proof, environment) {
8798
+ let head = null;
8799
+ try {
8800
+ head = (0, import_node_child_process.execSync)("git rev-parse HEAD", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim() || null;
8801
+ } catch {
8802
+ head = null;
8803
+ }
8804
+ let lock = null, dir = process.cwd();
8805
+ for (let i = 0; i < 6 && !lock; i++) {
8806
+ for (const lf of LOCKFILES) {
8807
+ const p = (0, import_node_path.join)(dir, lf);
8808
+ if ((0, import_node_fs.existsSync)(p)) {
8809
+ lock = p;
8810
+ break;
8811
+ }
8812
+ }
8813
+ const parent = (0, import_node_path.dirname)(dir);
8814
+ if (parent === dir) break;
8815
+ dir = parent;
8816
+ }
8817
+ return {
8818
+ intentHash: proof.sourceHash,
8819
+ compilerVersion: proof.compilerVersion,
8820
+ implementation: head,
8821
+ dependencies: lock ? { file: (0, import_node_path.basename)(lock), hash: sha256((0, import_node_fs.readFileSync)(lock, "utf8")) } : null,
8822
+ environment: environment || null,
8823
+ generatedAt: proof.generatedAt
8824
+ };
8825
+ }
8498
8826
  var TOOLS = [
8499
8827
  {
8500
8828
  name: "intent_check",
@@ -8520,6 +8848,75 @@ var TOOLS = [
8520
8848
  },
8521
8849
  run: ({ intent, after, before = null, language = "typescript" }) => verifyDiff(String(intent), { before: before == null ? null : String(before), after: String(after), language })
8522
8850
  },
8851
+ {
8852
+ name: "intent_prove",
8853
+ description: "Emit the durable intent-proof-v1 artifact for .intent source: per-claim verdicts (verified / failed / planned / needs_verification, each bound to the named test that proves it) plus the freshness tuple (intent hash, compiler version, commit, dependency lockfile) that lets `thunder verify` mark the proof STALE later. Mirrors `thunder prove`; an unverified claim never reads as passed. Call it after the gate passes to record WHAT was proven.",
8854
+ inputSchema: {
8855
+ type: "object",
8856
+ required: ["source"],
8857
+ properties: {
8858
+ source: { ...str, description: "the .intent source" },
8859
+ sourceFile: { ...str, description: "file name to record in the proof (default intent.thunder)" },
8860
+ environment: { ...str, description: "environment name to bind into the freshness tuple (optional)" }
8861
+ }
8862
+ },
8863
+ run: ({ source, sourceFile = "intent.thunder", environment }) => {
8864
+ const text = String(source);
8865
+ const ast = parseIntent(text);
8866
+ const diagnostics = semanticDiagnostics(ast);
8867
+ const resolved = resolveObligations(ast);
8868
+ const proof = buildProof(ast, {
8869
+ sourceFile: String(sourceFile),
8870
+ sourceHash: sha256(text),
8871
+ targetsRequested: ast.targets || [],
8872
+ targetsGenerated: [],
8873
+ diagnostics,
8874
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
8875
+ });
8876
+ const CLAIM_MAP = { verified: "verified", failed: "failed", declared: "planned", unverified: "needs_verification" };
8877
+ const byId = new Map(resolved.obligations.map((o) => [o.id, o]));
8878
+ const applyStatus = (claim) => {
8879
+ const o = byId.get(claim.id);
8880
+ return o ? { ...claim, status: CLAIM_MAP[o.status], provenBy: o.provenBy || null } : claim;
8881
+ };
8882
+ proof.guarantees = proof.guarantees.map(applyStatus);
8883
+ proof.neverRules = proof.neverRules.map(applyStatus);
8884
+ proof.freshness = proofFreshness(proof, environment);
8885
+ const ok = proof.verification.semanticPassed && resolved.tests.ok !== false && resolved.failed === 0;
8886
+ return { ok, proofId: `proof-${proof.sourceHash.replace("sha256:", "").slice(0, 6)}`, ...proof, tests: resolved.tests };
8887
+ }
8888
+ },
8889
+ {
8890
+ name: "intent_conform",
8891
+ description: 'Grade cross-target conformance (thunder-conformance-v1): the deterministic engine defines the canonical result every in-file test case must produce, and each target\'s outputs (passed via `results`) are graded against it. Same tests, every implementation , a diverging target case is a CONFORMANCE FAILURE. Mirrors `thunder conform`. Without `results`, targets honestly stay "declared", never "pass".',
8892
+ inputSchema: {
8893
+ type: "object",
8894
+ required: ["source"],
8895
+ properties: {
8896
+ source: { ...str, description: "the .intent source (with test blocks)" },
8897
+ targets: { type: "array", items: str, description: "target languages to grade (default: the targets the intent declares)" },
8898
+ results: { type: "object", description: 'per-target actual outputs to grade: {target: {"Test / case": result}}' }
8899
+ }
8900
+ },
8901
+ run: ({ source, targets = [], results = null }) => {
8902
+ const rep = buildConformance(parseIntent(String(source)), { targets: Array.isArray(targets) ? targets.map(String) : [], results });
8903
+ return { ok: rep.semanticFailures === 0 && rep.failures.length === 0, ...rep };
8904
+ }
8905
+ },
8906
+ {
8907
+ name: "intent_drift",
8908
+ description: "Check whether real code, as it exists TODAY, still satisfies its intent , the standing-guard complement to intent_verify_diff (no diff needed). Re-lifts the code and reports drift findings: guarantees with no matching evidence, never-rules with no guard, declared inputs missing from the signature, and new behavior the intent never declared. Mirrors `thunder drift`.",
8909
+ inputSchema: {
8910
+ type: "object",
8911
+ required: ["intent", "code"],
8912
+ properties: {
8913
+ intent: { ...str, description: "the approved .intent source (the contract)" },
8914
+ code: { ...str, description: "the implementation source as it exists now" },
8915
+ language: { ...str, description: `source language (default typescript). One of: ${SUPPORTED_LANGUAGES.join(", ")}` }
8916
+ }
8917
+ },
8918
+ run: ({ intent, code, language = "typescript" }) => checkDrift(String(intent), String(code), { language })
8919
+ },
8523
8920
  {
8524
8921
  name: "intent_draft",
8525
8922
  description: "Turn a STRUCTURED brief into a rigorous, canonically-formatted ThunderLang draft plus a review checklist of what a human must still fill in (unverified guarantees, unguarded secrets, missing goal). Use this after distilling a user request into structured fields; the draft is a proposal for human approval, never verified.",
@@ -9385,6 +9782,7 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
9385
9782
  toMermaid,
9386
9783
  toOpenAPI,
9387
9784
  toPlaywright,
9785
+ toPython,
9388
9786
  toSMV,
9389
9787
  toSarif,
9390
9788
  toTypeScript,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillstech/thunderlang",
3
- "version": "0.2.0",
3
+ "version": "0.4.1",
4
4
  "description": "Deterministic ThunderLang compiler, CLI, and Language Server. No AI required. Turns .thunder files into the canonical Intent Graph, docs, and a proof, and EXECUTES the intent: run decisions, simulate lifecycles, and check outcome contracts. Interops with DMN, BPMN, JSON Schema, and OpenAPI.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",