@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 +41 -0
- package/README.md +25 -25
- package/dist/core.cjs +89 -5
- package/dist/index.cjs +388 -8
- package/package.json +3 -3
- package/src/cli.mjs +116 -67
- 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/evidence.mjs +116 -0
- package/src/exporters.mjs +14 -5
- package/src/index.d.ts +1 -0
- package/src/index.mjs +2 -1
- package/src/lift.mjs +1 -1
- package/src/mcp.mjs +125 -1
package/dist/index.cjs
CHANGED
|
@@ -160,6 +160,7 @@ __export(index_exports, {
|
|
|
160
160
|
comprehensionLevel: () => comprehensionLevel,
|
|
161
161
|
comprehensionReport: () => comprehensionReport,
|
|
162
162
|
confidenceFromClassification: () => confidenceFromClassification,
|
|
163
|
+
conformToEvidence: () => conformToEvidence,
|
|
163
164
|
contractHash: () => contractHash,
|
|
164
165
|
contradictionsView: () => contradictionsView,
|
|
165
166
|
correctionsFor: () => correctionsFor,
|
|
@@ -168,6 +169,7 @@ __export(index_exports, {
|
|
|
168
169
|
detectFormat: () => detectFormat,
|
|
169
170
|
diffGraphs: () => diffGraphs,
|
|
170
171
|
draftIntent: () => draftIntent,
|
|
172
|
+
driftToEvidence: () => driftToEvidence,
|
|
171
173
|
dropNodeField: () => dropNodeField,
|
|
172
174
|
emptyApprovals: () => emptyApprovals,
|
|
173
175
|
emptyEventLog: () => emptyEventLog,
|
|
@@ -288,12 +290,14 @@ __export(index_exports, {
|
|
|
288
290
|
toCss: () => toCss,
|
|
289
291
|
toDMN: () => toDMN,
|
|
290
292
|
toDesignTokens: () => toDesignTokens,
|
|
293
|
+
toEvidenceEvents: () => toEvidenceEvents,
|
|
291
294
|
toFinding: () => toFinding,
|
|
292
295
|
toJSONSchema: () => toJSONSchema,
|
|
293
296
|
toJava: () => toJava,
|
|
294
297
|
toMermaid: () => toMermaid,
|
|
295
298
|
toOpenAPI: () => toOpenAPI,
|
|
296
299
|
toPlaywright: () => toPlaywright,
|
|
300
|
+
toPython: () => toPython,
|
|
297
301
|
toSMV: () => toSMV,
|
|
298
302
|
toSarif: () => toSarif,
|
|
299
303
|
toTypeScript: () => toTypeScript,
|
|
@@ -308,6 +312,7 @@ __export(index_exports, {
|
|
|
308
312
|
validateIR: () => validateIR,
|
|
309
313
|
validateProof: () => validateProof,
|
|
310
314
|
verifyDiff: () => verifyDiff,
|
|
315
|
+
verifyDiffToEvidence: () => verifyDiffToEvidence,
|
|
311
316
|
verifyLedger: () => verifyLedger,
|
|
312
317
|
violatesArchitecture: () => violatesArchitecture,
|
|
313
318
|
whyBuilt: () => whyBuilt
|
|
@@ -327,8 +332,8 @@ function intentRefId(astOrName, { sourceHash } = {}) {
|
|
|
327
332
|
const name = typeof astOrName === "string" ? astOrName : subjectName(astOrName);
|
|
328
333
|
const base = `intent:${slug(name || "mission")}`;
|
|
329
334
|
if (!sourceHash) return base;
|
|
330
|
-
const
|
|
331
|
-
return
|
|
335
|
+
const short2 = String(sourceHash).replace(/^sha256:/, "").slice(0, 8);
|
|
336
|
+
return short2 ? `${base}@${short2}` : base;
|
|
332
337
|
}
|
|
333
338
|
function skillRefId(name) {
|
|
334
339
|
return `skill:${slug(name || "unknown")}`;
|
|
@@ -1224,6 +1229,103 @@ function parseIntent(source) {
|
|
|
1224
1229
|
return ast;
|
|
1225
1230
|
}
|
|
1226
1231
|
|
|
1232
|
+
// src/evidence.mjs
|
|
1233
|
+
var COUNTED_STATUSES = ["verified", "failed", "planned", "needs_verification"];
|
|
1234
|
+
var stripHash = (h) => String(h || "").replace(/^sha256:/, "");
|
|
1235
|
+
var short = (h, n = 16) => stripHash(h).slice(0, n);
|
|
1236
|
+
function envelope(eventType, evidenceId, ctx = {}, payload = {}) {
|
|
1237
|
+
return {
|
|
1238
|
+
schema: "evidence-event-v1",
|
|
1239
|
+
sourceProduct: "thunderlang",
|
|
1240
|
+
producer: { name: "thunderlang", version: ctx.compilerVersion || null },
|
|
1241
|
+
eventType,
|
|
1242
|
+
evidenceType: "tool_verified",
|
|
1243
|
+
evidenceId,
|
|
1244
|
+
occurredAt: ctx.occurredAt || null,
|
|
1245
|
+
visibility: ctx.visibility || "private",
|
|
1246
|
+
subject: { missionName: ctx.missionName || null, intentHash: ctx.intentHash || null },
|
|
1247
|
+
payload
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
var safeClaim = (kind, c) => ({ id: c.id, kind, status: c.status, provenBy: c.provenBy || null });
|
|
1251
|
+
function countByStatus(claims) {
|
|
1252
|
+
const counts = Object.fromEntries(COUNTED_STATUSES.map((s) => [s, 0]));
|
|
1253
|
+
for (const c of claims) if (c && Object.prototype.hasOwnProperty.call(counts, c.status)) counts[c.status]++;
|
|
1254
|
+
return counts;
|
|
1255
|
+
}
|
|
1256
|
+
var safeFinding = (f) => ({ code: f.code, level: f.level, ...f.regression !== void 0 ? { regression: !!f.regression } : {}, ...f.line !== void 0 ? { line: f.line } : {} });
|
|
1257
|
+
function toEvidenceEvents(proof) {
|
|
1258
|
+
if (!proof || typeof proof !== "object") return [];
|
|
1259
|
+
const guarantees = Array.isArray(proof.guarantees) ? proof.guarantees : [];
|
|
1260
|
+
const neverRules = Array.isArray(proof.neverRules) ? proof.neverRules : [];
|
|
1261
|
+
const claims = [
|
|
1262
|
+
...guarantees.map((c) => safeClaim("guarantee", c)),
|
|
1263
|
+
...neverRules.map((c) => safeClaim("prohibition", c))
|
|
1264
|
+
];
|
|
1265
|
+
const fr = proof.freshness || {};
|
|
1266
|
+
const intentHash2 = proof.freshness?.intentHash || proof.sourceHash || null;
|
|
1267
|
+
return [envelope("intent.proven", `tl-proof-${short(intentHash2)}`, {
|
|
1268
|
+
compilerVersion: proof.compilerVersion,
|
|
1269
|
+
occurredAt: proof.generatedAt,
|
|
1270
|
+
missionName: proof.missionName,
|
|
1271
|
+
intentHash: intentHash2
|
|
1272
|
+
}, {
|
|
1273
|
+
proofStatus: proof.proofStatus || null,
|
|
1274
|
+
total: claims.length,
|
|
1275
|
+
counts: countByStatus(claims),
|
|
1276
|
+
freshness: { implementation: fr.implementation || null, dependencies: fr.dependencies ? fr.dependencies.hash || null : null, environment: fr.environment || null },
|
|
1277
|
+
claims
|
|
1278
|
+
})];
|
|
1279
|
+
}
|
|
1280
|
+
function verifyDiffToEvidence(verdict2, ctx = {}) {
|
|
1281
|
+
if (!verdict2 || typeof verdict2 !== "object") return [];
|
|
1282
|
+
const findings2 = Array.isArray(verdict2.findings) ? verdict2.findings : [];
|
|
1283
|
+
const id = `tl-verifydiff-${short(ctx.intentHash)}${ctx.changeHash ? `-${short(ctx.changeHash, 8)}` : ""}`;
|
|
1284
|
+
return [envelope("change.gated", id, ctx, {
|
|
1285
|
+
verdict: verdict2.verdict || null,
|
|
1286
|
+
ok: !!verdict2.ok,
|
|
1287
|
+
blocking: verdict2.blocking || 0,
|
|
1288
|
+
regressions: verdict2.summary?.regressions ?? findings2.filter((f) => f.regression).length,
|
|
1289
|
+
findings: findings2.map(safeFinding)
|
|
1290
|
+
})];
|
|
1291
|
+
}
|
|
1292
|
+
function conformToEvidence(report, ctx = {}) {
|
|
1293
|
+
if (!report || typeof report !== "object") return [];
|
|
1294
|
+
const cases = Array.isArray(report.cases) ? report.cases : [];
|
|
1295
|
+
const columns = Array.isArray(report.columns) ? report.columns : [];
|
|
1296
|
+
const targets = {};
|
|
1297
|
+
for (const col of columns) {
|
|
1298
|
+
const t = { pass: 0, fail: 0, declared: 0 };
|
|
1299
|
+
for (const c of cases) {
|
|
1300
|
+
const s = c.targets?.[col]?.status;
|
|
1301
|
+
if (s === "pass") t.pass++;
|
|
1302
|
+
else if (s === "fail") t.fail++;
|
|
1303
|
+
else t.declared++;
|
|
1304
|
+
}
|
|
1305
|
+
targets[col] = t;
|
|
1306
|
+
}
|
|
1307
|
+
return [envelope("conformance.verified", `tl-conform-${short(ctx.intentHash)}`, ctx, {
|
|
1308
|
+
total: report.total || cases.length,
|
|
1309
|
+
columns,
|
|
1310
|
+
semanticFailures: report.semanticFailures || 0,
|
|
1311
|
+
graded: !!report.graded,
|
|
1312
|
+
targets,
|
|
1313
|
+
// failure locations by name only (case + target), never expected/actual values.
|
|
1314
|
+
failures: (Array.isArray(report.failures) ? report.failures : []).map((f) => ({ target: f.target, case: f.case }))
|
|
1315
|
+
})];
|
|
1316
|
+
}
|
|
1317
|
+
function driftToEvidence(report, ctx = {}) {
|
|
1318
|
+
if (!report || typeof report !== "object") return [];
|
|
1319
|
+
const findings2 = Array.isArray(report.findings) ? report.findings : [];
|
|
1320
|
+
const id = `tl-drift-${short(ctx.intentHash)}${ctx.codeHash ? `-${short(ctx.codeHash, 8)}` : ""}`;
|
|
1321
|
+
return [envelope("intent.drift", id, ctx, {
|
|
1322
|
+
status: report.status || null,
|
|
1323
|
+
total: findings2.length,
|
|
1324
|
+
blocking: report.summary?.blocking ?? 0,
|
|
1325
|
+
findings: findings2.map(safeFinding)
|
|
1326
|
+
})];
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1227
1329
|
// src/hash.mjs
|
|
1228
1330
|
var K = new Uint32Array([
|
|
1229
1331
|
1116352408,
|
|
@@ -2287,7 +2389,7 @@ function notesSummary(ast) {
|
|
|
2287
2389
|
byLens
|
|
2288
2390
|
};
|
|
2289
2391
|
}
|
|
2290
|
-
var COMPILER_VERSION = "0.
|
|
2392
|
+
var COMPILER_VERSION = "0.4.2";
|
|
2291
2393
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
2292
2394
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
2293
2395
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -4086,7 +4188,7 @@ function liftRepo(files, { language } = {}) {
|
|
|
4086
4188
|
const base = slug(r.lifted.mission);
|
|
4087
4189
|
const n = (usedNames.get(base) || 0) + 1;
|
|
4088
4190
|
usedNames.set(base, n);
|
|
4089
|
-
const outName = n === 1 ? `${base}.
|
|
4191
|
+
const outName = n === 1 ? `${base}.thunder` : `${base}-${n}.thunder`;
|
|
4090
4192
|
missions.push({
|
|
4091
4193
|
mission: r.lifted.mission,
|
|
4092
4194
|
sourceFile: file,
|
|
@@ -5147,7 +5249,9 @@ function toPlaywright(ast) {
|
|
|
5147
5249
|
const exps = ast.experiences || [];
|
|
5148
5250
|
const L = [];
|
|
5149
5251
|
L.push("// Playwright test scaffold generated from experience intent by @skillstech/thunderlang.");
|
|
5150
|
-
L.push("// SKELETON: fill in selectors and assertions.
|
|
5252
|
+
L.push("// SKELETON: fill in selectors and assertions. Every test starts with a test.fixme");
|
|
5253
|
+
L.push('// guard so an unimplemented scaffold reports "fixme" instead of passing vacuously;');
|
|
5254
|
+
L.push("// remove the guard from each test once its body is real.");
|
|
5151
5255
|
L.push("import { test, expect } from '@playwright/test';");
|
|
5152
5256
|
L.push("");
|
|
5153
5257
|
if (!exps.length) {
|
|
@@ -5162,6 +5266,7 @@ function toPlaywright(ast) {
|
|
|
5162
5266
|
}
|
|
5163
5267
|
for (const j of exp.journeys || []) {
|
|
5164
5268
|
L.push(` test(${jsStr(j.name || "journey")}, async ({ page }) => {`);
|
|
5269
|
+
L.push(` test.fixme(true, ${jsStr("scaffold: implement the steps, then remove this guard")});`);
|
|
5165
5270
|
if (!(j.steps || []).length) L.push(" // TODO: no steps declared for this journey");
|
|
5166
5271
|
for (const step of j.steps || []) {
|
|
5167
5272
|
L.push(` await test.step(${jsStr(step)}, async () => {`);
|
|
@@ -5173,10 +5278,12 @@ function toPlaywright(ast) {
|
|
|
5173
5278
|
for (const st of exp.states || []) {
|
|
5174
5279
|
if (st.hasRecovery) {
|
|
5175
5280
|
L.push(` test(${jsStr(`failure state "${st.name}" offers a recovery path`)}, async ({ page }) => {`);
|
|
5281
|
+
L.push(` test.fixme(true, ${jsStr("scaffold: implement this state check, then remove this guard")});`);
|
|
5176
5282
|
L.push(` // TODO: drive the UI into "${st.name}", assert a recovery action is available`);
|
|
5177
5283
|
L.push(" });");
|
|
5178
5284
|
} else {
|
|
5179
5285
|
L.push(` test(${jsStr(`reaches state: ${st.name}`)}, async ({ page }) => {`);
|
|
5286
|
+
L.push(` test.fixme(true, ${jsStr("scaffold: implement this state check, then remove this guard")});`);
|
|
5180
5287
|
L.push(` // TODO: drive the UI to "${st.name}" and assert it is shown`);
|
|
5181
5288
|
L.push(" });");
|
|
5182
5289
|
}
|
|
@@ -6351,6 +6458,7 @@ function exprToCode(src, { inputs = [], dialect = "js" } = {}) {
|
|
|
6351
6458
|
var exprToJs = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "js" });
|
|
6352
6459
|
var exprToCSharp = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "csharp" });
|
|
6353
6460
|
var exprToJava = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "java" });
|
|
6461
|
+
var exprToPython = (src, opts = {}) => exprToCode(src, { ...opts, dialect: "python" });
|
|
6354
6462
|
|
|
6355
6463
|
// src/runtime.mjs
|
|
6356
6464
|
var RUNTIME_SCHEMA = "intent-runtime-v1";
|
|
@@ -7848,7 +7956,7 @@ function toCSharp(ast) {
|
|
|
7848
7956
|
try {
|
|
7849
7957
|
cond = exprToCSharp(r.when, { inputs });
|
|
7850
7958
|
} catch {
|
|
7851
|
-
cond = `false /* TODO: "${r.when}" */`;
|
|
7959
|
+
cond = `false /* TODO: could not translate "${r.when}" */`;
|
|
7852
7960
|
}
|
|
7853
7961
|
L.push(` if (${cond}) return ${JSON.stringify(r.result)}; // rule ${r.name}`);
|
|
7854
7962
|
}
|
|
@@ -7857,6 +7965,7 @@ function toCSharp(ast) {
|
|
|
7857
7965
|
const inName = (ast.inputs || []).length ? `${subject}Input` : null;
|
|
7858
7966
|
const outName = (ast.outputs || []).length ? `${subject}Output` : "void";
|
|
7859
7967
|
L.push(` public static ${outName} Run(${inName ? `${inName} input` : ""})`, " {");
|
|
7968
|
+
for (const r of ast.requires || []) L.push(` // precondition: ${r} , TODO: validate`);
|
|
7860
7969
|
for (const n of ast.neverRules || []) L.push(` // NEVER: ${n.statement}`);
|
|
7861
7970
|
for (const g of ast.guarantees || []) L.push(` // GUARANTEE: ${g.statement}`);
|
|
7862
7971
|
L.push(' throw new NotImplementedException("TODO: implement , the intent above defines what this must do.");', " }");
|
|
@@ -7917,7 +8026,7 @@ function toJava(ast) {
|
|
|
7917
8026
|
try {
|
|
7918
8027
|
cond = exprToJava(r.when, { inputs });
|
|
7919
8028
|
} catch {
|
|
7920
|
-
cond = `false /* TODO: "${r.when}" */`;
|
|
8029
|
+
cond = `false /* TODO: could not translate "${r.when}" */`;
|
|
7921
8030
|
}
|
|
7922
8031
|
L.push(` if (${cond}) return ${JSON.stringify(r.result)}; // rule ${r.name}`);
|
|
7923
8032
|
}
|
|
@@ -7926,18 +8035,98 @@ function toJava(ast) {
|
|
|
7926
8035
|
const inName = (ast.inputs || []).length ? `${subject}Input` : null;
|
|
7927
8036
|
const outName = (ast.outputs || []).length ? `${subject}Output` : "void";
|
|
7928
8037
|
L.push(` public static ${outName} run(${inName ? `${inName} input` : ""}) {`);
|
|
8038
|
+
for (const r of ast.requires || []) L.push(` // precondition: ${r} , TODO: validate`);
|
|
7929
8039
|
for (const n of ast.neverRules || []) L.push(` // NEVER: ${n.statement}`);
|
|
7930
8040
|
for (const g of ast.guarantees || []) L.push(` // GUARANTEE: ${g.statement}`);
|
|
7931
8041
|
L.push(' throw new UnsupportedOperationException("TODO: implement , the intent above defines what this must do.");', " }");
|
|
7932
8042
|
L.push("}", "");
|
|
7933
8043
|
return L.join("\n");
|
|
7934
8044
|
}
|
|
8045
|
+
var PY_TYPES = {
|
|
8046
|
+
Email: "str",
|
|
8047
|
+
Url: "str",
|
|
8048
|
+
UserId: "str",
|
|
8049
|
+
AccountId: "str",
|
|
8050
|
+
OrderId: "str",
|
|
8051
|
+
InvoiceId: "str",
|
|
8052
|
+
CustomerId: "str",
|
|
8053
|
+
Secret: "str",
|
|
8054
|
+
Token: "str",
|
|
8055
|
+
Jwt: "str",
|
|
8056
|
+
IdempotencyKey: "str",
|
|
8057
|
+
Currency: "str",
|
|
8058
|
+
Date: "str",
|
|
8059
|
+
DateTime: "str",
|
|
8060
|
+
Money: "float",
|
|
8061
|
+
Percentage: "float",
|
|
8062
|
+
Count: "int",
|
|
8063
|
+
Duration: "int",
|
|
8064
|
+
Flag: "bool"
|
|
8065
|
+
};
|
|
8066
|
+
function pyType(type, domain) {
|
|
8067
|
+
if (!type) return "object";
|
|
8068
|
+
const list = /^List<(.+)>$/.exec(type);
|
|
8069
|
+
if (list) return `list[${pyType(list[1], domain)}]`;
|
|
8070
|
+
const base = type.replace(/\(.*\)/, "");
|
|
8071
|
+
if (PY_TYPES[base]) return PY_TYPES[base];
|
|
8072
|
+
domain.add(base);
|
|
8073
|
+
return base;
|
|
8074
|
+
}
|
|
8075
|
+
var snake = (s) => pascal2(s).replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
8076
|
+
function toPython(ast) {
|
|
8077
|
+
const subject = pascal2(subjectName(ast) || "Intent");
|
|
8078
|
+
const domain = /* @__PURE__ */ new Set();
|
|
8079
|
+
const L = [
|
|
8080
|
+
`# ${subject} , generated from ThunderLang (intent-codegen-v1). Deterministic, no AI.`,
|
|
8081
|
+
"# The dataclass contract and the decision logic are fully determined by the intent;",
|
|
8082
|
+
"# business logic marked TODO is yours, bound by the guarantees and never-rules below.",
|
|
8083
|
+
""
|
|
8084
|
+
];
|
|
8085
|
+
const hasData = (ast.inputs || []).length || (ast.outputs || []).length;
|
|
8086
|
+
if (hasData) L.push("from __future__ import annotations", "from dataclasses import dataclass", "");
|
|
8087
|
+
const cls = (name, fields) => fields.length ? ["@dataclass", `class ${name}:`, ...fields.map((f) => ` ${f.name}: ${pyType(f.type, domain)}`), ""] : [];
|
|
8088
|
+
if ((ast.inputs || []).length) L.push(...cls(`${subject}Input`, ast.inputs));
|
|
8089
|
+
if ((ast.outputs || []).length) L.push(...cls(`${subject}Output`, ast.outputs));
|
|
8090
|
+
for (const d of ast.decisions || []) {
|
|
8091
|
+
const inputs = d.inputs || [];
|
|
8092
|
+
L.push(`# decision ${d.name} , first matching rule wins.`);
|
|
8093
|
+
L.push(`def ${snake(d.name)}(${inputs.join(", ")}) -> str:`);
|
|
8094
|
+
for (const r of d.rules || []) {
|
|
8095
|
+
let cond, note = "";
|
|
8096
|
+
try {
|
|
8097
|
+
cond = exprToPython(r.when, { inputs });
|
|
8098
|
+
} catch {
|
|
8099
|
+
cond = "False";
|
|
8100
|
+
note = ` # TODO: could not translate "${r.when}"`;
|
|
8101
|
+
}
|
|
8102
|
+
L.push(` if ${cond}:${note}`);
|
|
8103
|
+
L.push(` return ${JSON.stringify(r.result)} # rule ${r.name}`);
|
|
8104
|
+
}
|
|
8105
|
+
L.push(` return ${JSON.stringify(d.default ?? "Undecided")} # default`, "");
|
|
8106
|
+
}
|
|
8107
|
+
const inName = (ast.inputs || []).length ? `${subject}Input` : null;
|
|
8108
|
+
const outName = (ast.outputs || []).length ? `${subject}Output` : "None";
|
|
8109
|
+
L.push(`def run(${inName ? `input: ${inName}` : ""}) -> ${outName}:`);
|
|
8110
|
+
for (const r of ast.requires || []) L.push(` # precondition: ${r} , TODO: validate`);
|
|
8111
|
+
for (const n of ast.neverRules || []) L.push(` # NEVER: ${n.statement}`);
|
|
8112
|
+
for (const g of ast.guarantees || []) L.push(` # GUARANTEE: ${g.statement}`);
|
|
8113
|
+
L.push(' raise NotImplementedError("TODO: implement , the intent above defines what this must do.")', "");
|
|
8114
|
+
const stubs = [...domain].filter((t) => !PY_TYPES[t] && /^[A-Z]/.test(t)).sort();
|
|
8115
|
+
if (stubs.length) {
|
|
8116
|
+
L.push("# Domain types referenced by the intent , complete these.");
|
|
8117
|
+
for (const t of stubs) L.push(`class ${t}: pass # TODO: fields`);
|
|
8118
|
+
L.push("");
|
|
8119
|
+
}
|
|
8120
|
+
return L.join("\n");
|
|
8121
|
+
}
|
|
7935
8122
|
var GENERATORS = {
|
|
7936
8123
|
typescript: toTypeScript,
|
|
7937
8124
|
ts: toTypeScript,
|
|
7938
8125
|
csharp: toCSharp,
|
|
7939
8126
|
cs: toCSharp,
|
|
7940
|
-
java: toJava
|
|
8127
|
+
java: toJava,
|
|
8128
|
+
python: toPython,
|
|
8129
|
+
py: toPython
|
|
7941
8130
|
};
|
|
7942
8131
|
|
|
7943
8132
|
// src/changes.mjs
|
|
@@ -8527,6 +8716,54 @@ function verifyDiff(intentText, { before = null, after, language = "typescript"
|
|
|
8527
8716
|
};
|
|
8528
8717
|
}
|
|
8529
8718
|
|
|
8719
|
+
// src/mcp.mjs
|
|
8720
|
+
var import_node_child_process = require("node:child_process");
|
|
8721
|
+
var import_node_fs = require("node:fs");
|
|
8722
|
+
var import_node_path = require("node:path");
|
|
8723
|
+
|
|
8724
|
+
// src/conformance.mjs
|
|
8725
|
+
function buildConformance(ast, { targets = [], results = null } = {}) {
|
|
8726
|
+
const sem = runTests(ast);
|
|
8727
|
+
const cases = (sem.results || []).map((r) => ({
|
|
8728
|
+
key: `${r.target} / ${r.case}`,
|
|
8729
|
+
test: r.target,
|
|
8730
|
+
case: r.case,
|
|
8731
|
+
inputs: r.inputs || {},
|
|
8732
|
+
expected: r.expected,
|
|
8733
|
+
semantic: r.actual,
|
|
8734
|
+
semanticPass: !!r.pass
|
|
8735
|
+
}));
|
|
8736
|
+
const columns = (targets.length ? targets : ast.targets || []).map((t) => String(t).toLowerCase());
|
|
8737
|
+
const rows = cases.map((c) => {
|
|
8738
|
+
const t = {};
|
|
8739
|
+
for (const col of columns) {
|
|
8740
|
+
const tr = results && results[col];
|
|
8741
|
+
if (!tr || !(c.key in tr)) {
|
|
8742
|
+
t[col] = { status: "declared" };
|
|
8743
|
+
continue;
|
|
8744
|
+
}
|
|
8745
|
+
const actual = tr[c.key];
|
|
8746
|
+
t[col] = { status: String(actual) === String(c.expected) ? "pass" : "fail", actual };
|
|
8747
|
+
}
|
|
8748
|
+
return { ...c, targets: t };
|
|
8749
|
+
});
|
|
8750
|
+
const failures = [];
|
|
8751
|
+
for (const row of rows) for (const col of columns) {
|
|
8752
|
+
const tr = row.targets[col];
|
|
8753
|
+
if (tr.status === "fail") failures.push({ target: col, case: row.key, expected: row.expected, actual: tr.actual });
|
|
8754
|
+
}
|
|
8755
|
+
return {
|
|
8756
|
+
schema: "thunder-conformance-v1",
|
|
8757
|
+
mission: ast.mission,
|
|
8758
|
+
total: cases.length,
|
|
8759
|
+
columns,
|
|
8760
|
+
semanticFailures: cases.filter((c) => !c.semanticPass).length,
|
|
8761
|
+
graded: !!results,
|
|
8762
|
+
failures,
|
|
8763
|
+
cases: rows
|
|
8764
|
+
};
|
|
8765
|
+
}
|
|
8766
|
+
|
|
8530
8767
|
// src/draft.mjs
|
|
8531
8768
|
var DRAFT_SCHEMA = "intent-draft-v1";
|
|
8532
8769
|
var SECRET_TYPES2 = /* @__PURE__ */ new Set(["secret", "password", "passwd", "jwt", "token", "apikey", "api_key", "privatekey", "private_key", "credential", "cvv"]);
|
|
@@ -8618,6 +8855,75 @@ function draftIntent(brief = {}) {
|
|
|
8618
8855
|
// src/mcp.mjs
|
|
8619
8856
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
8620
8857
|
var str = { type: "string" };
|
|
8858
|
+
function resolveObligations(ast) {
|
|
8859
|
+
const t = runTests(ast);
|
|
8860
|
+
const testPass = /* @__PURE__ */ new Map();
|
|
8861
|
+
for (const res of t.results || []) {
|
|
8862
|
+
const cur = testPass.get(res.target);
|
|
8863
|
+
testPass.set(res.target, cur === void 0 ? !!res.pass : cur && !!res.pass);
|
|
8864
|
+
}
|
|
8865
|
+
const norm4 = (s) => String(s).toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
8866
|
+
const matchTest = (verifyText) => {
|
|
8867
|
+
const v = norm4(verifyText);
|
|
8868
|
+
if (!v) return null;
|
|
8869
|
+
for (const [name, pass] of testPass) {
|
|
8870
|
+
const n = norm4(name);
|
|
8871
|
+
if (n && (n === v || n.includes(v) || v.includes(n))) return { name, pass };
|
|
8872
|
+
}
|
|
8873
|
+
return null;
|
|
8874
|
+
};
|
|
8875
|
+
const build = (kind, o) => {
|
|
8876
|
+
const verify = o.verify || [];
|
|
8877
|
+
let status = "unverified", provenBy = null;
|
|
8878
|
+
if (verify.length) {
|
|
8879
|
+
let m = null;
|
|
8880
|
+
for (const vt of verify) {
|
|
8881
|
+
const x = matchTest(vt);
|
|
8882
|
+
if (x) {
|
|
8883
|
+
m = x;
|
|
8884
|
+
if (!x.pass) break;
|
|
8885
|
+
}
|
|
8886
|
+
}
|
|
8887
|
+
if (m) {
|
|
8888
|
+
status = m.pass ? "verified" : "failed";
|
|
8889
|
+
provenBy = m.name;
|
|
8890
|
+
} else status = "declared";
|
|
8891
|
+
}
|
|
8892
|
+
return { kind, id: o.id, status, provenBy };
|
|
8893
|
+
};
|
|
8894
|
+
const obligations = [...ast.guarantees.map((g) => build("guarantee", g)), ...ast.neverRules.map((n) => build("prohibition", n))];
|
|
8895
|
+
return { obligations, tests: t, failed: obligations.filter((o) => o.status === "failed").length };
|
|
8896
|
+
}
|
|
8897
|
+
var LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "poetry.lock", "Pipfile.lock", "Cargo.lock", "go.sum", "Gemfile.lock", "composer.lock"];
|
|
8898
|
+
function proofFreshness(proof, environment) {
|
|
8899
|
+
let head = null;
|
|
8900
|
+
try {
|
|
8901
|
+
head = (0, import_node_child_process.execSync)("git rev-parse HEAD", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim() || null;
|
|
8902
|
+
} catch {
|
|
8903
|
+
head = null;
|
|
8904
|
+
}
|
|
8905
|
+
let lock = null, dir = process.cwd();
|
|
8906
|
+
for (let i = 0; i < 6 && !lock; i++) {
|
|
8907
|
+
for (const lf of LOCKFILES) {
|
|
8908
|
+
const p = (0, import_node_path.join)(dir, lf);
|
|
8909
|
+
if ((0, import_node_fs.existsSync)(p)) {
|
|
8910
|
+
lock = p;
|
|
8911
|
+
break;
|
|
8912
|
+
}
|
|
8913
|
+
}
|
|
8914
|
+
const parent = (0, import_node_path.dirname)(dir);
|
|
8915
|
+
if (parent === dir) break;
|
|
8916
|
+
dir = parent;
|
|
8917
|
+
}
|
|
8918
|
+
return {
|
|
8919
|
+
intentHash: proof.sourceHash,
|
|
8920
|
+
compilerVersion: proof.compilerVersion,
|
|
8921
|
+
implementation: head,
|
|
8922
|
+
dependencies: lock ? { file: (0, import_node_path.basename)(lock), hash: sha256((0, import_node_fs.readFileSync)(lock, "utf8")) } : null,
|
|
8923
|
+
environment: environment || null,
|
|
8924
|
+
generatedAt: proof.generatedAt
|
|
8925
|
+
};
|
|
8926
|
+
}
|
|
8621
8927
|
var TOOLS = [
|
|
8622
8928
|
{
|
|
8623
8929
|
name: "intent_check",
|
|
@@ -8643,6 +8949,75 @@ var TOOLS = [
|
|
|
8643
8949
|
},
|
|
8644
8950
|
run: ({ intent, after, before = null, language = "typescript" }) => verifyDiff(String(intent), { before: before == null ? null : String(before), after: String(after), language })
|
|
8645
8951
|
},
|
|
8952
|
+
{
|
|
8953
|
+
name: "intent_prove",
|
|
8954
|
+
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.",
|
|
8955
|
+
inputSchema: {
|
|
8956
|
+
type: "object",
|
|
8957
|
+
required: ["source"],
|
|
8958
|
+
properties: {
|
|
8959
|
+
source: { ...str, description: "the .intent source" },
|
|
8960
|
+
sourceFile: { ...str, description: "file name to record in the proof (default intent.thunder)" },
|
|
8961
|
+
environment: { ...str, description: "environment name to bind into the freshness tuple (optional)" }
|
|
8962
|
+
}
|
|
8963
|
+
},
|
|
8964
|
+
run: ({ source, sourceFile = "intent.thunder", environment }) => {
|
|
8965
|
+
const text = String(source);
|
|
8966
|
+
const ast = parseIntent(text);
|
|
8967
|
+
const diagnostics = semanticDiagnostics(ast);
|
|
8968
|
+
const resolved = resolveObligations(ast);
|
|
8969
|
+
const proof = buildProof(ast, {
|
|
8970
|
+
sourceFile: String(sourceFile),
|
|
8971
|
+
sourceHash: sha256(text),
|
|
8972
|
+
targetsRequested: ast.targets || [],
|
|
8973
|
+
targetsGenerated: [],
|
|
8974
|
+
diagnostics,
|
|
8975
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8976
|
+
});
|
|
8977
|
+
const CLAIM_MAP = { verified: "verified", failed: "failed", declared: "planned", unverified: "needs_verification" };
|
|
8978
|
+
const byId = new Map(resolved.obligations.map((o) => [o.id, o]));
|
|
8979
|
+
const applyStatus = (claim) => {
|
|
8980
|
+
const o = byId.get(claim.id);
|
|
8981
|
+
return o ? { ...claim, status: CLAIM_MAP[o.status], provenBy: o.provenBy || null } : claim;
|
|
8982
|
+
};
|
|
8983
|
+
proof.guarantees = proof.guarantees.map(applyStatus);
|
|
8984
|
+
proof.neverRules = proof.neverRules.map(applyStatus);
|
|
8985
|
+
proof.freshness = proofFreshness(proof, environment);
|
|
8986
|
+
const ok = proof.verification.semanticPassed && resolved.tests.ok !== false && resolved.failed === 0;
|
|
8987
|
+
return { ok, proofId: `proof-${proof.sourceHash.replace("sha256:", "").slice(0, 6)}`, ...proof, tests: resolved.tests };
|
|
8988
|
+
}
|
|
8989
|
+
},
|
|
8990
|
+
{
|
|
8991
|
+
name: "intent_conform",
|
|
8992
|
+
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".',
|
|
8993
|
+
inputSchema: {
|
|
8994
|
+
type: "object",
|
|
8995
|
+
required: ["source"],
|
|
8996
|
+
properties: {
|
|
8997
|
+
source: { ...str, description: "the .intent source (with test blocks)" },
|
|
8998
|
+
targets: { type: "array", items: str, description: "target languages to grade (default: the targets the intent declares)" },
|
|
8999
|
+
results: { type: "object", description: 'per-target actual outputs to grade: {target: {"Test / case": result}}' }
|
|
9000
|
+
}
|
|
9001
|
+
},
|
|
9002
|
+
run: ({ source, targets = [], results = null }) => {
|
|
9003
|
+
const rep = buildConformance(parseIntent(String(source)), { targets: Array.isArray(targets) ? targets.map(String) : [], results });
|
|
9004
|
+
return { ok: rep.semanticFailures === 0 && rep.failures.length === 0, ...rep };
|
|
9005
|
+
}
|
|
9006
|
+
},
|
|
9007
|
+
{
|
|
9008
|
+
name: "intent_drift",
|
|
9009
|
+
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`.",
|
|
9010
|
+
inputSchema: {
|
|
9011
|
+
type: "object",
|
|
9012
|
+
required: ["intent", "code"],
|
|
9013
|
+
properties: {
|
|
9014
|
+
intent: { ...str, description: "the approved .intent source (the contract)" },
|
|
9015
|
+
code: { ...str, description: "the implementation source as it exists now" },
|
|
9016
|
+
language: { ...str, description: `source language (default typescript). One of: ${SUPPORTED_LANGUAGES.join(", ")}` }
|
|
9017
|
+
}
|
|
9018
|
+
},
|
|
9019
|
+
run: ({ intent, code, language = "typescript" }) => checkDrift(String(intent), String(code), { language })
|
|
9020
|
+
},
|
|
8646
9021
|
{
|
|
8647
9022
|
name: "intent_draft",
|
|
8648
9023
|
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.",
|
|
@@ -9374,6 +9749,7 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
|
|
|
9374
9749
|
comprehensionLevel,
|
|
9375
9750
|
comprehensionReport,
|
|
9376
9751
|
confidenceFromClassification,
|
|
9752
|
+
conformToEvidence,
|
|
9377
9753
|
contractHash,
|
|
9378
9754
|
contradictionsView,
|
|
9379
9755
|
correctionsFor,
|
|
@@ -9382,6 +9758,7 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
|
|
|
9382
9758
|
detectFormat,
|
|
9383
9759
|
diffGraphs,
|
|
9384
9760
|
draftIntent,
|
|
9761
|
+
driftToEvidence,
|
|
9385
9762
|
dropNodeField,
|
|
9386
9763
|
emptyApprovals,
|
|
9387
9764
|
emptyEventLog,
|
|
@@ -9502,12 +9879,14 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
|
|
|
9502
9879
|
toCss,
|
|
9503
9880
|
toDMN,
|
|
9504
9881
|
toDesignTokens,
|
|
9882
|
+
toEvidenceEvents,
|
|
9505
9883
|
toFinding,
|
|
9506
9884
|
toJSONSchema,
|
|
9507
9885
|
toJava,
|
|
9508
9886
|
toMermaid,
|
|
9509
9887
|
toOpenAPI,
|
|
9510
9888
|
toPlaywright,
|
|
9889
|
+
toPython,
|
|
9511
9890
|
toSMV,
|
|
9512
9891
|
toSarif,
|
|
9513
9892
|
toTypeScript,
|
|
@@ -9522,6 +9901,7 @@ function startLspServer({ readable = process.stdin, writable = process.stdout }
|
|
|
9522
9901
|
validateIR,
|
|
9523
9902
|
validateProof,
|
|
9524
9903
|
verifyDiff,
|
|
9904
|
+
verifyDiffToEvidence,
|
|
9525
9905
|
verifyLedger,
|
|
9526
9906
|
violatesArchitecture,
|
|
9527
9907
|
whyBuilt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillstech/thunderlang",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
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",
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
"homepage": "https://thunderlang.dev",
|
|
71
71
|
"repository": {
|
|
72
72
|
"type": "git",
|
|
73
|
-
"url": "git+https://github.com/SkillsTechTalk/
|
|
73
|
+
"url": "git+https://github.com/SkillsTechTalk/thunderlang.git",
|
|
74
74
|
"directory": "compiler"
|
|
75
75
|
},
|
|
76
76
|
"bugs": {
|
|
77
|
-
"url": "https://github.com/SkillsTechTalk/
|
|
77
|
+
"url": "https://github.com/SkillsTechTalk/thunderlang/issues"
|
|
78
78
|
},
|
|
79
79
|
"author": "Skills Tech Talk, LLC",
|
|
80
80
|
"license": "Apache-2.0",
|