@skillstech/thunderlang 0.1.6 → 0.2.0
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 +87 -47
- package/LICENSE +201 -21
- package/NOTICE +15 -0
- package/README.md +5 -8
- package/dist/core.cjs +123 -26
- package/dist/index.cjs +128 -31
- package/intent-graph.schema.json +227 -4
- package/package.json +12 -12
- package/src/ai-events.mjs +1 -1
- package/src/cli.mjs +759 -151
- package/src/codegen.mjs +1 -1
- package/src/compile.mjs +3 -3
- package/src/conformance.mjs +35 -0
- package/src/coverage.mjs +56 -0
- package/src/drift.mjs +3 -3
- package/src/emit.mjs +5 -3
- package/src/expr.mjs +11 -7
- package/src/focus.mjs +1 -1
- package/src/guard.mjs +3 -3
- package/src/intellisense.mjs +1 -1
- package/src/intent-graph.mjs +29 -0
- package/src/intent-schema.mjs +4 -4
- package/src/ledger.mjs +1 -1
- package/src/lift.mjs +2 -2
- package/src/lsp.mjs +1 -1
- package/src/mcp.mjs +1 -1
- package/src/mutate.mjs +46 -0
- package/src/parse.mjs +85 -6
- package/src/proof-schema.mjs +2 -2
- package/src/property.mjs +108 -0
- package/src/report.mjs +1 -1
- package/src/sarif.mjs +1 -1
- package/src/scan-queries.mjs +1 -1
- package/src/style.mjs +3 -3
- package/src/target-cs.mjs +98 -0
- package/src/target-java.mjs +87 -0
- package/src/target-py.mjs +90 -0
- package/src/target-ts.mjs +66 -0
- package/src/target-util.mjs +95 -0
- package/src/testing.mjs +1 -1
- package/src/twelve-factor.mjs +2 -2
package/dist/core.cjs
CHANGED
|
@@ -471,7 +471,7 @@ var IL_AUTHOR_RULE_ROWS = [
|
|
|
471
471
|
{ ruleId: "IL-SEC-002", area: "security", severity: "blocker", blocks: ["release"], summary: "API returns a secret with no auth requirement." },
|
|
472
472
|
{ ruleId: "IL-TYPE-001", area: "type", severity: "info", blocks: [], summary: "Field uses an unrecognized (likely mistyped) type." },
|
|
473
473
|
// 12-Factor Agents conformance (twelve-factor-v1). Advisory: an intent's alignment with the
|
|
474
|
-
// humanlayer/12-factor-agents principles. `
|
|
474
|
+
// humanlayer/12-factor-agents principles. `thunder twelve-factor` scores these.
|
|
475
475
|
{ ruleId: "IL-12F-01", area: "twelve-factor", severity: "info", blocks: [], summary: "F1 Natural language to tool calls: model dispatch as a decision/command." },
|
|
476
476
|
{ ruleId: "IL-12F-02", area: "twelve-factor", severity: "info", blocks: [], summary: "F2 Own your prompts: behavior is an owned, guaranteed contract, not a black box." },
|
|
477
477
|
{ ruleId: "IL-12F-03", area: "twelve-factor", severity: "info", blocks: [], summary: "F3 Own your context window: declare a scope boundary." },
|
|
@@ -497,8 +497,8 @@ var IL_CORE_RULE_ROWS = [
|
|
|
497
497
|
{ ruleId: "unknown-block", area: "core", severity: "info", blocks: [], summary: "Unrecognized top-level block." },
|
|
498
498
|
{ ruleId: "INTENT-ARCH-001", area: "architecture", severity: "warning", blocks: [], summary: "Architecture rule not understood." },
|
|
499
499
|
{ ruleId: "INTENT-AI-010", area: "ai", severity: "warning", blocks: [], summary: "Unsupported implementation scope." },
|
|
500
|
-
{ ruleId: "INTENT_NOTE_UNKNOWN_LENS", area: "note", severity: "info", blocks: [], summary: "
|
|
501
|
-
{ ruleId: "INTENT_NOTE_EMPTY", area: "note", severity: "info", blocks: [], summary: "
|
|
500
|
+
{ ruleId: "INTENT_NOTE_UNKNOWN_LENS", area: "note", severity: "info", blocks: [], summary: "ThunderLens note uses an unknown lens." },
|
|
501
|
+
{ ruleId: "INTENT_NOTE_EMPTY", area: "note", severity: "info", blocks: [], summary: "ThunderLens note is empty." }
|
|
502
502
|
];
|
|
503
503
|
var RULE_PHASES = ["author", "verify"];
|
|
504
504
|
var RULE_OWNERS = ["IL", "OT"];
|
|
@@ -572,7 +572,7 @@ function intentProofJsonSchema() {
|
|
|
572
572
|
sourceProduct: { type: "string" },
|
|
573
573
|
missionName: { type: ["string", "null"] },
|
|
574
574
|
sourceFile: { type: "string" },
|
|
575
|
-
// sha256:<hex> , the source fingerprint `
|
|
575
|
+
// sha256:<hex> , the source fingerprint `thunder verify` re-checks for drift/tampering.
|
|
576
576
|
sourceHash: { type: "string", pattern: "^sha256:[0-9a-f]{64}$" },
|
|
577
577
|
compilerVersion: { type: "string" },
|
|
578
578
|
generatedAt: { type: ["string", "null"] },
|
|
@@ -789,6 +789,60 @@ function parseDecision(name, node2) {
|
|
|
789
789
|
}
|
|
790
790
|
return dec;
|
|
791
791
|
}
|
|
792
|
+
function parseProperty(name, node2) {
|
|
793
|
+
const prop = { name, vars: [], decide: null, expects: [], line: node2.line };
|
|
794
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
795
|
+
const k = firstWord(c.text);
|
|
796
|
+
const a = rest(c.text);
|
|
797
|
+
if (k === "forAll" || k === "forall") {
|
|
798
|
+
for (const v of c.children.filter((x) => !isNote(x))) {
|
|
799
|
+
const m = v.text.trim().match(/^([A-Za-z_]\w*)\s*:\s*(\w+)(?:\s+where\s+(.+))?$/);
|
|
800
|
+
if (!m) continue;
|
|
801
|
+
const whereChild = (v.children || []).find((x) => firstWord(x.text) === "where");
|
|
802
|
+
prop.vars.push({ name: m[1], type: m[2], where: m[3] || (whereChild ? rest(whereChild.text) : null) });
|
|
803
|
+
}
|
|
804
|
+
} else if (k === "decide") prop.decide = a || c.children[0] && c.children[0].text.trim() || null;
|
|
805
|
+
else if (k === "when") {
|
|
806
|
+
const dm = a.match(/(?:decide|execute)\s+(\w+)/);
|
|
807
|
+
if (dm) prop.decide = dm[1];
|
|
808
|
+
} else if (k === "expect") for (const e of c.children.filter((x) => !isNote(x))) prop.expects.push(e.text.trim());
|
|
809
|
+
}
|
|
810
|
+
return prop;
|
|
811
|
+
}
|
|
812
|
+
function parseEvaluation(name, node2) {
|
|
813
|
+
const ev = { name, dataset: null, requires: [], line: node2.line };
|
|
814
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
815
|
+
const k = firstWord(c.text);
|
|
816
|
+
const a = rest(c.text);
|
|
817
|
+
if (k === "dataset") ev.dataset = a || c.children[0] && c.children[0].text.trim() || null;
|
|
818
|
+
else if (k === "require") {
|
|
819
|
+
for (const r of c.children.filter((x) => !isNote(x))) {
|
|
820
|
+
const m = r.text.trim().match(/^([A-Za-z_][\w.]*)\s*(>=|<=|==|!=|>|<)\s*(-?[\d.]+)$/);
|
|
821
|
+
if (m) ev.requires.push({ metric: m[1], op: m[2], threshold: parseFloat(m[3]) });
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return ev;
|
|
826
|
+
}
|
|
827
|
+
function parseScenario(name, node2) {
|
|
828
|
+
const sc = { name, given: [], when: [], then: [], never: [], eventually: [], line: node2.line };
|
|
829
|
+
const clausesOf = (c) => {
|
|
830
|
+
const items = leafItems(c);
|
|
831
|
+
return items.length ? items : rest(c.text) ? [rest(c.text)] : [];
|
|
832
|
+
};
|
|
833
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
834
|
+
const k = firstWord(c.text);
|
|
835
|
+
if (k === "given") sc.given.push(...clausesOf(c));
|
|
836
|
+
else if (k === "when") sc.when.push(...clausesOf(c));
|
|
837
|
+
else if (k === "then") sc.then.push(...clausesOf(c));
|
|
838
|
+
else if (k === "never") sc.never.push(...clausesOf(c));
|
|
839
|
+
else if (k === "eventually") {
|
|
840
|
+
const within = (rest(c.text).match(/within\s+(.+)/) || [])[1] || null;
|
|
841
|
+
sc.eventually.push({ within, clauses: leafItems(c) });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return sc;
|
|
845
|
+
}
|
|
792
846
|
function parseLifecycle(name, node2) {
|
|
793
847
|
const lc = { name, states: [], transitions: [], terminals: [], line: node2.line };
|
|
794
848
|
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
@@ -821,7 +875,13 @@ function parseInvariant(name, node2) {
|
|
|
821
875
|
for (const c of kids(node2)) {
|
|
822
876
|
const k = firstWord(c.text);
|
|
823
877
|
if (k === "verify") inv.verify.push(rest(c.text));
|
|
824
|
-
else if (k === "
|
|
878
|
+
else if (k === "id") {
|
|
879
|
+
const v = rest(c.text);
|
|
880
|
+
if (v) {
|
|
881
|
+
inv.id = v;
|
|
882
|
+
inv.stableId = v;
|
|
883
|
+
}
|
|
884
|
+
} else if (k === "because") inv.because = rest(c.text);
|
|
825
885
|
else if (k === "statement") inv.statement = rest(c.text) || leafItems(c).join(" ");
|
|
826
886
|
else if (k === "scope") inv.scope = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
827
887
|
else if (k === "severity") inv.severity = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
@@ -907,11 +967,12 @@ function parseHandler(trigger, node2) {
|
|
|
907
967
|
actions
|
|
908
968
|
};
|
|
909
969
|
}
|
|
970
|
+
var VERIFY_KINDS = /* @__PURE__ */ new Set(["assertion", "static", "runtime", "evidence", "tool", "approval", "formal"]);
|
|
910
971
|
function upsertRule(list, statement, line) {
|
|
911
972
|
const id = slug(statement);
|
|
912
973
|
let r = list.find((x) => x.id === id);
|
|
913
974
|
if (!r) {
|
|
914
|
-
r = { id, statement, because: null, verify: [], notes: [], line };
|
|
975
|
+
r = { id, statement, because: null, verify: [], verifications: [], notes: [], line };
|
|
915
976
|
list.push(r);
|
|
916
977
|
}
|
|
917
978
|
return r;
|
|
@@ -920,8 +981,28 @@ function applyDetail(rule, node2) {
|
|
|
920
981
|
for (const c of node2.children) {
|
|
921
982
|
const kw = firstWord(c.text);
|
|
922
983
|
if (kw === "because") rule.because = rest(c.text) || c.children[0] && c.children[0].text || null;
|
|
923
|
-
else if (kw === "verify")
|
|
924
|
-
|
|
984
|
+
else if (kw === "verify") {
|
|
985
|
+
const a = rest(c.text);
|
|
986
|
+
const m = a && a.match(/^by\s+(\w+)/i);
|
|
987
|
+
if (m) {
|
|
988
|
+
const kind = m[1].toLowerCase();
|
|
989
|
+
const details = (c.children || []).filter((x) => !isNote(x)).map((x) => x.text.trim()).filter(Boolean);
|
|
990
|
+
rule.verifications.push({ kind: VERIFY_KINDS.has(kind) ? kind : "unknown", declared: kind, details });
|
|
991
|
+
rule.verify.push(details.length ? `${kind}: ${details.join("; ")}` : `by ${kind}`);
|
|
992
|
+
} else {
|
|
993
|
+
const v = a || c.children[0] && c.children[0].text || "";
|
|
994
|
+
if (v) {
|
|
995
|
+
rule.verify.push(v);
|
|
996
|
+
rule.verifications.push({ kind: "unclassified", declared: null, details: [v] });
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
} else if (kw === "id") {
|
|
1000
|
+
const v = rest(c.text) || c.children[0] && c.children[0].text;
|
|
1001
|
+
if (v) {
|
|
1002
|
+
rule.id = v;
|
|
1003
|
+
rule.stableId = v;
|
|
1004
|
+
}
|
|
1005
|
+
} else if (isNote(c)) rule.notes.push(parseNoteNode(c));
|
|
925
1006
|
}
|
|
926
1007
|
rule.verify = rule.verify.filter(Boolean);
|
|
927
1008
|
}
|
|
@@ -986,6 +1067,9 @@ function parseIntent(source) {
|
|
|
986
1067
|
handlers: [],
|
|
987
1068
|
// Decisions, rules, process (Gap 4)
|
|
988
1069
|
decisions: [],
|
|
1070
|
+
properties: [],
|
|
1071
|
+
scenarios: [],
|
|
1072
|
+
evaluations: [],
|
|
989
1073
|
// Governance: waivers , governed exceptions to blocking diagnostics (Gap 5)
|
|
990
1074
|
waivers: [],
|
|
991
1075
|
// Data purpose + privacy , governed data elements (Gap 6)
|
|
@@ -1214,6 +1298,15 @@ function parseIntent(source) {
|
|
|
1214
1298
|
case "decision":
|
|
1215
1299
|
ast.decisions.push(parseDecision(arg, node2));
|
|
1216
1300
|
break;
|
|
1301
|
+
case "property":
|
|
1302
|
+
ast.properties.push(parseProperty(arg, node2));
|
|
1303
|
+
break;
|
|
1304
|
+
case "scenario":
|
|
1305
|
+
ast.scenarios.push(parseScenario(arg, node2));
|
|
1306
|
+
break;
|
|
1307
|
+
case "evaluation":
|
|
1308
|
+
ast.evaluations.push(parseEvaluation(arg, node2));
|
|
1309
|
+
break;
|
|
1217
1310
|
// ── System profile ──
|
|
1218
1311
|
case "capability": {
|
|
1219
1312
|
const cap = { name: arg, description: null, implements: [], line: node2.line };
|
|
@@ -3198,21 +3291,23 @@ function compileExpr(src) {
|
|
|
3198
3291
|
function evalExpr(src, inputs = {}) {
|
|
3199
3292
|
return compileExpr(src)(inputs);
|
|
3200
3293
|
}
|
|
3294
|
+
var C_LOGIC = { and: (a, b) => `(${a} && ${b})`, or: (a, b) => `(${a} || ${b})`, not: (a) => `!${a}`, bool: (v) => String(v) };
|
|
3201
3295
|
var DIALECTS = {
|
|
3202
|
-
js: { eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
3203
|
-
csharp: { eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
3204
|
-
java: { eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" }
|
|
3296
|
+
js: { ...C_LOGIC, eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
3297
|
+
csharp: { ...C_LOGIC, eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
3298
|
+
java: { ...C_LOGIC, eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" },
|
|
3299
|
+
python: { and: (a, b) => `(${a} and ${b})`, or: (a, b) => `(${a} or ${b})`, not: (a) => `(not ${a})`, bool: (v) => v ? "True" : "False", eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `${x} in [${list.join(", ")}]`, nil: "None" }
|
|
3205
3300
|
};
|
|
3206
3301
|
function renderExpr(src, inputs, D) {
|
|
3207
3302
|
const known = new Set(inputs);
|
|
3208
3303
|
const r = (n) => {
|
|
3209
3304
|
switch (n.k) {
|
|
3210
3305
|
case "or":
|
|
3211
|
-
return
|
|
3306
|
+
return D.or(r(n.a), r(n.b));
|
|
3212
3307
|
case "and":
|
|
3213
|
-
return
|
|
3308
|
+
return D.and(r(n.a), r(n.b));
|
|
3214
3309
|
case "not":
|
|
3215
|
-
return
|
|
3310
|
+
return D.not(r(n.a));
|
|
3216
3311
|
case "neg":
|
|
3217
3312
|
return `-${r(n.a)}`;
|
|
3218
3313
|
case "arith":
|
|
@@ -3226,7 +3321,7 @@ function renderExpr(src, inputs, D) {
|
|
|
3226
3321
|
case "list":
|
|
3227
3322
|
return `[${n.items.map(r).join(", ")}]`;
|
|
3228
3323
|
case "lit":
|
|
3229
|
-
return typeof n.v === "string" ? JSON.stringify(n.v) : String(n.v);
|
|
3324
|
+
return typeof n.v === "string" ? JSON.stringify(n.v) : typeof n.v === "boolean" ? D.bool(n.v) : String(n.v);
|
|
3230
3325
|
case "ref":
|
|
3231
3326
|
return known.has(n.path) || known.has(n.path.split(".")[0]) ? n.path : JSON.stringify(n.path);
|
|
3232
3327
|
default:
|
|
@@ -3359,14 +3454,14 @@ function buildGuard(ast, { denyResults, mask = "[redacted]" } = {}) {
|
|
|
3359
3454
|
}
|
|
3360
3455
|
function decide(name, inputs) {
|
|
3361
3456
|
const d = decisions.get(name);
|
|
3362
|
-
if (!d) throw new Error(`
|
|
3457
|
+
if (!d) throw new Error(`thunder guard: no decision "${name}"`);
|
|
3363
3458
|
const r = evaluateDecision(d, inputs || {});
|
|
3364
3459
|
return { ...r, allowed: !isDenied(r.result) };
|
|
3365
3460
|
}
|
|
3366
3461
|
function assertAllowed(name, inputs) {
|
|
3367
3462
|
const r = decide(name, inputs);
|
|
3368
3463
|
if (!r.allowed) {
|
|
3369
|
-
const e = new Error(`
|
|
3464
|
+
const e = new Error(`thunder guard: decision "${name}" denied the action (result: ${r.result})`);
|
|
3370
3465
|
e.code = "INTENT_GUARD_DENIED";
|
|
3371
3466
|
e.decision = name;
|
|
3372
3467
|
e.result = r.result;
|
|
@@ -3570,7 +3665,7 @@ var FACTORS = [
|
|
|
3570
3665
|
const scoped = len(ast.scope?.include) + len(ast.scope?.exclude);
|
|
3571
3666
|
if (scoped) return { verdict: "satisfied", evidence: `context boundary declared (scope: ${len(ast.scope.include)} include / ${len(ast.scope.exclude)} exclude)` };
|
|
3572
3667
|
if (len(ast.requires)) return { verdict: "partial", evidence: "dependencies declared but no explicit scope boundary", fix: "Declare `scope include/exclude` so the context window is curated, not unbounded." };
|
|
3573
|
-
return { verdict: "absent", evidence: "no scope declared", fix: "Add a `scope` block to bound what belongs in context (
|
|
3668
|
+
return { verdict: "absent", evidence: "no scope declared", fix: "Add a `scope` block to bound what belongs in context (ThunderLens/Focus narrows it further)." };
|
|
3574
3669
|
}
|
|
3575
3670
|
},
|
|
3576
3671
|
{
|
|
@@ -4035,7 +4130,7 @@ function toDesignTokens(ast) {
|
|
|
4035
4130
|
});
|
|
4036
4131
|
for (const t of si.tokens) {
|
|
4037
4132
|
const leaf = { $value: coerceValue(t.value), $type: tokenType(t.path) };
|
|
4038
|
-
if (!isKnownPath(t.path)) leaf.$extensions = { "dev.
|
|
4133
|
+
if (!isKnownPath(t.path)) leaf.$extensions = { "dev.intentlanguage": { canonical: false } };
|
|
4039
4134
|
setPath(root, t.path, leaf);
|
|
4040
4135
|
}
|
|
4041
4136
|
}
|
|
@@ -4043,7 +4138,7 @@ function toDesignTokens(ast) {
|
|
|
4043
4138
|
$description: `Design tokens for ${ast.title || ast.mission || "intent"} (generated from style_intent by @skillstech/thunderlang)`,
|
|
4044
4139
|
...root,
|
|
4045
4140
|
$extensions: {
|
|
4046
|
-
"dev.
|
|
4141
|
+
"dev.intentlanguage": {
|
|
4047
4142
|
schema: DESIGN_TOKENS_SCHEMA,
|
|
4048
4143
|
format: "W3C Design Tokens (DTCG)",
|
|
4049
4144
|
source: ast.mission || null,
|
|
@@ -4193,7 +4288,7 @@ function notesSummary(ast) {
|
|
|
4193
4288
|
byLens
|
|
4194
4289
|
};
|
|
4195
4290
|
}
|
|
4196
|
-
var COMPILER_VERSION = "0.
|
|
4291
|
+
var COMPILER_VERSION = "0.2.0";
|
|
4197
4292
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
4198
4293
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
4199
4294
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -4686,13 +4781,15 @@ function buildProof(ast, { sourceFile, sourceHash, targetsRequested, targetsGene
|
|
|
4686
4781
|
id: g.id,
|
|
4687
4782
|
text: g.statement,
|
|
4688
4783
|
status: g.verify.length > 0 || verifiedText ? "planned" : "needs_verification",
|
|
4689
|
-
evidence: g.verify
|
|
4784
|
+
evidence: g.verify,
|
|
4785
|
+
verifications: g.verifications || []
|
|
4690
4786
|
})),
|
|
4691
4787
|
neverRules: ast.neverRules.map((n) => ({
|
|
4692
4788
|
id: n.id,
|
|
4693
4789
|
text: n.statement,
|
|
4694
4790
|
status: n.verify.length > 0 ? "planned" : "needs_verification",
|
|
4695
|
-
evidence: n.verify
|
|
4791
|
+
evidence: n.verify,
|
|
4792
|
+
verifications: n.verifications || []
|
|
4696
4793
|
})),
|
|
4697
4794
|
errors: (ast.errors || []).map((e) => ({ name: e.name })),
|
|
4698
4795
|
examples: (ast.examples || []).map((ex) => ({ given: ex.given, expect: ex.expect })),
|
|
@@ -5527,7 +5624,7 @@ var LANG_EXT = {
|
|
|
5527
5624
|
ruby: "rb",
|
|
5528
5625
|
perl: "pl"
|
|
5529
5626
|
};
|
|
5530
|
-
function liftSource(source, { language = "typescript", file = "", seeds } = {}) {
|
|
5627
|
+
function liftSource(source, { language = "typescript", file = "", seeds = void 0 } = {}) {
|
|
5531
5628
|
const key = String(language).toLowerCase();
|
|
5532
5629
|
const adapter = ADAPTERS[key];
|
|
5533
5630
|
if (!adapter) {
|
|
@@ -5597,7 +5694,7 @@ function renderLensDoc(ast, lens) {
|
|
|
5597
5694
|
const L = [];
|
|
5598
5695
|
L.push(`# ${m} , for the ${lens} reader`, "");
|
|
5599
5696
|
L.push(
|
|
5600
|
-
`>
|
|
5697
|
+
`> ThunderLens \`${lens}\` notes are woven in below. They explain meaning for this`,
|
|
5601
5698
|
`> audience; they are documentation, not verification.`,
|
|
5602
5699
|
""
|
|
5603
5700
|
);
|
|
@@ -6069,7 +6166,7 @@ function fingerprint(parts) {
|
|
|
6069
6166
|
return `fp_${h.toString(16)}`;
|
|
6070
6167
|
}
|
|
6071
6168
|
function makeScope({ type = "custom", title = null, seeds = [], projectId = null, createdBy = null, createdAt = null, ...rest2 } = {}) {
|
|
6072
|
-
if (!SCOPE_TYPES.includes(type)) throw new Error(`
|
|
6169
|
+
if (!SCOPE_TYPES.includes(type)) throw new Error(`thunder focus: unknown scope type "${type}"`);
|
|
6073
6170
|
return {
|
|
6074
6171
|
schema: FOCUS_SCHEMA,
|
|
6075
6172
|
scopeId: `scope.${type}.${fingerprint([type, ...seeds].map(String))}`,
|
package/dist/index.cjs
CHANGED
|
@@ -450,6 +450,60 @@ function parseDecision(name, node2) {
|
|
|
450
450
|
}
|
|
451
451
|
return dec;
|
|
452
452
|
}
|
|
453
|
+
function parseProperty(name, node2) {
|
|
454
|
+
const prop = { name, vars: [], decide: null, expects: [], line: node2.line };
|
|
455
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
456
|
+
const k = firstWord(c.text);
|
|
457
|
+
const a = rest(c.text);
|
|
458
|
+
if (k === "forAll" || k === "forall") {
|
|
459
|
+
for (const v of c.children.filter((x) => !isNote(x))) {
|
|
460
|
+
const m = v.text.trim().match(/^([A-Za-z_]\w*)\s*:\s*(\w+)(?:\s+where\s+(.+))?$/);
|
|
461
|
+
if (!m) continue;
|
|
462
|
+
const whereChild = (v.children || []).find((x) => firstWord(x.text) === "where");
|
|
463
|
+
prop.vars.push({ name: m[1], type: m[2], where: m[3] || (whereChild ? rest(whereChild.text) : null) });
|
|
464
|
+
}
|
|
465
|
+
} else if (k === "decide") prop.decide = a || c.children[0] && c.children[0].text.trim() || null;
|
|
466
|
+
else if (k === "when") {
|
|
467
|
+
const dm = a.match(/(?:decide|execute)\s+(\w+)/);
|
|
468
|
+
if (dm) prop.decide = dm[1];
|
|
469
|
+
} else if (k === "expect") for (const e of c.children.filter((x) => !isNote(x))) prop.expects.push(e.text.trim());
|
|
470
|
+
}
|
|
471
|
+
return prop;
|
|
472
|
+
}
|
|
473
|
+
function parseEvaluation(name, node2) {
|
|
474
|
+
const ev = { name, dataset: null, requires: [], line: node2.line };
|
|
475
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
476
|
+
const k = firstWord(c.text);
|
|
477
|
+
const a = rest(c.text);
|
|
478
|
+
if (k === "dataset") ev.dataset = a || c.children[0] && c.children[0].text.trim() || null;
|
|
479
|
+
else if (k === "require") {
|
|
480
|
+
for (const r of c.children.filter((x) => !isNote(x))) {
|
|
481
|
+
const m = r.text.trim().match(/^([A-Za-z_][\w.]*)\s*(>=|<=|==|!=|>|<)\s*(-?[\d.]+)$/);
|
|
482
|
+
if (m) ev.requires.push({ metric: m[1], op: m[2], threshold: parseFloat(m[3]) });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return ev;
|
|
487
|
+
}
|
|
488
|
+
function parseScenario(name, node2) {
|
|
489
|
+
const sc = { name, given: [], when: [], then: [], never: [], eventually: [], line: node2.line };
|
|
490
|
+
const clausesOf = (c) => {
|
|
491
|
+
const items = leafItems(c);
|
|
492
|
+
return items.length ? items : rest(c.text) ? [rest(c.text)] : [];
|
|
493
|
+
};
|
|
494
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
495
|
+
const k = firstWord(c.text);
|
|
496
|
+
if (k === "given") sc.given.push(...clausesOf(c));
|
|
497
|
+
else if (k === "when") sc.when.push(...clausesOf(c));
|
|
498
|
+
else if (k === "then") sc.then.push(...clausesOf(c));
|
|
499
|
+
else if (k === "never") sc.never.push(...clausesOf(c));
|
|
500
|
+
else if (k === "eventually") {
|
|
501
|
+
const within = (rest(c.text).match(/within\s+(.+)/) || [])[1] || null;
|
|
502
|
+
sc.eventually.push({ within, clauses: leafItems(c) });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return sc;
|
|
506
|
+
}
|
|
453
507
|
function parseLifecycle(name, node2) {
|
|
454
508
|
const lc = { name, states: [], transitions: [], terminals: [], line: node2.line };
|
|
455
509
|
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
@@ -482,7 +536,13 @@ function parseInvariant(name, node2) {
|
|
|
482
536
|
for (const c of kids(node2)) {
|
|
483
537
|
const k = firstWord(c.text);
|
|
484
538
|
if (k === "verify") inv.verify.push(rest(c.text));
|
|
485
|
-
else if (k === "
|
|
539
|
+
else if (k === "id") {
|
|
540
|
+
const v = rest(c.text);
|
|
541
|
+
if (v) {
|
|
542
|
+
inv.id = v;
|
|
543
|
+
inv.stableId = v;
|
|
544
|
+
}
|
|
545
|
+
} else if (k === "because") inv.because = rest(c.text);
|
|
486
546
|
else if (k === "statement") inv.statement = rest(c.text) || leafItems(c).join(" ");
|
|
487
547
|
else if (k === "scope") inv.scope = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
488
548
|
else if (k === "severity") inv.severity = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
@@ -568,11 +628,12 @@ function parseHandler(trigger, node2) {
|
|
|
568
628
|
actions
|
|
569
629
|
};
|
|
570
630
|
}
|
|
631
|
+
var VERIFY_KINDS = /* @__PURE__ */ new Set(["assertion", "static", "runtime", "evidence", "tool", "approval", "formal"]);
|
|
571
632
|
function upsertRule(list, statement, line) {
|
|
572
633
|
const id = slug(statement);
|
|
573
634
|
let r = list.find((x) => x.id === id);
|
|
574
635
|
if (!r) {
|
|
575
|
-
r = { id, statement, because: null, verify: [], notes: [], line };
|
|
636
|
+
r = { id, statement, because: null, verify: [], verifications: [], notes: [], line };
|
|
576
637
|
list.push(r);
|
|
577
638
|
}
|
|
578
639
|
return r;
|
|
@@ -581,8 +642,28 @@ function applyDetail(rule, node2) {
|
|
|
581
642
|
for (const c of node2.children) {
|
|
582
643
|
const kw = firstWord(c.text);
|
|
583
644
|
if (kw === "because") rule.because = rest(c.text) || c.children[0] && c.children[0].text || null;
|
|
584
|
-
else if (kw === "verify")
|
|
585
|
-
|
|
645
|
+
else if (kw === "verify") {
|
|
646
|
+
const a = rest(c.text);
|
|
647
|
+
const m = a && a.match(/^by\s+(\w+)/i);
|
|
648
|
+
if (m) {
|
|
649
|
+
const kind = m[1].toLowerCase();
|
|
650
|
+
const details = (c.children || []).filter((x) => !isNote(x)).map((x) => x.text.trim()).filter(Boolean);
|
|
651
|
+
rule.verifications.push({ kind: VERIFY_KINDS.has(kind) ? kind : "unknown", declared: kind, details });
|
|
652
|
+
rule.verify.push(details.length ? `${kind}: ${details.join("; ")}` : `by ${kind}`);
|
|
653
|
+
} else {
|
|
654
|
+
const v = a || c.children[0] && c.children[0].text || "";
|
|
655
|
+
if (v) {
|
|
656
|
+
rule.verify.push(v);
|
|
657
|
+
rule.verifications.push({ kind: "unclassified", declared: null, details: [v] });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
} else if (kw === "id") {
|
|
661
|
+
const v = rest(c.text) || c.children[0] && c.children[0].text;
|
|
662
|
+
if (v) {
|
|
663
|
+
rule.id = v;
|
|
664
|
+
rule.stableId = v;
|
|
665
|
+
}
|
|
666
|
+
} else if (isNote(c)) rule.notes.push(parseNoteNode(c));
|
|
586
667
|
}
|
|
587
668
|
rule.verify = rule.verify.filter(Boolean);
|
|
588
669
|
}
|
|
@@ -647,6 +728,9 @@ function parseIntent(source) {
|
|
|
647
728
|
handlers: [],
|
|
648
729
|
// Decisions, rules, process (Gap 4)
|
|
649
730
|
decisions: [],
|
|
731
|
+
properties: [],
|
|
732
|
+
scenarios: [],
|
|
733
|
+
evaluations: [],
|
|
650
734
|
// Governance: waivers , governed exceptions to blocking diagnostics (Gap 5)
|
|
651
735
|
waivers: [],
|
|
652
736
|
// Data purpose + privacy , governed data elements (Gap 6)
|
|
@@ -875,6 +959,15 @@ function parseIntent(source) {
|
|
|
875
959
|
case "decision":
|
|
876
960
|
ast.decisions.push(parseDecision(arg, node2));
|
|
877
961
|
break;
|
|
962
|
+
case "property":
|
|
963
|
+
ast.properties.push(parseProperty(arg, node2));
|
|
964
|
+
break;
|
|
965
|
+
case "scenario":
|
|
966
|
+
ast.scenarios.push(parseScenario(arg, node2));
|
|
967
|
+
break;
|
|
968
|
+
case "evaluation":
|
|
969
|
+
ast.evaluations.push(parseEvaluation(arg, node2));
|
|
970
|
+
break;
|
|
878
971
|
// ── System profile ──
|
|
879
972
|
case "capability": {
|
|
880
973
|
const cap = { name: arg, description: null, implements: [], line: node2.line };
|
|
@@ -1309,7 +1402,7 @@ var FACTORS = [
|
|
|
1309
1402
|
const scoped = len(ast.scope?.include) + len(ast.scope?.exclude);
|
|
1310
1403
|
if (scoped) return { verdict: "satisfied", evidence: `context boundary declared (scope: ${len(ast.scope.include)} include / ${len(ast.scope.exclude)} exclude)` };
|
|
1311
1404
|
if (len(ast.requires)) return { verdict: "partial", evidence: "dependencies declared but no explicit scope boundary", fix: "Declare `scope include/exclude` so the context window is curated, not unbounded." };
|
|
1312
|
-
return { verdict: "absent", evidence: "no scope declared", fix: "Add a `scope` block to bound what belongs in context (
|
|
1405
|
+
return { verdict: "absent", evidence: "no scope declared", fix: "Add a `scope` block to bound what belongs in context (ThunderLens/Focus narrows it further)." };
|
|
1313
1406
|
}
|
|
1314
1407
|
},
|
|
1315
1408
|
{
|
|
@@ -1977,7 +2070,7 @@ function toDesignTokens(ast) {
|
|
|
1977
2070
|
});
|
|
1978
2071
|
for (const t of si.tokens) {
|
|
1979
2072
|
const leaf = { $value: coerceValue(t.value), $type: tokenType(t.path) };
|
|
1980
|
-
if (!isKnownPath(t.path)) leaf.$extensions = { "dev.
|
|
2073
|
+
if (!isKnownPath(t.path)) leaf.$extensions = { "dev.intentlanguage": { canonical: false } };
|
|
1981
2074
|
setPath(root, t.path, leaf);
|
|
1982
2075
|
}
|
|
1983
2076
|
}
|
|
@@ -1985,7 +2078,7 @@ function toDesignTokens(ast) {
|
|
|
1985
2078
|
$description: `Design tokens for ${ast.title || ast.mission || "intent"} (generated from style_intent by @skillstech/thunderlang)`,
|
|
1986
2079
|
...root,
|
|
1987
2080
|
$extensions: {
|
|
1988
|
-
"dev.
|
|
2081
|
+
"dev.intentlanguage": {
|
|
1989
2082
|
schema: DESIGN_TOKENS_SCHEMA,
|
|
1990
2083
|
format: "W3C Design Tokens (DTCG)",
|
|
1991
2084
|
source: ast.mission || null,
|
|
@@ -2194,7 +2287,7 @@ function notesSummary(ast) {
|
|
|
2194
2287
|
byLens
|
|
2195
2288
|
};
|
|
2196
2289
|
}
|
|
2197
|
-
var COMPILER_VERSION = "0.
|
|
2290
|
+
var COMPILER_VERSION = "0.2.0";
|
|
2198
2291
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
2199
2292
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
2200
2293
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -2687,13 +2780,15 @@ function buildProof(ast, { sourceFile, sourceHash, targetsRequested, targetsGene
|
|
|
2687
2780
|
id: g.id,
|
|
2688
2781
|
text: g.statement,
|
|
2689
2782
|
status: g.verify.length > 0 || verifiedText ? "planned" : "needs_verification",
|
|
2690
|
-
evidence: g.verify
|
|
2783
|
+
evidence: g.verify,
|
|
2784
|
+
verifications: g.verifications || []
|
|
2691
2785
|
})),
|
|
2692
2786
|
neverRules: ast.neverRules.map((n) => ({
|
|
2693
2787
|
id: n.id,
|
|
2694
2788
|
text: n.statement,
|
|
2695
2789
|
status: n.verify.length > 0 ? "planned" : "needs_verification",
|
|
2696
|
-
evidence: n.verify
|
|
2790
|
+
evidence: n.verify,
|
|
2791
|
+
verifications: n.verifications || []
|
|
2697
2792
|
})),
|
|
2698
2793
|
errors: (ast.errors || []).map((e) => ({ name: e.name })),
|
|
2699
2794
|
examples: (ast.examples || []).map((ex) => ({ given: ex.given, expect: ex.expect })),
|
|
@@ -2750,7 +2845,7 @@ function intentProofJsonSchema() {
|
|
|
2750
2845
|
sourceProduct: { type: "string" },
|
|
2751
2846
|
missionName: { type: ["string", "null"] },
|
|
2752
2847
|
sourceFile: { type: "string" },
|
|
2753
|
-
// sha256:<hex> , the source fingerprint `
|
|
2848
|
+
// sha256:<hex> , the source fingerprint `thunder verify` re-checks for drift/tampering.
|
|
2754
2849
|
sourceHash: { type: "string", pattern: "^sha256:[0-9a-f]{64}$" },
|
|
2755
2850
|
compilerVersion: { type: "string" },
|
|
2756
2851
|
generatedAt: { type: ["string", "null"] },
|
|
@@ -2862,7 +2957,7 @@ function renderLensDoc(ast, lens) {
|
|
|
2862
2957
|
const L = [];
|
|
2863
2958
|
L.push(`# ${m} , for the ${lens} reader`, "");
|
|
2864
2959
|
L.push(
|
|
2865
|
-
`>
|
|
2960
|
+
`> ThunderLens \`${lens}\` notes are woven in below. They explain meaning for this`,
|
|
2866
2961
|
`> audience; they are documentation, not verification.`,
|
|
2867
2962
|
""
|
|
2868
2963
|
);
|
|
@@ -3104,7 +3199,7 @@ function getHover(source, position = {}) {
|
|
|
3104
3199
|
target: lens,
|
|
3105
3200
|
kind: "note_lens",
|
|
3106
3201
|
title: `note ${lens}`,
|
|
3107
|
-
description: LENS_INFO[lens] || "An
|
|
3202
|
+
description: LENS_INFO[lens] || "An ThunderLens reader lens.",
|
|
3108
3203
|
examples: [],
|
|
3109
3204
|
relatedSuggestions: []
|
|
3110
3205
|
}
|
|
@@ -3904,7 +3999,7 @@ var LANG_EXT = {
|
|
|
3904
3999
|
ruby: "rb",
|
|
3905
4000
|
perl: "pl"
|
|
3906
4001
|
};
|
|
3907
|
-
function liftSource(source, { language = "typescript", file = "", seeds } = {}) {
|
|
4002
|
+
function liftSource(source, { language = "typescript", file = "", seeds = void 0 } = {}) {
|
|
3908
4003
|
const key = String(language).toLowerCase();
|
|
3909
4004
|
const adapter = ADAPTERS[key];
|
|
3910
4005
|
if (!adapter) {
|
|
@@ -4051,7 +4146,7 @@ function checkDrift(intentText, codeSource, { language = "typescript" } = {}) {
|
|
|
4051
4146
|
add("warning", "INTENT_DRIFT_STALE_PROOF", "The approved intent was edited after approval (hash mismatch). Re-approve it.");
|
|
4052
4147
|
}
|
|
4053
4148
|
} else {
|
|
4054
|
-
add("info", "INTENT_DRIFT_NOT_APPROVED", "This intent has no approval block. Approve it first:
|
|
4149
|
+
add("info", "INTENT_DRIFT_NOT_APPROVED", "This intent has no approval block. Approve it first: thunder approve <file>.");
|
|
4055
4150
|
}
|
|
4056
4151
|
const lift = liftSource(codeSource, { language });
|
|
4057
4152
|
if (!lift.ok) {
|
|
@@ -5677,7 +5772,7 @@ var IL_AUTHOR_RULE_ROWS = [
|
|
|
5677
5772
|
{ ruleId: "IL-SEC-002", area: "security", severity: "blocker", blocks: ["release"], summary: "API returns a secret with no auth requirement." },
|
|
5678
5773
|
{ ruleId: "IL-TYPE-001", area: "type", severity: "info", blocks: [], summary: "Field uses an unrecognized (likely mistyped) type." },
|
|
5679
5774
|
// 12-Factor Agents conformance (twelve-factor-v1). Advisory: an intent's alignment with the
|
|
5680
|
-
// humanlayer/12-factor-agents principles. `
|
|
5775
|
+
// humanlayer/12-factor-agents principles. `thunder twelve-factor` scores these.
|
|
5681
5776
|
{ ruleId: "IL-12F-01", area: "twelve-factor", severity: "info", blocks: [], summary: "F1 Natural language to tool calls: model dispatch as a decision/command." },
|
|
5682
5777
|
{ ruleId: "IL-12F-02", area: "twelve-factor", severity: "info", blocks: [], summary: "F2 Own your prompts: behavior is an owned, guaranteed contract, not a black box." },
|
|
5683
5778
|
{ ruleId: "IL-12F-03", area: "twelve-factor", severity: "info", blocks: [], summary: "F3 Own your context window: declare a scope boundary." },
|
|
@@ -5703,8 +5798,8 @@ var IL_CORE_RULE_ROWS = [
|
|
|
5703
5798
|
{ ruleId: "unknown-block", area: "core", severity: "info", blocks: [], summary: "Unrecognized top-level block." },
|
|
5704
5799
|
{ ruleId: "INTENT-ARCH-001", area: "architecture", severity: "warning", blocks: [], summary: "Architecture rule not understood." },
|
|
5705
5800
|
{ ruleId: "INTENT-AI-010", area: "ai", severity: "warning", blocks: [], summary: "Unsupported implementation scope." },
|
|
5706
|
-
{ ruleId: "INTENT_NOTE_UNKNOWN_LENS", area: "note", severity: "info", blocks: [], summary: "
|
|
5707
|
-
{ ruleId: "INTENT_NOTE_EMPTY", area: "note", severity: "info", blocks: [], summary: "
|
|
5801
|
+
{ ruleId: "INTENT_NOTE_UNKNOWN_LENS", area: "note", severity: "info", blocks: [], summary: "ThunderLens note uses an unknown lens." },
|
|
5802
|
+
{ ruleId: "INTENT_NOTE_EMPTY", area: "note", severity: "info", blocks: [], summary: "ThunderLens note is empty." }
|
|
5708
5803
|
];
|
|
5709
5804
|
var RULE_PHASES = ["author", "verify"];
|
|
5710
5805
|
var RULE_OWNERS = ["IL", "OT"];
|
|
@@ -6088,21 +6183,23 @@ function compileExpr(src) {
|
|
|
6088
6183
|
function evalExpr(src, inputs = {}) {
|
|
6089
6184
|
return compileExpr(src)(inputs);
|
|
6090
6185
|
}
|
|
6186
|
+
var C_LOGIC = { and: (a, b) => `(${a} && ${b})`, or: (a, b) => `(${a} || ${b})`, not: (a) => `!${a}`, bool: (v) => String(v) };
|
|
6091
6187
|
var DIALECTS = {
|
|
6092
|
-
js: { eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
6093
|
-
csharp: { eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
6094
|
-
java: { eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" }
|
|
6188
|
+
js: { ...C_LOGIC, eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
6189
|
+
csharp: { ...C_LOGIC, eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
6190
|
+
java: { ...C_LOGIC, eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" },
|
|
6191
|
+
python: { and: (a, b) => `(${a} and ${b})`, or: (a, b) => `(${a} or ${b})`, not: (a) => `(not ${a})`, bool: (v) => v ? "True" : "False", eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `${x} in [${list.join(", ")}]`, nil: "None" }
|
|
6095
6192
|
};
|
|
6096
6193
|
function renderExpr(src, inputs, D) {
|
|
6097
6194
|
const known = new Set(inputs);
|
|
6098
6195
|
const r = (n) => {
|
|
6099
6196
|
switch (n.k) {
|
|
6100
6197
|
case "or":
|
|
6101
|
-
return
|
|
6198
|
+
return D.or(r(n.a), r(n.b));
|
|
6102
6199
|
case "and":
|
|
6103
|
-
return
|
|
6200
|
+
return D.and(r(n.a), r(n.b));
|
|
6104
6201
|
case "not":
|
|
6105
|
-
return
|
|
6202
|
+
return D.not(r(n.a));
|
|
6106
6203
|
case "neg":
|
|
6107
6204
|
return `-${r(n.a)}`;
|
|
6108
6205
|
case "arith":
|
|
@@ -6116,7 +6213,7 @@ function renderExpr(src, inputs, D) {
|
|
|
6116
6213
|
case "list":
|
|
6117
6214
|
return `[${n.items.map(r).join(", ")}]`;
|
|
6118
6215
|
case "lit":
|
|
6119
|
-
return typeof n.v === "string" ? JSON.stringify(n.v) : String(n.v);
|
|
6216
|
+
return typeof n.v === "string" ? JSON.stringify(n.v) : typeof n.v === "boolean" ? D.bool(n.v) : String(n.v);
|
|
6120
6217
|
case "ref":
|
|
6121
6218
|
return known.has(n.path) || known.has(n.path.split(".")[0]) ? n.path : JSON.stringify(n.path);
|
|
6122
6219
|
default:
|
|
@@ -6237,7 +6334,7 @@ function runTests(ast) {
|
|
|
6237
6334
|
const inputs = coerce(c.given);
|
|
6238
6335
|
const run = evaluateDecision(dec, inputs);
|
|
6239
6336
|
const pass = c.expect == null || String(run.result) === String(c.expect);
|
|
6240
|
-
results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
|
|
6337
|
+
results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, matched: run.matched, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
|
|
6241
6338
|
} else if (lc) {
|
|
6242
6339
|
const sim = simulateLifecycle(lc, c.events || []);
|
|
6243
6340
|
const passState = c.expect == null || sim.finalState === c.expect;
|
|
@@ -7246,7 +7343,7 @@ function fingerprint(parts) {
|
|
|
7246
7343
|
return `fp_${h.toString(16)}`;
|
|
7247
7344
|
}
|
|
7248
7345
|
function makeScope({ type = "custom", title = null, seeds = [], projectId = null, createdBy = null, createdAt = null, ...rest2 } = {}) {
|
|
7249
|
-
if (!SCOPE_TYPES.includes(type)) throw new Error(`
|
|
7346
|
+
if (!SCOPE_TYPES.includes(type)) throw new Error(`thunder focus: unknown scope type "${type}"`);
|
|
7250
7347
|
return {
|
|
7251
7348
|
schema: FOCUS_SCHEMA,
|
|
7252
7349
|
scopeId: `scope.${type}.${fingerprint([type, ...seeds].map(String))}`,
|
|
@@ -8071,7 +8168,7 @@ function payloadOf(seq, entry, prev) {
|
|
|
8071
8168
|
};
|
|
8072
8169
|
}
|
|
8073
8170
|
function record(ledger, entry) {
|
|
8074
|
-
if (!entry || !ENTRY_TYPES.includes(entry.type)) throw new Error(`
|
|
8171
|
+
if (!entry || !ENTRY_TYPES.includes(entry.type)) throw new Error(`thunder ledger: unknown entry type "${entry?.type}"`);
|
|
8075
8172
|
const base = ledger && Array.isArray(ledger.entries) ? ledger : emptyLedger();
|
|
8076
8173
|
const payload = payloadOf(base.entries.length, entry, base.head);
|
|
8077
8174
|
const hash = sha256(JSON.stringify(payload));
|
|
@@ -8218,7 +8315,7 @@ function emptyEventLog() {
|
|
|
8218
8315
|
}
|
|
8219
8316
|
function recordEvent(log, event) {
|
|
8220
8317
|
if (!event || !INTENT_AI_EVENTS.includes(event.type)) {
|
|
8221
|
-
throw new Error(`
|
|
8318
|
+
throw new Error(`thunder ai events: unknown event type "${event?.type}"`);
|
|
8222
8319
|
}
|
|
8223
8320
|
const base = log && Array.isArray(log.events) ? log : emptyEventLog();
|
|
8224
8321
|
return { ...base, events: [...base.events, event] };
|
|
@@ -8582,14 +8679,14 @@ function buildGuard(ast, { denyResults, mask = "[redacted]" } = {}) {
|
|
|
8582
8679
|
}
|
|
8583
8680
|
function decide(name, inputs) {
|
|
8584
8681
|
const d = decisions.get(name);
|
|
8585
|
-
if (!d) throw new Error(`
|
|
8682
|
+
if (!d) throw new Error(`thunder guard: no decision "${name}"`);
|
|
8586
8683
|
const r = evaluateDecision(d, inputs || {});
|
|
8587
8684
|
return { ...r, allowed: !isDenied(r.result) };
|
|
8588
8685
|
}
|
|
8589
8686
|
function assertAllowed(name, inputs) {
|
|
8590
8687
|
const r = decide(name, inputs);
|
|
8591
8688
|
if (!r.allowed) {
|
|
8592
|
-
const e = new Error(`
|
|
8689
|
+
const e = new Error(`thunder guard: decision "${name}" denied the action (result: ${r.result})`);
|
|
8593
8690
|
e.code = "INTENT_GUARD_DENIED";
|
|
8594
8691
|
e.decision = name;
|
|
8595
8692
|
e.result = r.result;
|