@skillstech/thunderlang 0.3.0 → 0.4.2

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 CHANGED
@@ -3,6 +3,47 @@
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.2
7
+
8
+ The evidence-graph release. Additive; no breaking changes.
9
+
10
+ - **Evidence projections.** ThunderLang can now project its verification into `evidence-event-v1`
11
+ events (`evidenceType: tool_verified`) for the shared SkillsTech proof spine: `thunder evidence
12
+ <file>` emits `intent.proven`, and `--evidence` on `verify-diff` / `conform` / `drift` emits
13
+ `change.gated` / `conformance.verified` / `intent.drift`. The projections are pure, offline, and
14
+ safe-derived (ids, hashes, verdicts, diagnostic codes, and counts only , never source, secret
15
+ values, finding messages, or expected/actual values). The library exports `toEvidenceEvents`,
16
+ `verifyDiffToEvidence`, `conformToEvidence`, and `driftToEvidence`. The network push to the spine
17
+ is intentionally left to the org-layer/CI, so the compiler stays offline and deterministic.
18
+ - **Fix: `thunder lift` emits canonical `.thunder` drafts** instead of the legacy `.intent`
19
+ extension, on both single-file and repo lifts.
20
+
21
+ ## 0.4.1
22
+
23
+ The coherence release. Additive; no breaking changes. Version chosen to align with OpenThunder
24
+ (TL and OT version in lockstep; OT is moving from 0.4.0 to match).
25
+
26
+ - **Python code generation.** `thunder gen <file> --target python` now emits a typed Python 3
27
+ scaffold, aligning the codegen axis with live execution. Also fixes a real gap: C# and Java `gen`
28
+ silently dropped `requires` preconditions that TypeScript emitted; they are now emitted as guided
29
+ TODOs.
30
+ - **Language support matrix.** New `docs/language-support-matrix.md` documents the three axes , lift
31
+ (14 languages), gen, and live target execution (4) , and makes them discoverable.
32
+ - **VS Code extension.** `editors/vscode/` ships a real extension: syntax highlighting and a Language
33
+ Server client that launches `thunder lsp`. Packageable with `vsce package`.
34
+ - **Grouped, scannable CLI help.** `thunder --help` is reorganized into nine labeled groups with a
35
+ one-line mental model. Command behavior is unchanged (help text only); a test guards that no
36
+ command was dropped.
37
+ - **Verify-real-code loop, productized.** New MCP tools `intent_prove`, `intent_conform`, and
38
+ `intent_drift` let AI agents drive the full loop. `docs/verifying-ai-changes.md` is rewritten as
39
+ the headline workflow (author intent, write code, prove conformance, gate the merge) with a CI
40
+ recipe.
41
+ - **Honest Playwright scaffolds.** The exported Playwright tests now open with `test.fixme` so an
42
+ unfilled scaffold cannot pass vacuously.
43
+ - **intent-graph-v1 stability policy.** New `docs/schema-stability.md` commits to additive-only type
44
+ sets within v1, an IR version that moves independently of the package, a deprecation policy, and a
45
+ road to 1.0, so consumers like OpenThunder can depend on the IR safely.
46
+
6
47
  ## 0.3.0
7
48
 
8
49
  The 14-language lift release. Additive; no breaking changes. Restores the OpenThunder -> ThunderLang
package/README.md CHANGED
@@ -11,7 +11,7 @@ Part of [ThunderLang](https://thunderlang.dev), the intent language for AI-era s
11
11
  ## Install
12
12
 
13
13
  ```bash
14
- npm install -g @skillstech/thunderlang # then: intent check path/to/Mission.intent
14
+ npm install -g @skillstech/thunderlang # then: thunder check path/to/Mission.intent
15
15
  npx @skillstech/thunderlang help # or run without installing
16
16
  ```
17
17
 
@@ -19,36 +19,36 @@ npx @skillstech/thunderlang help # or run without installing
19
19
 
20
20
  ```bash
21
21
  # Author & check
22
- intent init [Name] # scaffold a runnable starter mission
23
- intent check <file|dir> # semantic diagnostics (exit 1 on error); a dir gates the repo
24
- intent fmt <file|dir> -w # canonical formatting (whitespace only; comments preserved)
25
- intent build <file> # docs, contract graph, test plan, .intent-proof.json
26
- intent graph <file> # the canonical Intent Graph (intent-graph-v1)
27
- intent check . --format sarif # SARIF 2.1.0 for GitHub/GitLab code scanning
28
- intent schema # emit the graph schema + diagnostic catalog
29
- intent rules · intent explain <CODE> # the full diagnostic catalog / one code
30
- intent proof --schema # the canonical proof envelope schema (intent-proof-v1)
22
+ thunder init [Name] # scaffold a runnable starter mission
23
+ thunder check <file|dir> # semantic diagnostics (exit 1 on error); a dir gates the repo
24
+ thunder fmt <file|dir> -w # canonical formatting (whitespace only; comments preserved)
25
+ thunder build <file> # docs, contract graph, test plan, .intent-proof.json
26
+ thunder graph <file> # the canonical Intent Graph (intent-graph-v1)
27
+ thunder check . --format sarif # SARIF 2.1.0 for GitHub/GitLab code scanning
28
+ thunder schema # emit the graph schema + diagnostic catalog
29
+ thunder rules · thunder explain <CODE> # the full diagnostic catalog / one code
30
+ thunder proof --schema # the canonical proof envelope schema (intent-proof-v1)
31
31
 
32
32
  # Execute (no AI, no generated code)
33
- intent run <file> --inputs '{"age":20}' # evaluate the decision(s) against inputs
34
- intent simulate <file> --events submit,approve # walk the lifecycle(s)
35
- intent test <file> # run in-file test blocks (case/scenario)
36
- intent outcomes <file> # evaluate outcome contracts vs results
37
- intent style <file> # resolve style intents vs the canonical token space
33
+ thunder run <file> --inputs '{"age":20}' # evaluate the decision(s) against inputs
34
+ thunder simulate <file> --events submit,approve # walk the lifecycle(s)
35
+ thunder test <file> # run in-file test blocks (case/scenario)
36
+ thunder outcomes <file> # evaluate outcome contracts vs results
37
+ thunder style <file> # resolve style intents vs the canonical token space
38
38
 
39
39
  # Interop (9 export formats)
40
- intent export <file> --format dmn|bpmn|smv|jsonschema|openapi|tokens|css|mermaid|playwright
41
- intent import <file> [--format dmn|bpmn] [--json] # lift DMN/BPMN into intent
42
- intent source <file|graph.json> # regenerate .intent from a graph
43
- intent migrate <graph.json> [--to <ver>] # upgrade a persisted graph
44
- intent verify <proof.json> [src] # confirm a proof matches its source
40
+ thunder export <file> --format dmn|bpmn|smv|jsonschema|openapi|tokens|css|mermaid|playwright
41
+ thunder import <file> [--format dmn|bpmn] [--json] # lift DMN/BPMN into intent
42
+ thunder source <file|graph.json> # regenerate .intent from a graph
43
+ thunder migrate <graph.json> [--to <ver>] # upgrade a persisted graph
44
+ thunder verify <proof.json> [src] # confirm a proof matches its source
45
45
 
46
46
  # Navigate & compare
47
- intent atlas <dir> [--search q] intent index <dir>
48
- intent diff <before> <after> intent merge <base> <ours> <theirs>
47
+ thunder atlas <dir> [--search q] thunder index <dir>
48
+ thunder diff <before> <after> thunder merge <base> <ours> <theirs>
49
49
 
50
50
  # Code <-> intent
51
- intent lift <file> intent approve <file> intent drift <file>
51
+ thunder lift <file> thunder approve <file> thunder drift <file>
52
52
  ```
53
53
 
54
54
  Run `thunder help` for the full reference.
@@ -75,8 +75,8 @@ test Eligibility
75
75
  ```
76
76
 
77
77
  ```bash
78
- intent run mission.intent --inputs '{"age":20,"score":90}' # -> Eligible, with a trace
79
- intent test mission.intent # -> 1/1 passed
78
+ thunder run mission.intent --inputs '{"age":20,"score":90}' # -> Eligible, with a trace
79
+ thunder test mission.intent # -> 1/1 passed
80
80
  ```
81
81
 
82
82
  No AI, no generated code. The intent itself decides.
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.3.0";
4293
+ var COMPILER_VERSION = "0.4.2";
4292
4294
  var PROOF_SCHEMA_VERSION = "0.1.0";
4293
4295
  var SOURCE_PRODUCT = "skillstech-compiler";
4294
4296
  function buildContractGraph(ast, generatedAt) {
@@ -5711,7 +5713,7 @@ function liftRepo(files, { language } = {}) {
5711
5713
  const base = slug(r.lifted.mission);
5712
5714
  const n = (usedNames.get(base) || 0) + 1;
5713
5715
  usedNames.set(base, n);
5714
- const outName = n === 1 ? `${base}.intent` : `${base}-${n}.intent`;
5716
+ const outName = n === 1 ? `${base}.thunder` : `${base}-${n}.thunder`;
5715
5717
  missions.push({
5716
5718
  mission: r.lifted.mission,
5717
5719
  sourceFile: file,
@@ -6671,7 +6673,7 @@ function toCSharp(ast) {
6671
6673
  try {
6672
6674
  cond = exprToCSharp(r.when, { inputs });
6673
6675
  } catch {
6674
- cond = `false /* TODO: "${r.when}" */`;
6676
+ cond = `false /* TODO: could not translate "${r.when}" */`;
6675
6677
  }
6676
6678
  L.push(` if (${cond}) return ${JSON.stringify(r.result)}; // rule ${r.name}`);
6677
6679
  }
@@ -6680,6 +6682,7 @@ function toCSharp(ast) {
6680
6682
  const inName = (ast.inputs || []).length ? `${subject}Input` : null;
6681
6683
  const outName = (ast.outputs || []).length ? `${subject}Output` : "void";
6682
6684
  L.push(` public static ${outName} Run(${inName ? `${inName} input` : ""})`, " {");
6685
+ for (const r of ast.requires || []) L.push(` // precondition: ${r} , TODO: validate`);
6683
6686
  for (const n of ast.neverRules || []) L.push(` // NEVER: ${n.statement}`);
6684
6687
  for (const g of ast.guarantees || []) L.push(` // GUARANTEE: ${g.statement}`);
6685
6688
  L.push(' throw new NotImplementedException("TODO: implement , the intent above defines what this must do.");', " }");
@@ -6740,7 +6743,7 @@ function toJava(ast) {
6740
6743
  try {
6741
6744
  cond = exprToJava(r.when, { inputs });
6742
6745
  } catch {
6743
- cond = `false /* TODO: "${r.when}" */`;
6746
+ cond = `false /* TODO: could not translate "${r.when}" */`;
6744
6747
  }
6745
6748
  L.push(` if (${cond}) return ${JSON.stringify(r.result)}; // rule ${r.name}`);
6746
6749
  }
@@ -6749,18 +6752,98 @@ function toJava(ast) {
6749
6752
  const inName = (ast.inputs || []).length ? `${subject}Input` : null;
6750
6753
  const outName = (ast.outputs || []).length ? `${subject}Output` : "void";
6751
6754
  L.push(` public static ${outName} run(${inName ? `${inName} input` : ""}) {`);
6755
+ for (const r of ast.requires || []) L.push(` // precondition: ${r} , TODO: validate`);
6752
6756
  for (const n of ast.neverRules || []) L.push(` // NEVER: ${n.statement}`);
6753
6757
  for (const g of ast.guarantees || []) L.push(` // GUARANTEE: ${g.statement}`);
6754
6758
  L.push(' throw new UnsupportedOperationException("TODO: implement , the intent above defines what this must do.");', " }");
6755
6759
  L.push("}", "");
6756
6760
  return L.join("\n");
6757
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
+ }
6758
6839
  var GENERATORS = {
6759
6840
  typescript: toTypeScript,
6760
6841
  ts: toTypeScript,
6761
6842
  csharp: toCSharp,
6762
6843
  cs: toCSharp,
6763
- java: toJava
6844
+ java: toJava,
6845
+ python: toPython,
6846
+ py: toPython
6764
6847
  };
6765
6848
 
6766
6849
  // src/changes.mjs
@@ -6977,6 +7060,7 @@ function changeReport(pairs) {
6977
7060
  toDesignTokens,
6978
7061
  toFinding,
6979
7062
  toJava,
7063
+ toPython,
6980
7064
  toTypeScript,
6981
7065
  twelveFactorReport,
6982
7066
  twelveFactorSummary,