@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/CHANGELOG.md +39 -0
- package/dist/core.cjs +217 -10
- package/dist/index.cjs +409 -11
- package/package.json +1 -1
- package/src/cli.mjs +77 -69
- package/src/codegen.mjs +73 -4
- package/src/core.d.ts +1 -1
- package/src/core.mjs +1 -1
- package/src/emit.mjs +1 -1
- package/src/exporters.mjs +14 -5
- package/src/index.d.ts +1 -0
- package/src/index.mjs +1 -1
- package/src/lift.mjs +79 -3
- package/src/mcp.mjs +125 -1
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.
|
|
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.
|
|
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.
|
|
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",
|