@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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,45 @@
|
|
|
3
3
|
All notable changes to `@skillstech/thunderlang`. Pre-1.0: the language and the
|
|
4
4
|
`intent-graph-v1` schema version independently and may still change.
|
|
5
5
|
|
|
6
|
+
## 0.4.1
|
|
7
|
+
|
|
8
|
+
The coherence release. Additive; no breaking changes. Version chosen to align with OpenThunder
|
|
9
|
+
(TL and OT version in lockstep; OT is moving from 0.4.0 to match).
|
|
10
|
+
|
|
11
|
+
- **Python code generation.** `thunder gen <file> --target python` now emits a typed Python 3
|
|
12
|
+
scaffold, aligning the codegen axis with live execution. Also fixes a real gap: C# and Java `gen`
|
|
13
|
+
silently dropped `requires` preconditions that TypeScript emitted; they are now emitted as guided
|
|
14
|
+
TODOs.
|
|
15
|
+
- **Language support matrix.** New `docs/language-support-matrix.md` documents the three axes , lift
|
|
16
|
+
(14 languages), gen, and live target execution (4) , and makes them discoverable.
|
|
17
|
+
- **VS Code extension.** `editors/vscode/` ships a real extension: syntax highlighting and a Language
|
|
18
|
+
Server client that launches `thunder lsp`. Packageable with `vsce package`.
|
|
19
|
+
- **Grouped, scannable CLI help.** `thunder --help` is reorganized into nine labeled groups with a
|
|
20
|
+
one-line mental model. Command behavior is unchanged (help text only); a test guards that no
|
|
21
|
+
command was dropped.
|
|
22
|
+
- **Verify-real-code loop, productized.** New MCP tools `intent_prove`, `intent_conform`, and
|
|
23
|
+
`intent_drift` let AI agents drive the full loop. `docs/verifying-ai-changes.md` is rewritten as
|
|
24
|
+
the headline workflow (author intent, write code, prove conformance, gate the merge) with a CI
|
|
25
|
+
recipe.
|
|
26
|
+
- **Honest Playwright scaffolds.** The exported Playwright tests now open with `test.fixme` so an
|
|
27
|
+
unfilled scaffold cannot pass vacuously.
|
|
28
|
+
- **intent-graph-v1 stability policy.** New `docs/schema-stability.md` commits to additive-only type
|
|
29
|
+
sets within v1, an IR version that moves independently of the package, a deprecation policy, and a
|
|
30
|
+
road to 1.0, so consumers like OpenThunder can depend on the IR safely.
|
|
31
|
+
|
|
32
|
+
## 0.3.0
|
|
33
|
+
|
|
34
|
+
The 14-language lift release. Additive; no breaking changes. Restores the OpenThunder -> ThunderLang
|
|
35
|
+
recover-intent loop for every language OpenThunder's archgraph core can now discover.
|
|
36
|
+
|
|
37
|
+
- **Kotlin, Scala, and Elixir lift adapters.** `thunder lift` now extracts candidate intent from
|
|
38
|
+
`.kt`/`.kts`, `.scala`/`.sc`, and `.ex`/`.exs`, bringing the supported-language count to 14. A new
|
|
39
|
+
`name: Type` parameter parser handles the JVM signature shape; Elixir joins the dynamic-language set.
|
|
40
|
+
- **Fix: repo-walk language coverage.** `LIFT_EXTS` was stale and silently skipped Python, Java, C#,
|
|
41
|
+
Go, C++, PHP, and Ruby during repo/`--all` lifts; it now mirrors the full adapter set.
|
|
42
|
+
- **Fix: single-file lift language detection.** `thunder lift <file>` now auto-detects the language by
|
|
43
|
+
extension instead of defaulting to TypeScript, consistent with `--all`/repo modes.
|
|
44
|
+
|
|
6
45
|
## 0.2.0
|
|
7
46
|
|
|
8
47
|
The cross-language target execution release. Additive; no breaking changes.
|
package/dist/core.cjs
CHANGED
|
@@ -161,6 +161,7 @@ __export(core_exports, {
|
|
|
161
161
|
toDesignTokens: () => toDesignTokens,
|
|
162
162
|
toFinding: () => toFinding,
|
|
163
163
|
toJava: () => toJava,
|
|
164
|
+
toPython: () => toPython,
|
|
164
165
|
toTypeScript: () => toTypeScript,
|
|
165
166
|
twelveFactorReport: () => twelveFactorReport,
|
|
166
167
|
twelveFactorSummary: () => twelveFactorSummary,
|
|
@@ -3336,6 +3337,7 @@ function exprToCode(src, { inputs = [], dialect = "js" } = {}) {
|
|
|
3336
3337
|
var exprToJs = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "js" });
|
|
3337
3338
|
var exprToCSharp = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "csharp" });
|
|
3338
3339
|
var exprToJava = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "java" });
|
|
3340
|
+
var exprToPython = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "python" });
|
|
3339
3341
|
|
|
3340
3342
|
// src/runtime.mjs
|
|
3341
3343
|
var RUNTIME_SCHEMA = "intent-runtime-v1";
|
|
@@ -4288,7 +4290,7 @@ function notesSummary(ast) {
|
|
|
4288
4290
|
byLens
|
|
4289
4291
|
};
|
|
4290
4292
|
}
|
|
4291
|
-
var COMPILER_VERSION = "0.
|
|
4293
|
+
var COMPILER_VERSION = "0.4.1";
|
|
4292
4294
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
4293
4295
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
4294
4296
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -5378,6 +5380,112 @@ function extractFactsRuby(source, file = "input.rb") {
|
|
|
5378
5380
|
}
|
|
5379
5381
|
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "ruby", sourceRoot: file, functions, tests, errors };
|
|
5380
5382
|
}
|
|
5383
|
+
function parseNameColonTypeParams(raw) {
|
|
5384
|
+
return splitTopLevel(raw, ",").map((p) => p.trim()).filter(Boolean).map((p) => {
|
|
5385
|
+
const cleaned = p.replace(/@\w+(\([^)]*\))?/g, "").replace(/^(?:val|var|vararg|implicit|final|lazy)\s+/, "").replace(/=.*$/, "").trim();
|
|
5386
|
+
const mm = cleaned.match(/^([A-Za-z_]\w*)\s*:\s*(.+)$/);
|
|
5387
|
+
if (mm) return { name: mm[1], type: mm[2].trim() };
|
|
5388
|
+
return { name: cleaned.replace(/[^\w].*$/, "") || cleaned, type: null };
|
|
5389
|
+
});
|
|
5390
|
+
}
|
|
5391
|
+
function extractFactsKotlin(source, file = "input.kt") {
|
|
5392
|
+
let m;
|
|
5393
|
+
const functions = [];
|
|
5394
|
+
const tests = [];
|
|
5395
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5396
|
+
const testMethods = /* @__PURE__ */ new Set();
|
|
5397
|
+
const ta = /@Test\b[\s\S]{0,120}?\bfun\s+(?:`([^`]+)`|(\w+))\s*\(/g;
|
|
5398
|
+
while (m = ta.exec(source)) testMethods.add(m[1] || m[2]);
|
|
5399
|
+
const fnRe = /\bfun\s+(?:<[^>]*>\s*)?(?:[A-Za-z_][\w.]*\.)?(?:`([^`]+)`|([A-Za-z_]\w*))\s*\(([^)]*)\)\s*(?::\s*([^{=\n]+))?/g;
|
|
5400
|
+
while (m = fnRe.exec(source)) {
|
|
5401
|
+
const name = m[1] || m[2];
|
|
5402
|
+
if (testMethods.has(name)) {
|
|
5403
|
+
if (!tests.some((t) => t.name === name)) tests.push({ name, file, line: lineOf(source, m.index) });
|
|
5404
|
+
continue;
|
|
5405
|
+
}
|
|
5406
|
+
if (seen.has(name)) continue;
|
|
5407
|
+
seen.add(name);
|
|
5408
|
+
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) }] });
|
|
5409
|
+
}
|
|
5410
|
+
const errors = [];
|
|
5411
|
+
const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
|
|
5412
|
+
let mm;
|
|
5413
|
+
const ce = /class\s+(\w*(?:Exception|Error))\b/g;
|
|
5414
|
+
while (mm = ce.exec(source)) addErr(mm[1], mm.index);
|
|
5415
|
+
const th = /throw\s+(\w+)\s*\(/g;
|
|
5416
|
+
while (mm = th.exec(source)) addErr(mm[1], mm.index);
|
|
5417
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "kotlin", sourceRoot: file, functions, tests, errors };
|
|
5418
|
+
}
|
|
5419
|
+
function extractFactsScala(source, file = "input.scala") {
|
|
5420
|
+
let m;
|
|
5421
|
+
const functions = [];
|
|
5422
|
+
const tests = [];
|
|
5423
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5424
|
+
const fnRe = /\bdef\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*\(([^)]*)\)\s*(?::\s*([^={\n]+))?/g;
|
|
5425
|
+
while (m = fnRe.exec(source)) {
|
|
5426
|
+
const name = m[1];
|
|
5427
|
+
if (seen.has(name)) continue;
|
|
5428
|
+
seen.add(name);
|
|
5429
|
+
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) }] });
|
|
5430
|
+
}
|
|
5431
|
+
const seenT = /* @__PURE__ */ new Set();
|
|
5432
|
+
const addTest = (n, idx) => {
|
|
5433
|
+
const k = String(n).toLowerCase();
|
|
5434
|
+
if (n && !seenT.has(k)) {
|
|
5435
|
+
seenT.add(k);
|
|
5436
|
+
tests.push({ name: n, file, line: lineOf(source, idx) });
|
|
5437
|
+
}
|
|
5438
|
+
};
|
|
5439
|
+
const tr = /\btest\s*\(\s*"([^"]+)"/g;
|
|
5440
|
+
while (m = tr.exec(source)) addTest(m[1], m.index);
|
|
5441
|
+
const inRe = /"([^"]+)"\s+(?:in|should|must)\b/g;
|
|
5442
|
+
while (m = inRe.exec(source)) addTest(m[1], m.index);
|
|
5443
|
+
const errors = [];
|
|
5444
|
+
const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
|
|
5445
|
+
let mm;
|
|
5446
|
+
const ce = /class\s+(\w*(?:Exception|Error))\b/g;
|
|
5447
|
+
while (mm = ce.exec(source)) addErr(mm[1], mm.index);
|
|
5448
|
+
const th = /throw\s+new\s+(\w+)\s*\(/g;
|
|
5449
|
+
while (mm = th.exec(source)) addErr(mm[1], mm.index);
|
|
5450
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "scala", sourceRoot: file, functions, tests, errors };
|
|
5451
|
+
}
|
|
5452
|
+
function extractFactsElixir(source, file = "input.ex") {
|
|
5453
|
+
let m;
|
|
5454
|
+
const functions = [];
|
|
5455
|
+
const tests = [];
|
|
5456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5457
|
+
const seenT = /* @__PURE__ */ new Set();
|
|
5458
|
+
const addTest = (n, idx) => {
|
|
5459
|
+
const k = String(n).toLowerCase();
|
|
5460
|
+
if (n && !seenT.has(k)) {
|
|
5461
|
+
seenT.add(k);
|
|
5462
|
+
tests.push({ name: n, file, line: lineOf(source, idx) });
|
|
5463
|
+
}
|
|
5464
|
+
};
|
|
5465
|
+
const tr = /\btest\s+"([^"]+)"/g;
|
|
5466
|
+
while (m = tr.exec(source)) addTest(m[1], m.index);
|
|
5467
|
+
const fnRe = /^[ \t]*defp?\s+([a-z_]\w*[!?]?)\s*(?:\(([^)]*)\))?/gm;
|
|
5468
|
+
while (m = fnRe.exec(source)) {
|
|
5469
|
+
const name = m[1];
|
|
5470
|
+
if (seen.has(name)) continue;
|
|
5471
|
+
seen.add(name);
|
|
5472
|
+
const parameters = splitTopLevel(m[2] || "", ",").map((p) => p.trim()).filter(Boolean).map((p) => ({ name: p.split(/\\\\/)[0].trim().replace(/^%\{?/, "").replace(/[^\w].*$/, "") || p, type: null }));
|
|
5473
|
+
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) }] });
|
|
5474
|
+
}
|
|
5475
|
+
const errors = [];
|
|
5476
|
+
const seenErr = /* @__PURE__ */ new Set();
|
|
5477
|
+
const addErr = (n, idx) => {
|
|
5478
|
+
if (n && !seenErr.has(n)) {
|
|
5479
|
+
seenErr.add(n);
|
|
5480
|
+
errors.push({ name: n, file, line: lineOf(source, idx) });
|
|
5481
|
+
}
|
|
5482
|
+
};
|
|
5483
|
+
const modErr = /defmodule\s+([\w.]*(?:Error|Exception))\b/g;
|
|
5484
|
+
while (m = modErr.exec(source)) addErr(m[1].split(".").pop(), m.index);
|
|
5485
|
+
const raiseRe = /raise\s+([A-Z][\w.]*)/g;
|
|
5486
|
+
while (m = raiseRe.exec(source)) addErr(m[1].split(".").pop(), m.index);
|
|
5487
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "elixir", sourceRoot: file, functions, tests, errors };
|
|
5488
|
+
}
|
|
5381
5489
|
var ADAPTERS = {
|
|
5382
5490
|
typescript: extractFactsTypeScript,
|
|
5383
5491
|
ts: extractFactsTypeScript,
|
|
@@ -5401,10 +5509,18 @@ var ADAPTERS = {
|
|
|
5401
5509
|
cc: extractFactsCpp,
|
|
5402
5510
|
php: extractFactsPhp,
|
|
5403
5511
|
ruby: extractFactsRuby,
|
|
5404
|
-
rb: extractFactsRuby
|
|
5512
|
+
rb: extractFactsRuby,
|
|
5513
|
+
kotlin: extractFactsKotlin,
|
|
5514
|
+
kt: extractFactsKotlin,
|
|
5515
|
+
kts: extractFactsKotlin,
|
|
5516
|
+
scala: extractFactsScala,
|
|
5517
|
+
sc: extractFactsScala,
|
|
5518
|
+
elixir: extractFactsElixir,
|
|
5519
|
+
ex: extractFactsElixir,
|
|
5520
|
+
exs: extractFactsElixir
|
|
5405
5521
|
};
|
|
5406
|
-
var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl"];
|
|
5407
|
-
var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php"]);
|
|
5522
|
+
var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl", "kotlin", "scala", "elixir"];
|
|
5523
|
+
var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php", "elixir"]);
|
|
5408
5524
|
var LANG_DISPLAY = {
|
|
5409
5525
|
typescript: "TypeScript",
|
|
5410
5526
|
javascript: "JavaScript",
|
|
@@ -5416,7 +5532,10 @@ var LANG_DISPLAY = {
|
|
|
5416
5532
|
cpp: "C++",
|
|
5417
5533
|
php: "PHP",
|
|
5418
5534
|
ruby: "Ruby",
|
|
5419
|
-
perl: "Perl"
|
|
5535
|
+
perl: "Perl",
|
|
5536
|
+
kotlin: "Kotlin",
|
|
5537
|
+
scala: "Scala",
|
|
5538
|
+
elixir: "Elixir"
|
|
5420
5539
|
};
|
|
5421
5540
|
function unwrapReturn(ret) {
|
|
5422
5541
|
if (!ret) return { output: null, error: null };
|
|
@@ -5540,6 +5659,9 @@ function languageForFile(file) {
|
|
|
5540
5659
|
if (/\.(cpp|cc|cxx|hpp|hh|c|h)$/i.test(file)) return "cpp";
|
|
5541
5660
|
if (/\.php$/i.test(file)) return "php";
|
|
5542
5661
|
if (/\.rb$/i.test(file)) return "ruby";
|
|
5662
|
+
if (/\.kts?$/i.test(file)) return "kotlin";
|
|
5663
|
+
if (/\.(scala|sc)$/i.test(file)) return "scala";
|
|
5664
|
+
if (/\.exs?$/i.test(file)) return "elixir";
|
|
5543
5665
|
if (/\.(mjs|cjs|jsx?)$/i.test(file)) return "javascript";
|
|
5544
5666
|
return "typescript";
|
|
5545
5667
|
}
|
|
@@ -5547,7 +5669,7 @@ function isPublicFn(fn, language) {
|
|
|
5547
5669
|
const name = fn.name || "";
|
|
5548
5670
|
if (/(?:Internal|Impl|_impl|_helper|_test|Helper)$/.test(name)) return false;
|
|
5549
5671
|
if (language === "go" || language === "golang") return /^[A-Z]/.test(name) && name !== "Test";
|
|
5550
|
-
if (language === "python" || language === "ruby") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
|
|
5672
|
+
if (language === "python" || language === "ruby" || language === "elixir") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
|
|
5551
5673
|
return !name.startsWith("_") && name !== "init" && name !== "constructor";
|
|
5552
5674
|
}
|
|
5553
5675
|
function liftAll(source, { language = "typescript", file = "", publicOnly = true } = {}) {
|
|
@@ -5622,7 +5744,10 @@ var LANG_EXT = {
|
|
|
5622
5744
|
cpp: "cpp",
|
|
5623
5745
|
php: "php",
|
|
5624
5746
|
ruby: "rb",
|
|
5625
|
-
perl: "pl"
|
|
5747
|
+
perl: "pl",
|
|
5748
|
+
kotlin: "kt",
|
|
5749
|
+
scala: "scala",
|
|
5750
|
+
elixir: "ex"
|
|
5626
5751
|
};
|
|
5627
5752
|
function liftSource(source, { language = "typescript", file = "", seeds = void 0 } = {}) {
|
|
5628
5753
|
const key = String(language).toLowerCase();
|
|
@@ -6548,7 +6673,7 @@ function toCSharp(ast) {
|
|
|
6548
6673
|
try {
|
|
6549
6674
|
cond = exprToCSharp(r.when, { inputs });
|
|
6550
6675
|
} catch {
|
|
6551
|
-
cond = `false /* TODO: "${r.when}" */`;
|
|
6676
|
+
cond = `false /* TODO: could not translate "${r.when}" */`;
|
|
6552
6677
|
}
|
|
6553
6678
|
L.push(` if (${cond}) return ${JSON.stringify(r.result)}; // rule ${r.name}`);
|
|
6554
6679
|
}
|
|
@@ -6557,6 +6682,7 @@ function toCSharp(ast) {
|
|
|
6557
6682
|
const inName = (ast.inputs || []).length ? `${subject}Input` : null;
|
|
6558
6683
|
const outName = (ast.outputs || []).length ? `${subject}Output` : "void";
|
|
6559
6684
|
L.push(` public static ${outName} Run(${inName ? `${inName} input` : ""})`, " {");
|
|
6685
|
+
for (const r of ast.requires || []) L.push(` // precondition: ${r} , TODO: validate`);
|
|
6560
6686
|
for (const n of ast.neverRules || []) L.push(` // NEVER: ${n.statement}`);
|
|
6561
6687
|
for (const g of ast.guarantees || []) L.push(` // GUARANTEE: ${g.statement}`);
|
|
6562
6688
|
L.push(' throw new NotImplementedException("TODO: implement , the intent above defines what this must do.");', " }");
|
|
@@ -6617,7 +6743,7 @@ function toJava(ast) {
|
|
|
6617
6743
|
try {
|
|
6618
6744
|
cond = exprToJava(r.when, { inputs });
|
|
6619
6745
|
} catch {
|
|
6620
|
-
cond = `false /* TODO: "${r.when}" */`;
|
|
6746
|
+
cond = `false /* TODO: could not translate "${r.when}" */`;
|
|
6621
6747
|
}
|
|
6622
6748
|
L.push(` if (${cond}) return ${JSON.stringify(r.result)}; // rule ${r.name}`);
|
|
6623
6749
|
}
|
|
@@ -6626,18 +6752,98 @@ function toJava(ast) {
|
|
|
6626
6752
|
const inName = (ast.inputs || []).length ? `${subject}Input` : null;
|
|
6627
6753
|
const outName = (ast.outputs || []).length ? `${subject}Output` : "void";
|
|
6628
6754
|
L.push(` public static ${outName} run(${inName ? `${inName} input` : ""}) {`);
|
|
6755
|
+
for (const r of ast.requires || []) L.push(` // precondition: ${r} , TODO: validate`);
|
|
6629
6756
|
for (const n of ast.neverRules || []) L.push(` // NEVER: ${n.statement}`);
|
|
6630
6757
|
for (const g of ast.guarantees || []) L.push(` // GUARANTEE: ${g.statement}`);
|
|
6631
6758
|
L.push(' throw new UnsupportedOperationException("TODO: implement , the intent above defines what this must do.");', " }");
|
|
6632
6759
|
L.push("}", "");
|
|
6633
6760
|
return L.join("\n");
|
|
6634
6761
|
}
|
|
6762
|
+
var PY_TYPES = {
|
|
6763
|
+
Email: "str",
|
|
6764
|
+
Url: "str",
|
|
6765
|
+
UserId: "str",
|
|
6766
|
+
AccountId: "str",
|
|
6767
|
+
OrderId: "str",
|
|
6768
|
+
InvoiceId: "str",
|
|
6769
|
+
CustomerId: "str",
|
|
6770
|
+
Secret: "str",
|
|
6771
|
+
Token: "str",
|
|
6772
|
+
Jwt: "str",
|
|
6773
|
+
IdempotencyKey: "str",
|
|
6774
|
+
Currency: "str",
|
|
6775
|
+
Date: "str",
|
|
6776
|
+
DateTime: "str",
|
|
6777
|
+
Money: "float",
|
|
6778
|
+
Percentage: "float",
|
|
6779
|
+
Count: "int",
|
|
6780
|
+
Duration: "int",
|
|
6781
|
+
Flag: "bool"
|
|
6782
|
+
};
|
|
6783
|
+
function pyType(type, domain) {
|
|
6784
|
+
if (!type) return "object";
|
|
6785
|
+
const list = /^List<(.+)>$/.exec(type);
|
|
6786
|
+
if (list) return `list[${pyType(list[1], domain)}]`;
|
|
6787
|
+
const base = type.replace(/\(.*\)/, "");
|
|
6788
|
+
if (PY_TYPES[base]) return PY_TYPES[base];
|
|
6789
|
+
domain.add(base);
|
|
6790
|
+
return base;
|
|
6791
|
+
}
|
|
6792
|
+
var snake = (s) => pascal2(s).replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
6793
|
+
function toPython(ast) {
|
|
6794
|
+
const subject = pascal2(subjectName(ast) || "Intent");
|
|
6795
|
+
const domain = /* @__PURE__ */ new Set();
|
|
6796
|
+
const L = [
|
|
6797
|
+
`# ${subject} , generated from ThunderLang (intent-codegen-v1). Deterministic, no AI.`,
|
|
6798
|
+
"# The dataclass contract and the decision logic are fully determined by the intent;",
|
|
6799
|
+
"# business logic marked TODO is yours, bound by the guarantees and never-rules below.",
|
|
6800
|
+
""
|
|
6801
|
+
];
|
|
6802
|
+
const hasData = (ast.inputs || []).length || (ast.outputs || []).length;
|
|
6803
|
+
if (hasData) L.push("from __future__ import annotations", "from dataclasses import dataclass", "");
|
|
6804
|
+
const cls = (name, fields) => fields.length ? ["@dataclass", `class ${name}:`, ...fields.map((f) => ` ${f.name}: ${pyType(f.type, domain)}`), ""] : [];
|
|
6805
|
+
if ((ast.inputs || []).length) L.push(...cls(`${subject}Input`, ast.inputs));
|
|
6806
|
+
if ((ast.outputs || []).length) L.push(...cls(`${subject}Output`, ast.outputs));
|
|
6807
|
+
for (const d of ast.decisions || []) {
|
|
6808
|
+
const inputs = d.inputs || [];
|
|
6809
|
+
L.push(`# decision ${d.name} , first matching rule wins.`);
|
|
6810
|
+
L.push(`def ${snake(d.name)}(${inputs.join(", ")}) -> str:`);
|
|
6811
|
+
for (const r of d.rules || []) {
|
|
6812
|
+
let cond, note = "";
|
|
6813
|
+
try {
|
|
6814
|
+
cond = exprToPython(r.when, { inputs });
|
|
6815
|
+
} catch {
|
|
6816
|
+
cond = "False";
|
|
6817
|
+
note = ` # TODO: could not translate "${r.when}"`;
|
|
6818
|
+
}
|
|
6819
|
+
L.push(` if ${cond}:${note}`);
|
|
6820
|
+
L.push(` return ${JSON.stringify(r.result)} # rule ${r.name}`);
|
|
6821
|
+
}
|
|
6822
|
+
L.push(` return ${JSON.stringify(d.default ?? "Undecided")} # default`, "");
|
|
6823
|
+
}
|
|
6824
|
+
const inName = (ast.inputs || []).length ? `${subject}Input` : null;
|
|
6825
|
+
const outName = (ast.outputs || []).length ? `${subject}Output` : "None";
|
|
6826
|
+
L.push(`def run(${inName ? `input: ${inName}` : ""}) -> ${outName}:`);
|
|
6827
|
+
for (const r of ast.requires || []) L.push(` # precondition: ${r} , TODO: validate`);
|
|
6828
|
+
for (const n of ast.neverRules || []) L.push(` # NEVER: ${n.statement}`);
|
|
6829
|
+
for (const g of ast.guarantees || []) L.push(` # GUARANTEE: ${g.statement}`);
|
|
6830
|
+
L.push(' raise NotImplementedError("TODO: implement , the intent above defines what this must do.")', "");
|
|
6831
|
+
const stubs = [...domain].filter((t) => !PY_TYPES[t] && /^[A-Z]/.test(t)).sort();
|
|
6832
|
+
if (stubs.length) {
|
|
6833
|
+
L.push("# Domain types referenced by the intent , complete these.");
|
|
6834
|
+
for (const t of stubs) L.push(`class ${t}: pass # TODO: fields`);
|
|
6835
|
+
L.push("");
|
|
6836
|
+
}
|
|
6837
|
+
return L.join("\n");
|
|
6838
|
+
}
|
|
6635
6839
|
var GENERATORS = {
|
|
6636
6840
|
typescript: toTypeScript,
|
|
6637
6841
|
ts: toTypeScript,
|
|
6638
6842
|
csharp: toCSharp,
|
|
6639
6843
|
cs: toCSharp,
|
|
6640
|
-
java: toJava
|
|
6844
|
+
java: toJava,
|
|
6845
|
+
python: toPython,
|
|
6846
|
+
py: toPython
|
|
6641
6847
|
};
|
|
6642
6848
|
|
|
6643
6849
|
// src/changes.mjs
|
|
@@ -6854,6 +7060,7 @@ function changeReport(pairs) {
|
|
|
6854
7060
|
toDesignTokens,
|
|
6855
7061
|
toFinding,
|
|
6856
7062
|
toJava,
|
|
7063
|
+
toPython,
|
|
6857
7064
|
toTypeScript,
|
|
6858
7065
|
twelveFactorReport,
|
|
6859
7066
|
twelveFactorSummary,
|