lemmascript-claimcheck 0.1.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/DESIGN.md ADDED
@@ -0,0 +1,66 @@
1
+ # DESIGN — vetting intent against spec in LemmaScript
2
+
3
+ ## The gap
4
+
5
+ LemmaScript proves a function's `//@ requires`/`//@ ensures`. It cannot prove the spec *means* what the author thinks it means. A spec can be verified and still guarantee less — or other — than intended: `ensures \result >= a` proves cleanly but does not say "the larger of a and b."
6
+
7
+ `claimcheck` closes that gap for Dafny lemmas by round-tripping: informalize the formal contract back to English **blind** (without seeing the stated requirement), then compare. This tool applies the same round-trip to LemmaScript, with the natural-language claim co-located as a `//@ contract` annotation on the function:
8
+
9
+ ```ts
10
+ //@ contract Clamps x into the inclusive range [lo, hi]; the result never falls outside it.
11
+ //@ requires lo <= hi
12
+ //@ ensures \result >= lo && \result <= hi
13
+ export function clamp(x: number, lo: number, hi: number): number { ... }
14
+ ```
15
+
16
+ Because the same author writes both the prose and the spec, a mismatch is a **self-consistency** failure: their mental model and their formalization diverged. That is arguably more useful than checking an externally-supplied requirement — it catches the author misleading themselves (or a reader) about what the proof buys.
17
+
18
+ ## Why co-location
19
+
20
+ `claimcheck`'s weakest link is its external `{requirement, lemmaName}` mapping JSON — a hand-authored file that drifts from the code. Putting the claim *on the function* as `//@ contract` makes the mapping implicit: each function carrying a contract is exactly one claim, and `lsc extract` emits the pairing for free. There is no second registry to maintain. (Contrast `lemmafit`, which hand-authors a `SPEC.yaml` plus a `guarantees.json` mapping; here both are derived.)
21
+
22
+ ## The trust model
23
+
24
+ A guarantee in the output report rests on three independent legs:
25
+
26
+ 1. **verified** — `lsc check` discharges the `requires`/`ensures` (LemmaScript's job).
27
+ 2. **specified** — there is a formal contract behind the English, not just prose.
28
+ 3. **vetted** — the round-trip confirms the English faithfully describes that spec.
29
+
30
+ This tool establishes only leg 3. It **assumes** leg 1 (verification is run separately; the report header says so) and it does **not** re-check leg 2's lowering — that LemmaScript's spec compiles faithfully to Dafny/Lean is LemmaScript's own guarantee, not the gap being filled here. So the trust boundary is precise: *given a verified spec, does the contract describe it?* A `//@ contract` with no `requires`/`ensures` has leg 2 missing — it is reported as a **gap**, a claim with nothing formal behind it.
31
+
32
+ ## Decisions
33
+
34
+ - **Formal side is LS-level.** The round-trip informalizes a synthesized `function … requires … ensures …` block built from the IR's signature and spec strings — not generated Dafny. Checking the generated Dafny would test lowering fidelity, which is out of scope (see leg 2). The synthesized block keeps the tool a pure consumer of `lsc extract`.
35
+ - **Reuse `claimcheck` unchanged.** It is shelled as a sibling via `--stdin`. Its prompts are Dafny-flavored, but they read the synthesized block (LemmaScript spec syntax, TS types, `\result`) without adaptation — no fork or special-casing was needed.
36
+ - **`//@ contract` lives in the public extractor.** It is a verified-docstring directive in the `//@` namespace, ignored by the prover (never parsed as a spec expression). The extractor surfaces it so this tool can stay a downstream consumer, exactly like `lsc extract` feeds `lemmascript-guard`.
37
+
38
+ ## Architecture
39
+
40
+ A consumer of two siblings, no logic of its own beyond the adapter and the report:
41
+
42
+ ```
43
+ core.ts ──lsc extract──▶ Raw IR (carries //@ contract per function)
44
+ │ collect: backed claims + gaps
45
+ {requirement, lemmaName, dafnyCode: synth block} ──claimcheck --stdin──▶ verdicts
46
+ │ merge
47
+ domain.guarantees.json + domain.guarantees.md
48
+ ```
49
+
50
+ `RawModule` is imported type-only (the IR shape); its values arrive over the subprocess. The report is named after the source file, mirroring `lemmascript-guard`'s `core.guarded.ts`.
51
+
52
+ ## The feedback loop
53
+
54
+ A disputed verdict points to one of two fixes, and the `weakeningType` usually says which:
55
+
56
+ - the spec proves less than the prose claims → **strengthen the `ensures`** (then re-verify), or
57
+ - the prose overstates what was intended → **fix the `//@ contract`**.
58
+
59
+ Either way the report is the prompt: write intent in English, prove a spec, and let the round-trip surface where they disagree. This composes with an agent loop — the disputed entries and their discrepancies are direct, actionable feedback.
60
+
61
+ ## Limitations
62
+
63
+ - **Verification is assumed**, not enforced. The report vets faithfulness only; an unverified file can still produce a clean-looking report. Run `lsc check` first.
64
+ - **The unit is one function.** Cross-function properties (a state-machine invariant) are captured only insofar as each action's `//@ ensures` carries them; there is no whole-module claim.
65
+ - **Function names leak intent** into the otherwise-blind Pass 1 (`largest`, `clamp`). The round-trip tolerates this, as `claimcheck` does, but a deliberately misleading name could anchor a back-translation.
66
+ - **The check is only as honest as the contracts are load-bearing.** On a toy spec the round-trip is correct-by-construction theater; its value shows on a spec where a weakened `ensures` would be a genuine, catchable lie.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 midspiral
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # lemmascript-claimcheck
2
+
3
+ `claimcheck` for [LemmaScript](https://lemmascript.com): does a function's `//@ requires`/`//@ ensures` actually say what its plain-English `//@ contract` claims?
4
+
5
+ LemmaScript proves the formal spec; it can't prove the spec *means* what you think. This vets the gap. Write intent in English next to the proof:
6
+
7
+ ```ts
8
+ //@ contract Clamps x into the inclusive range [lo, hi]; the result never falls outside it.
9
+ //@ requires lo <= hi
10
+ //@ ensures \result >= lo && \result <= hi
11
+ export function clamp(x: number, lo: number, hi: number): number { ... }
12
+ ```
13
+
14
+ `lemmascript-claimcheck` informalizes the `requires`/`ensures` **blind** (without seeing the `//@ contract`) via the [claimcheck](https://github.com/metareflection/claimcheck) round-trip, then compares the back-translation to the contract. A mismatch means your proof guarantees something other than what your prose advertises — or vice versa.
15
+
16
+ It orchestrates two CLIs: it shells `lsc extract` (LemmaScript's frontend, now carrying `//@ contract` strings) for the Raw IR, and `claimcheck --stdin` for the round-trip. Both are expected on your `PATH`.
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ npm install -g lemmascript-claimcheck
22
+ npm install -g lemmascript claimcheck # the two tools it drives (peer prerequisites)
23
+ ```
24
+
25
+ It needs `lemmascript >= 0.5.7` (introduces the `//@ contract` annotation) and `claimcheck >= 0.5.0` (the `--lang` framing) — declared as `peerDependencies`, so npm warns on a version mismatch.
26
+
27
+ ## Usage
28
+
29
+ ```sh
30
+ lemmascript-claimcheck examples/demo.ts --bedrock
31
+ ```
32
+
33
+ For `domain.ts` this writes `domain.guarantees.json` and `domain.guarantees.md` next to the source: a trust manifest of what the module promises in English, each promise vetted against its spec, with disputed and unbacked claims flagged.
34
+
35
+ ```
36
+ lemmascript-claimcheck <file.ts> [--out <dir>] [--json] [--claims-only] [<claimcheck flags>]
37
+ ```
38
+
39
+ - `<file.ts>` is the leading positional. Every other flag (and its value) is forwarded to claimcheck.
40
+ - `--claims-only` prints the claims that would be sent (no API call) — useful for inspection.
41
+ - `--out <dir>` writes the reports elsewhere; default is next to the source.
42
+
43
+ ## Configuring the backend
44
+
45
+ The backend and models are claimcheck's concern — pick any setup it supports and pass it through:
46
+
47
+ | Layer | What | Example |
48
+ |-------|------|---------|
49
+ | `$CLAIMCHECK` | which claimcheck to run | default: `claimcheck` on PATH; or a dev checkout's `bin/claimcheck.js` (run via node) |
50
+ | `$CLAIMCHECK_ARGS` | persistent default flags | `export CLAIMCHECK_ARGS="--bedrock"` |
51
+ | CLI passthrough | per-run flags (override the default) | `... examples/demo.ts --claude-code` |
52
+
53
+ So any of these work: direct API (`ANTHROPIC_API_KEY`), `--bedrock`, `--vertex`, the in-claimcheck `--claude-code` (reuses your Claude Code auth), `--model`/`--compare-model`/`--informalize-model`, `--single-prompt`. Use `--` to end this tool's own flag parsing.
54
+
55
+ ## Output
56
+
57
+ Each `//@ contract`-carrying function becomes one entry:
58
+
59
+ | Status | Meaning |
60
+ |--------|---------|
61
+ | **confirmed** | the spec faithfully expresses the contract |
62
+ | **disputed** | the spec says less/other than the contract (with `weakeningType` + discrepancy) |
63
+ | **gap** | a `//@ contract` with no `//@ requires`/`//@ ensures` to back it |
64
+
65
+ Verification itself is **assumed** (run `lsc check` to discharge the proofs); the report header says so.
66
+
67
+ ## Example
68
+
69
+ `examples/demo.ts` carries one faithful contract (`clamp`), one that over-claims against a weakened spec (`largest`), and one unbacked claim (`double`) — exercising all three verdicts.
70
+
71
+ ## Development
72
+
73
+ ```sh
74
+ git clone https://github.com/midspiral/lemmascript-claimcheck && cd lemmascript-claimcheck
75
+ npm install
76
+ npm run build # tsc → dist/
77
+ ```
78
+
79
+ To run against local checkouts instead of the published tools, point the env overrides at them:
80
+
81
+ ```sh
82
+ LEMMASCRIPT=../LemmaScript CLAIMCHECK=../claimcheck/bin/claimcheck.js \
83
+ node dist/cli.js examples/demo.ts --bedrock
84
+ ```
85
+
86
+ `$LEMMASCRIPT` runs the checkout's `lsc` source through `tsx`; `$CLAIMCHECK` points at a checkout's `bin/claimcheck.js`.
@@ -0,0 +1,36 @@
1
+ // Shell `claimcheck --stdin` for the blind round-trip. The backend and models are
2
+ // claimcheck's concern — we just forward flags, so any setup works (direct API,
3
+ // --bedrock, --vertex, the in-claimcheck --claude-code, --model/--compare-model,
4
+ // --single-prompt, ...).
5
+ //
6
+ // Configuration, lowest to highest precedence:
7
+ // (default) shell `claimcheck` from PATH (install with `npm i -g claimcheck`).
8
+ // $CLAIMCHECK override: a command on PATH, or a path to a dev checkout's
9
+ // bin/claimcheck.js (run via node).
10
+ // $CLAIMCHECK_ARGS default flags, space-separated (a project's persistent
11
+ // backend choice, e.g. "--bedrock" or "--claude-code").
12
+ // passthrough per-invocation flags from the CLI (override the env default).
13
+ import { execFileSync } from "child_process";
14
+ function resolveCmd() {
15
+ const env = process.env.CLAIMCHECK;
16
+ if (env)
17
+ return env.endsWith(".js") ? { cmd: "node", pre: [env] } : { cmd: env, pre: [] };
18
+ return { cmd: "claimcheck", pre: [] };
19
+ }
20
+ /** `--lang lemmascript` (the formal side is a function contract, not a Dafny lemma),
21
+ * then $CLAIMCHECK_ARGS, then CLI passthrough — later wins, so either can override. */
22
+ export function claimcheckArgs(passthrough = []) {
23
+ const envArgs = (process.env.CLAIMCHECK_ARGS ?? "").split(/\s+/).filter(Boolean);
24
+ return ["--stdin", "--lang", "lemmascript", ...envArgs, ...passthrough];
25
+ }
26
+ export function runClaimcheck(claims, domain, passthrough = []) {
27
+ if (claims.length === 0)
28
+ return [];
29
+ const { cmd, pre } = resolveCmd();
30
+ const out = execFileSync(cmd, [...pre, ...claimcheckArgs(passthrough)], {
31
+ input: JSON.stringify({ claims, domain }),
32
+ encoding: "utf8",
33
+ maxBuffer: 64 * 1024 * 1024,
34
+ });
35
+ return (JSON.parse(out).results ?? []);
36
+ }
package/dist/claims.js ADDED
@@ -0,0 +1,41 @@
1
+ export function renderSig(fn) {
2
+ const tp = fn.typeParams.length ? `<${fn.typeParams.join(", ")}>` : "";
3
+ const params = fn.params.map((p) => `${p.name}: ${p.tsType}`).join(", ");
4
+ return `${fn.name}${tp}(${params}): ${fn.returnType}`;
5
+ }
6
+ /** The formal side fed to claimcheck — a Dafny-ish block built from the IR.
7
+ * LemmaScript spec syntax is near-identical to Dafny, so claimcheck's
8
+ * informalizer reads it directly; `\result` denotes the return value. */
9
+ export function synthContract(fn) {
10
+ const lines = [`function ${renderSig(fn)}`];
11
+ for (const r of fn.requires)
12
+ lines.push(` requires ${r}`);
13
+ for (const e of fn.ensures)
14
+ lines.push(` ensures ${e}`);
15
+ return lines.join("\n");
16
+ }
17
+ export function collectClaims(mod) {
18
+ const backed = [];
19
+ const gaps = [];
20
+ for (const fn of mod.functions) {
21
+ const contract = fn.contract ?? [];
22
+ if (contract.length === 0)
23
+ continue;
24
+ const requirement = contract.join(" ");
25
+ if (fn.requires.length === 0 && fn.ensures.length === 0) {
26
+ gaps.push({
27
+ specId: fn.name,
28
+ requirement,
29
+ reason: "//@ contract present but no //@ requires/ensures to back it",
30
+ });
31
+ continue;
32
+ }
33
+ backed.push({
34
+ fn,
35
+ requirement,
36
+ spec: { signature: renderSig(fn), requires: fn.requires, ensures: fn.ensures },
37
+ dafnyCode: synthContract(fn),
38
+ });
39
+ }
40
+ return { backed, gaps };
41
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ // lemmascript-claimcheck — vet that each `//@ contract` faithfully describes the
3
+ // function's formal `//@ requires`/`//@ ensures`, then emit a guarantees report.
4
+ //
5
+ // lemmascript-claimcheck <file.ts> [--out <dir>] [--json] [--claims-only]
6
+ // [<any claimcheck flags>]
7
+ //
8
+ // `<file.ts>` is the leading positional; every other flag (and its value) is
9
+ // forwarded to claimcheck unchanged — so the backend/model are configured there
10
+ // (--bedrock, --vertex, --claude-code, --model, --compare-model, --single-prompt,
11
+ // ...), or persistently via $CLAIMCHECK_ARGS. Use `--` to end our own parsing.
12
+ //
13
+ // For `domain.ts` writes `domain.guarantees.json` + `domain.guarantees.md`
14
+ // next to the source (or under --out).
15
+ import { existsSync, writeFileSync } from "fs";
16
+ import * as path from "path";
17
+ import { extractModule } from "./extract.js";
18
+ import { collectClaims } from "./claims.js";
19
+ import { runClaimcheck } from "./claimcheck.js";
20
+ import { buildReport, renderMarkdown } from "./guarantees.js";
21
+ function usage() {
22
+ console.error("usage: lemmascript-claimcheck <file.ts> [--out <dir>] [--json] [--claims-only] [<claimcheck flags>]");
23
+ process.exit(1);
24
+ }
25
+ let file;
26
+ let outDir;
27
+ let json = false;
28
+ let claimsOnly = false;
29
+ const passthrough = []; // everything we don't own → forwarded to claimcheck
30
+ const argv = process.argv.slice(2);
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const a = argv[i];
33
+ if (a === "--") {
34
+ passthrough.push(...argv.slice(i + 1));
35
+ break;
36
+ }
37
+ else if (a === "--out") {
38
+ outDir = argv[++i];
39
+ }
40
+ else if (a === "--json") {
41
+ json = true;
42
+ }
43
+ else if (a === "--claims-only") {
44
+ claimsOnly = true;
45
+ }
46
+ else if (!file && !a.startsWith("-")) {
47
+ file = a;
48
+ }
49
+ else {
50
+ passthrough.push(a); // any other flag (or its value) goes to claimcheck
51
+ }
52
+ }
53
+ if (!file)
54
+ usage();
55
+ const abs = path.resolve(file);
56
+ if (!existsSync(abs)) {
57
+ console.error(`file not found: ${abs}`);
58
+ process.exit(1);
59
+ }
60
+ const mod = extractModule(abs);
61
+ const { backed, gaps } = collectClaims(mod);
62
+ const domain = path.basename(abs, ".ts");
63
+ const claims = backed.map((b) => ({ requirement: b.requirement, lemmaName: b.fn.name, dafnyCode: b.dafnyCode }));
64
+ if (claimsOnly) {
65
+ console.log(JSON.stringify({ domain, claims, gaps }, null, 2));
66
+ process.exit(0);
67
+ }
68
+ const results = runClaimcheck(claims, domain, passthrough);
69
+ const generated = new Date().toISOString().split("T")[0];
70
+ const report = buildReport(file, backed, results, gaps, generated);
71
+ const dir = outDir ?? path.dirname(abs);
72
+ const jsonPath = path.join(dir, `${domain}.guarantees.json`);
73
+ const mdPath = path.join(dir, `${domain}.guarantees.md`);
74
+ writeFileSync(jsonPath, JSON.stringify(report, null, 2) + "\n");
75
+ writeFileSync(mdPath, renderMarkdown(report) + "\n");
76
+ if (json) {
77
+ console.log(JSON.stringify(report, null, 2));
78
+ }
79
+ else {
80
+ const s = report.summary;
81
+ console.log(`${domain}: ${s.confirmed} confirmed, ${s.disputed} disputed, ${s.gaps} gaps`);
82
+ console.log(`→ ${mdPath}`);
83
+ console.log(`→ ${jsonPath}`);
84
+ }
@@ -0,0 +1,22 @@
1
+ // Raw IR via LemmaScript's public `lsc extract`. By default we shell `lsc` from
2
+ // PATH (install with `npm i -g lemmascript`); set $LEMMASCRIPT to a sibling
3
+ // checkout to run its source through tsx instead (dev). The `//@ contract`
4
+ // strings ride along on each function (lemmascript >= 0.5.7).
5
+ import { execFileSync } from "child_process";
6
+ import * as path from "path";
7
+ function lscInvocation() {
8
+ const env = process.env.LEMMASCRIPT;
9
+ if (env) {
10
+ const lsc = path.join(env, "tools", "src", "lsc.ts");
11
+ return { cmd: "npx", pre: ["--prefix", path.join(env, "tools"), "tsx", lsc] };
12
+ }
13
+ return { cmd: "lsc", pre: [] };
14
+ }
15
+ export function extractModule(absFile) {
16
+ const { cmd, pre } = lscInvocation();
17
+ const json = execFileSync(cmd, [...pre, "extract", absFile], {
18
+ encoding: "utf8",
19
+ maxBuffer: 256 * 1024 * 1024,
20
+ });
21
+ return JSON.parse(json);
22
+ }
@@ -0,0 +1,103 @@
1
+ export function buildReport(file, backed, results, gaps, generated) {
2
+ const byName = new Map(results.map((r) => [r.lemmaName, r]));
3
+ const guarantees = backed.map((b) => {
4
+ const r = byName.get(b.fn.name);
5
+ const status = r?.status === "confirmed" ? "confirmed" : r?.status === "disputed" ? "disputed" : "unchecked";
6
+ const g = {
7
+ specId: b.fn.name,
8
+ function: b.fn.name,
9
+ requirement: b.requirement,
10
+ spec: b.spec,
11
+ status,
12
+ };
13
+ if (status === "disputed") {
14
+ const wt = r?.weakeningType ?? r?.comparison?.weakeningType;
15
+ const disc = r?.discrepancy ?? r?.comparison?.discrepancy;
16
+ if (wt)
17
+ g.weakeningType = wt;
18
+ if (disc)
19
+ g.discrepancy = disc;
20
+ }
21
+ const bt = r?.informalization?.naturalLanguage;
22
+ if (bt)
23
+ g.backTranslation = bt;
24
+ return g;
25
+ });
26
+ return {
27
+ file,
28
+ generated,
29
+ assumesVerified: true,
30
+ summary: {
31
+ contracts: backed.length,
32
+ confirmed: guarantees.filter((g) => g.status === "confirmed").length,
33
+ disputed: guarantees.filter((g) => g.status === "disputed").length,
34
+ gaps: gaps.length,
35
+ },
36
+ guarantees,
37
+ gaps,
38
+ };
39
+ }
40
+ function specBlock(g) {
41
+ const lines = ["```", g.spec.signature];
42
+ for (const r of g.spec.requires)
43
+ lines.push(` requires ${r}`);
44
+ for (const e of g.spec.ensures)
45
+ lines.push(` ensures ${e}`);
46
+ lines.push("```");
47
+ return lines;
48
+ }
49
+ export function renderMarkdown(report) {
50
+ const L = [];
51
+ L.push(`# Guarantees: ${report.file}`, "");
52
+ L.push(`Generated: ${report.generated}`, "");
53
+ L.push("> Verification is **assumed** (run `lsc check` to discharge the proofs). " +
54
+ "This report vets only that each `//@ contract` faithfully describes its formal " +
55
+ "`requires`/`ensures`, via claimcheck's blind round-trip.", "");
56
+ const s = report.summary;
57
+ L.push(`## Coverage`, "");
58
+ L.push(`- **${s.contracts}** backed contracts: ${s.confirmed} confirmed, ${s.disputed} disputed`);
59
+ L.push(`- **${s.gaps}** gaps (contract with no formal spec behind it)`, "");
60
+ const checked = report.guarantees.filter((g) => g.status !== "unchecked");
61
+ if (checked.length) {
62
+ L.push(`## Claimcheck Results`, "");
63
+ L.push(`| Function | Contract | Status |`, `|----------|----------|--------|`);
64
+ for (const g of report.guarantees) {
65
+ const badge = g.status === "confirmed" ? "✅ confirmed" : g.status === "disputed" ? "❌ disputed" : "— unchecked";
66
+ L.push(`| \`${g.function}\` | ${g.requirement} | ${badge} |`);
67
+ }
68
+ L.push("");
69
+ }
70
+ const confirmed = report.guarantees.filter((g) => g.status === "confirmed");
71
+ if (confirmed.length) {
72
+ L.push(`## Confirmed Guarantees`, "");
73
+ for (const g of confirmed) {
74
+ L.push(`**${g.requirement}** — \`${g.function}\``);
75
+ L.push(...specBlock(g));
76
+ if (g.backTranslation)
77
+ L.push(`- Back-translation: ${g.backTranslation}`);
78
+ L.push("");
79
+ }
80
+ }
81
+ const disputed = report.guarantees.filter((g) => g.status === "disputed");
82
+ if (disputed.length) {
83
+ L.push(`## Disputed`, "");
84
+ for (const g of disputed) {
85
+ L.push(`**${g.requirement}** — \`${g.function}\``);
86
+ if (g.weakeningType && g.weakeningType !== "none")
87
+ L.push(`- Weakening: ${g.weakeningType}`);
88
+ if (g.discrepancy)
89
+ L.push(`- Discrepancy: ${g.discrepancy}`);
90
+ if (g.backTranslation)
91
+ L.push(`- Back-translation: ${g.backTranslation}`);
92
+ L.push(...specBlock(g));
93
+ L.push("");
94
+ }
95
+ }
96
+ if (report.gaps.length) {
97
+ L.push(`## Gaps`, "");
98
+ for (const gap of report.gaps)
99
+ L.push(`- \`${gap.specId}\`: ${gap.requirement} — ${gap.reason}`);
100
+ L.push("");
101
+ }
102
+ return L.join("\n");
103
+ }
package/dist/ir.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "lemmascript-claimcheck",
3
+ "version": "0.1.0",
4
+ "description": "claimcheck for LemmaScript: vet that a function's informal //@ contract faithfully describes its verified //@ requires/ensures, and emit a guarantees report.",
5
+ "keywords": [
6
+ "lemmascript",
7
+ "claimcheck",
8
+ "formal-verification",
9
+ "verification",
10
+ "verified",
11
+ "typescript",
12
+ "specification",
13
+ "design-by-contract",
14
+ "natural-language",
15
+ "round-trip",
16
+ "llm",
17
+ "intent"
18
+ ],
19
+ "license": "MIT",
20
+ "author": "midspiral <hello@midspiral.com>",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/midspiral/lemmascript-claimcheck.git"
24
+ },
25
+ "homepage": "https://github.com/midspiral/lemmascript-claimcheck#readme",
26
+ "bugs": "https://github.com/midspiral/lemmascript-claimcheck/issues",
27
+ "type": "module",
28
+ "bin": {
29
+ "lemmascript-claimcheck": "dist/cli.js"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "DESIGN.md",
35
+ "LICENSE"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.build.json",
39
+ "typecheck": "tsc --noEmit",
40
+ "prepublishOnly": "npm run build"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^25.5.0",
44
+ "tsx": "^4.22.4",
45
+ "typescript": "^5.7.0"
46
+ },
47
+ "peerDependencies": {
48
+ "claimcheck": ">=0.5.0",
49
+ "lemmascript": ">=0.5.7"
50
+ }
51
+ }