@codacy/verity-cli 0.34.0-experimental.421a631 → 0.34.0-experimental.5235853
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 +77 -0
- package/bin/verity.js +168 -84
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -64,6 +64,78 @@ installs are untouched.
|
|
|
64
64
|
which is the ordinary case for someone whose first Verity install *is* the
|
|
65
65
|
plugin.
|
|
66
66
|
|
|
67
|
+
## [0.34.0] — 2026-09-30
|
|
68
|
+
|
|
69
|
+
**Formal verification at push: Verity can now prove a bug is real before it
|
|
70
|
+
blocks — and the code map and the knowledge loop tell the reviewer more about
|
|
71
|
+
the code around a change.**
|
|
72
|
+
|
|
73
|
+
### 🧮 Formal verification, opt-in
|
|
74
|
+
|
|
75
|
+
A third review at `git push`, next to the one-shot and the agent. It picks the
|
|
76
|
+
riskiest function the push changed, has the Verity service write a small Lean 4
|
|
77
|
+
model of it, checks the model on your machine, and replays any counterexample
|
|
78
|
+
on the real code in an isolated container with the network off. **Only a
|
|
79
|
+
reproduced bug blocks;** a counterexample that does not reproduce on the real
|
|
80
|
+
code is reported, never enforced. Most pushes get no model at all.
|
|
81
|
+
|
|
82
|
+
- **Off until you say yes.** `verity init` asks once, on a terminal, with "no"
|
|
83
|
+
as the default, and remembers either answer. `--yes` and headless runs are not
|
|
84
|
+
asked and never probe for a toolchain. `verity config formal push` turns it on
|
|
85
|
+
for a checkout, `verity config formal off` turns it off. It needs you to be
|
|
86
|
+
signed in: ranking, model writing and replay run on the Verity service. The
|
|
87
|
+
function's source is sent for the replay; no code is stored.
|
|
88
|
+
- **`verity lean install | status | remove`** manages the pinned official Lean 4
|
|
89
|
+
release under `~/.verity/lean` (≈800 MB download, 2.7 GB on disk). The
|
|
90
|
+
download is checksum-verified, abandoned after 60 s with nothing received,
|
|
91
|
+
and refused up front when the disk is short. An elan install is used when it
|
|
92
|
+
already has the pinned toolchain; Verity never makes elan download one.
|
|
93
|
+
- **On demand:** `verity prove candidates | generate | check | replay | auto`,
|
|
94
|
+
and the `/verity-prove <worry>` skill, where the words after the command are
|
|
95
|
+
what you want checked. Under the plugin, `/verity:verity-prove` arrives with
|
|
96
|
+
the plugin's next release.
|
|
97
|
+
- **A finding holds until the code changes.** Pushing the same bytes again
|
|
98
|
+
re-blocks without a new model. `verity prove waive <file>` accepts the risk
|
|
99
|
+
for the file's current bytes, and voids when they change.
|
|
100
|
+
- **Yours, never the repository's.** A `.verity/config.json` that git tracks,
|
|
101
|
+
or that is reached through a symlink, cannot switch formal verification on —
|
|
102
|
+
`verity doctor`, `verity config formal` and `verity init` say why it is off.
|
|
103
|
+
Findings and spend records a repository ships are ignored, and Lean never runs
|
|
104
|
+
on a `.verity/proofs/` workspace the repository supplies.
|
|
105
|
+
- **Admins can switch it off** per project, organization or globally with the
|
|
106
|
+
`formal` flag in the dashboard; the push then reports `disabled-by-server`.
|
|
107
|
+
|
|
108
|
+
### 🗺️ The code map
|
|
109
|
+
|
|
110
|
+
The CLI now parses TypeScript, TSX, JavaScript, Python, Go and Java with bundled
|
|
111
|
+
tree-sitter grammars (about 6 MB added to the package).
|
|
112
|
+
|
|
113
|
+
- **The reviewer sees what else touches the same state.** At commit and push,
|
|
114
|
+
the review's context names the other functions that write the state a change
|
|
115
|
+
writes, and the agent reviewer can ask for a function's callers and
|
|
116
|
+
definitions. Formal verification models the exact function, not a guess at
|
|
117
|
+
its extent.
|
|
118
|
+
- **Reads use the bytes under review:** at a push, the pushed commit; at a
|
|
119
|
+
commit, the staged files — not whatever is in the working tree.
|
|
120
|
+
- **Paths the project denies are never shown** by the code tools or in the
|
|
121
|
+
code-map context.
|
|
122
|
+
|
|
123
|
+
### 🧠 The knowledge loop
|
|
124
|
+
|
|
125
|
+
- **The reviewer says what it did with each node it was shown** — applied,
|
|
126
|
+
violated, irrelevant or unclear, each tied to a line in the change — and
|
|
127
|
+
ranking learns from it which nodes help. One scorer now chooses the nodes for
|
|
128
|
+
both the review and your agent's prompt.
|
|
129
|
+
- **A known rule said back to the agent.** When a change breaks a rule the
|
|
130
|
+
project has already learned, Verity can tell the agent which one. This ships
|
|
131
|
+
in shadow: the decision is made and recorded, and nothing is sent.
|
|
132
|
+
- **Fewer, better nodes.** The extractor's rubric was rewritten from 143
|
|
133
|
+
human-graded nodes; it no longer records what the code already says, and it
|
|
134
|
+
checks each new node against the nearest existing ones before adding it.
|
|
135
|
+
- **A failed diff in `verity memory` sync no longer drops a node edit you made.**
|
|
136
|
+
- **A failed knowledge lookup no longer fails a review.** The review runs
|
|
137
|
+
without knowledge and records that it did.
|
|
138
|
+
|
|
67
139
|
### 🎯 `/verity:init` does everything `verity init` does, in the session
|
|
68
140
|
|
|
69
141
|
Under the plugin, setup used to stop short of the terminal flow: `/verity:init`
|
|
@@ -89,6 +161,11 @@ sign-in meant leaving Claude Code for a terminal.
|
|
|
89
161
|
- **`verity telemetry install|uninstall` update `.verity/setup.json`**, so a
|
|
90
162
|
request deferred until sign-in stops being reported as still to do.
|
|
91
163
|
|
|
164
|
+
### 🔧 Fixed
|
|
165
|
+
|
|
166
|
+
- **A swapped `.verity` directory cannot redirect a write.** File writes check
|
|
167
|
+
the file they opened is the one they meant to write, after opening it.
|
|
168
|
+
|
|
92
169
|
## [0.33.3] — 2026-09-24
|
|
93
170
|
|
|
94
171
|
**The knowledge seed runs once per project, not once per checkout.**
|
package/bin/verity.js
CHANGED
|
@@ -17221,7 +17221,7 @@ function readConfigFile(path) {
|
|
|
17221
17221
|
return {
|
|
17222
17222
|
git_moments: [...new Set(moments)],
|
|
17223
17223
|
...source2 ? { git_moments_source: source2 } : {},
|
|
17224
|
-
...raw.formal === "push" ? { formal:
|
|
17224
|
+
...raw.formal === "push" || raw.formal === "off" ? { formal: raw.formal } : {}
|
|
17225
17225
|
};
|
|
17226
17226
|
} catch {
|
|
17227
17227
|
return DEFAULTS;
|
|
@@ -17250,10 +17250,10 @@ function readProjectConfigRaw() {
|
|
|
17250
17250
|
function readProjectConfig() {
|
|
17251
17251
|
const cfg = readProjectConfigRaw();
|
|
17252
17252
|
if (cfg.formal !== "push" || !cfg.path) return cfg;
|
|
17253
|
-
const
|
|
17254
|
-
if (!
|
|
17253
|
+
const refused2 = formalRefusal(cfg.path);
|
|
17254
|
+
if (!refused2) return cfg;
|
|
17255
17255
|
const { formal: _dropped, ...rest } = cfg;
|
|
17256
|
-
return { ...rest, formal_refused:
|
|
17256
|
+
return { ...rest, formal_refused: refused2 };
|
|
17257
17257
|
}
|
|
17258
17258
|
function formalRefusalMessage(reason) {
|
|
17259
17259
|
switch (reason) {
|
|
@@ -24152,7 +24152,7 @@ function registerConfigCommands(program2) {
|
|
|
24152
24152
|
config.command("formal [state]").description('Get or set formal verification at push: "push" or "off" (needs Lean \u2014 verity lean status)').action((state) => {
|
|
24153
24153
|
if (state === void 0) {
|
|
24154
24154
|
const cfg = readProjectConfig();
|
|
24155
|
-
process.stdout.write((cfg.formal
|
|
24155
|
+
process.stdout.write((cfg.formal === "push" ? "push" : "off") + "\n");
|
|
24156
24156
|
if (cfg.formal_refused) printWarn(formalRefusalMessage(cfg.formal_refused));
|
|
24157
24157
|
return;
|
|
24158
24158
|
}
|
|
@@ -24160,10 +24160,10 @@ function registerConfigCommands(program2) {
|
|
|
24160
24160
|
printError(`Unrecognised state: ${state}. Use "push" or "off".`);
|
|
24161
24161
|
process.exit(1);
|
|
24162
24162
|
}
|
|
24163
|
-
writeProjectConfig({ formal: state === "push" ? "push" :
|
|
24164
|
-
const
|
|
24165
|
-
if (
|
|
24166
|
-
printWarn(`Formal verification at push: still off \u2014 ${formalRefusalMessage(
|
|
24163
|
+
writeProjectConfig({ formal: state === "push" ? "push" : "off" });
|
|
24164
|
+
const refused2 = state === "push" ? readProjectConfig().formal_refused : void 0;
|
|
24165
|
+
if (refused2) {
|
|
24166
|
+
printWarn(`Formal verification at push: still off \u2014 ${formalRefusalMessage(refused2)}`);
|
|
24167
24167
|
return;
|
|
24168
24168
|
}
|
|
24169
24169
|
printInfo(state === "push" ? "Formal verification at push: on for this checkout (Lean must be installed \u2014 verity lean status)" : "Formal verification at push: off");
|
|
@@ -27073,7 +27073,7 @@ function channelSilence(input) {
|
|
|
27073
27073
|
// src/lib/cli-version.ts
|
|
27074
27074
|
function cliVersion() {
|
|
27075
27075
|
try {
|
|
27076
|
-
return true ? "0.34.0-experimental.
|
|
27076
|
+
return true ? "0.34.0-experimental.5235853" : "dev";
|
|
27077
27077
|
} catch {
|
|
27078
27078
|
return "dev";
|
|
27079
27079
|
}
|
|
@@ -31688,6 +31688,22 @@ async function workingMemory(run4) {
|
|
|
31688
31688
|
Object.assign(run4, { incrementReport, memory: memory2, memorySession, reachability });
|
|
31689
31689
|
}
|
|
31690
31690
|
|
|
31691
|
+
// src/lib/payload-bounds.ts
|
|
31692
|
+
var MAX_WIRE_PATHS = 500;
|
|
31693
|
+
function boundPaths(paths, first, max = MAX_WIRE_PATHS) {
|
|
31694
|
+
if (paths.length <= max) return [...paths];
|
|
31695
|
+
const present = new Set(paths);
|
|
31696
|
+
const kept = /* @__PURE__ */ new Set();
|
|
31697
|
+
const out2 = [];
|
|
31698
|
+
for (const p of [...first, ...paths]) {
|
|
31699
|
+
if (out2.length >= max) break;
|
|
31700
|
+
if (!present.has(p) || kept.has(p)) continue;
|
|
31701
|
+
kept.add(p);
|
|
31702
|
+
out2.push(p);
|
|
31703
|
+
}
|
|
31704
|
+
return out2;
|
|
31705
|
+
}
|
|
31706
|
+
|
|
31691
31707
|
// src/lib/note-budget.ts
|
|
31692
31708
|
var import_node_fs49 = require("node:fs");
|
|
31693
31709
|
var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
|
|
@@ -31802,6 +31818,7 @@ async function buildRequest(run4) {
|
|
|
31802
31818
|
(conversation?.prompts?.length ?? 0) > 0
|
|
31803
31819
|
);
|
|
31804
31820
|
const noteConfirmation = peekNoteOutcome();
|
|
31821
|
+
const unobserved = foldResult?.unobserved ?? [];
|
|
31805
31822
|
const excludedByReason = {};
|
|
31806
31823
|
for (const e of codeDelta.excluded ?? []) {
|
|
31807
31824
|
excludedByReason[e.reason] = (excludedByReason[e.reason] ?? 0) + 1;
|
|
@@ -31846,7 +31863,9 @@ async function buildRequest(run4) {
|
|
|
31846
31863
|
coverage_telemetry: coverageTelemetry,
|
|
31847
31864
|
static_results: staticResults,
|
|
31848
31865
|
code_delta: codeDelta,
|
|
31849
|
-
|
|
31866
|
+
// Bounded, reviewed paths first — the server applies the same bound. The
|
|
31867
|
+
// full count is `coverage_telemetry.for_review`. VRT-242.
|
|
31868
|
+
changed_files: boundPaths(allForReview, codeDelta.files.map((f) => f.path)),
|
|
31850
31869
|
standard_version: "latest",
|
|
31851
31870
|
analysis_mode: analysisMode,
|
|
31852
31871
|
plan_only: analysisMode === "plan",
|
|
@@ -31897,7 +31916,9 @@ async function buildRequest(run4) {
|
|
|
31897
31916
|
// `hash_at_last_verdict` is frozen at each verdict and `hash_now` tracks
|
|
31898
31917
|
// disk, so their inequality IS "changed since we last spoke".
|
|
31899
31918
|
authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
|
|
31900
|
-
|
|
31919
|
+
// The server reads only how many; a bulk change sent every path. VRT-242.
|
|
31920
|
+
unobserved: unobserved.slice(0, MAX_WIRE_PATHS),
|
|
31921
|
+
...unobserved.length > MAX_WIRE_PATHS && { unobserved_total: unobserved.length },
|
|
31901
31922
|
commands: foldResult?.commands ?? [],
|
|
31902
31923
|
tools: foldResult?.tools ?? [],
|
|
31903
31924
|
tasks: foldResult?.tasks ?? [],
|
|
@@ -32108,8 +32129,10 @@ function buildOfflineFallback(reason, staticResults) {
|
|
|
32108
32129
|
}
|
|
32109
32130
|
|
|
32110
32131
|
// src/lib/retry.ts
|
|
32132
|
+
var WORKER_RESOURCE_LIMIT = 546;
|
|
32111
32133
|
function shouldWarmRetryAnalyze(result) {
|
|
32112
32134
|
if (result.ok) return false;
|
|
32135
|
+
if (result.http_status === WORKER_RESOURCE_LIMIT) return false;
|
|
32113
32136
|
if (result.category === "network" || result.category === "timeout") return true;
|
|
32114
32137
|
if (result.category === "http_5xx") return true;
|
|
32115
32138
|
if (result.category === "invalid_json" && (result.http_status ?? 0) >= 500) return true;
|
|
@@ -32119,7 +32142,7 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
32119
32142
|
// src/commands/analyze/phases/12-transmit.ts
|
|
32120
32143
|
async function transmit(run4) {
|
|
32121
32144
|
const { globals } = run4;
|
|
32122
|
-
const {
|
|
32145
|
+
const { requestBody, serviceUrl, staticResults, token } = run4;
|
|
32123
32146
|
const ANALYZE_TIMEOUT_MS = 1e5;
|
|
32124
32147
|
let result = await analyzeRequest({
|
|
32125
32148
|
serviceUrl,
|
|
@@ -32156,11 +32179,12 @@ async function transmit(run4) {
|
|
|
32156
32179
|
}
|
|
32157
32180
|
if (!result.ok) {
|
|
32158
32181
|
cacheRequest(requestBody);
|
|
32182
|
+
const sentKb = () => Math.round(Buffer.byteLength(JSON.stringify(requestBody)) / 1024);
|
|
32159
32183
|
let message;
|
|
32160
32184
|
if (result.http_status === 413) {
|
|
32161
|
-
|
|
32162
|
-
|
|
32163
|
-
message = `Verity:
|
|
32185
|
+
message = `Verity: request rejected \u2014 payload too large (${sentKb()} KB sent: ${result.error}). Commit or stash pre-existing changes, or run with --mode skip if you didn't intend to submit code this turn.`;
|
|
32186
|
+
} else if (result.http_status === WORKER_RESOURCE_LIMIT) {
|
|
32187
|
+
message = `Verity: the service ran out of CPU or memory on this request (HTTP 546, ${sentKb()} KB sent). This is not a cold start: the same change fails the same way, so it was not retried. Commit or stash unrelated changes to shrink the request (local analysis this run).`;
|
|
32164
32188
|
} else if (result.category === "network" || result.category === "timeout") {
|
|
32165
32189
|
message = `Verity offline \u2014 ${result.error}`;
|
|
32166
32190
|
} else if (result.error.startsWith("STALE_VERIFICATION")) {
|
|
@@ -33869,6 +33893,11 @@ async function rankViaServer(candidates2, ctx, auth2) {
|
|
|
33869
33893
|
cmd: "prove-rank"
|
|
33870
33894
|
});
|
|
33871
33895
|
if (!res.ok) return { ok: false, ranker: "luna", error: res.error, ranked: fallback };
|
|
33896
|
+
const off = res.data;
|
|
33897
|
+
if (off?.enabled === false) {
|
|
33898
|
+
const why = typeof off.reason === "string" ? off.reason.slice(0, 40) : "disabled";
|
|
33899
|
+
return { ok: false, ranker: "luna", error: `formal is off for this project (${why})`, ranked: fallback, disabled: why };
|
|
33900
|
+
}
|
|
33872
33901
|
const items = Array.isArray(res.data?.items) ? res.data.items : [];
|
|
33873
33902
|
return { ok: true, ranker: "luna", ranked: applyAnnotations(candidates2, items), costUsd: typeof res.data?.cost_usd === "number" ? res.data.cost_usd : null };
|
|
33874
33903
|
}
|
|
@@ -34593,6 +34622,8 @@ async function generateModel(target, opts) {
|
|
|
34593
34622
|
});
|
|
34594
34623
|
if (!res.ok) return { ok: false, error: res.error };
|
|
34595
34624
|
const d = res.data ?? {};
|
|
34625
|
+
const off = d;
|
|
34626
|
+
if (off.enabled === false) return { ok: false, error: `formal is off for this project (${typeof off.reason === "string" ? off.reason.slice(0, 40) : "disabled"})` };
|
|
34596
34627
|
if (d.error && typeof d.error === "object") return { ok: false, error: `${d.error.code ?? "SERVICE_ERROR"}: ${d.error.message ?? "the service failed"}` };
|
|
34597
34628
|
const usage2 = d.usage && typeof d.usage === "object" ? { inputTokens: Number(d.usage.inputTokens ?? 0), outputTokens: Number(d.usage.outputTokens ?? 0) } : null;
|
|
34598
34629
|
return acceptAnswer(d, usage2, typeof d.cost_usd === "number" ? d.cost_usd : null);
|
|
@@ -34772,8 +34803,7 @@ Rules:
|
|
|
34772
34803
|
5. Write the harness as ONE file and run it in the SAME shell call that writes it (a heredoc followed by the run). TypeScript: node --experimental-strip-types harness.ts (detect runtimes only if that fails: node --version, python3 --version); if strip-types is unavailable, remove type annotations ONLY on lines that are not part of the function under test's logic, and say so. Python: python3 harness.py.
|
|
34773
34804
|
6. Nothing you run can reach the network. Do not try.
|
|
34774
34805
|
7. Keep it under about eight shell calls. Output tokens are time.
|
|
34775
|
-
8. Answer with the JSON object only: verdict (reproduced | diverged | unreplayable), reason (2-4 sentences: what you ran, what the real code did, and how that compares to the trace's final state), harness_files (every file you wrote: path and its line count \u2014 do NOT paste contents, your shell calls already carry them), executed_output (the last relevant stdout, at most 2000 characters)
|
|
34776
|
-
9. impact \u2014 AFTER the verdict, and never a reason to change it: what the violation you ran costs a user of this code, the most serious one the run showed. money: a balance, charge, refund, price or stock is wrong. access: someone gets access they should not have, or an auth check is bypassed. data: a user's records are lost, corrupted or wrongly duplicated. external: an effect outside the system happens twice or never (a charge, an email, a shipment, a webhook). count: a counter, metric or statistic is off. cache: a cached or derived value is stale until it refreshes. internal: the program's own bookkeeping is off, with no effect a user would see. none: the verdict is not reproduced.
|
|
34806
|
+
8. Answer with the JSON object only: verdict (reproduced | diverged | unreplayable), reason (2-4 sentences: what you ran, what the real code did, and how that compares to the trace's final state), harness_files (every file you wrote: path and its line count \u2014 do NOT paste contents, your shell calls already carry them), executed_output (the last relevant stdout, at most 2000 characters).
|
|
34777
34807
|
|
|
34778
34808
|
"reproduced" means the real code, driven by the trace, reached the property violation the model describes. "diverged" means it did not \u2014 the model is wrong about the code, which is a useful answer. "unreplayable" means you could not run the real function faithfully; say why.`;
|
|
34779
34809
|
var REPLAY_SCHEMA = {
|
|
@@ -34791,10 +34821,9 @@ var REPLAY_SCHEMA = {
|
|
|
34791
34821
|
required: ["path", "lines"]
|
|
34792
34822
|
}
|
|
34793
34823
|
},
|
|
34794
|
-
executed_output: { type: "string" }
|
|
34795
|
-
impact: { type: "string", enum: ["money", "access", "data", "external", "count", "cache", "internal", "none"] }
|
|
34824
|
+
executed_output: { type: "string" }
|
|
34796
34825
|
},
|
|
34797
|
-
required: ["verdict", "reason", "harness_files", "executed_output"
|
|
34826
|
+
required: ["verdict", "reason", "harness_files", "executed_output"]
|
|
34798
34827
|
};
|
|
34799
34828
|
function renderBriefInput(brief) {
|
|
34800
34829
|
const p = brief.property;
|
|
@@ -34821,8 +34850,6 @@ function renderBriefInput(brief) {
|
|
|
34821
34850
|
}
|
|
34822
34851
|
return parts2.join("\n");
|
|
34823
34852
|
}
|
|
34824
|
-
var IMPACTS = ["money", "access", "data", "external", "count", "cache", "internal", "none"];
|
|
34825
|
-
var isImpact = (v) => typeof v === "string" && IMPACTS.includes(v);
|
|
34826
34853
|
function stringsIn(value, out2 = [], depth = 0) {
|
|
34827
34854
|
if (depth > 8) return out2;
|
|
34828
34855
|
if (typeof value === "string") out2.push(value);
|
|
@@ -34864,8 +34891,7 @@ function parseReplayOutput(output) {
|
|
|
34864
34891
|
verdict,
|
|
34865
34892
|
reason: typeof o.reason === "string" ? o.reason.slice(0, 2e3) : "",
|
|
34866
34893
|
harness_files: Array.isArray(o.harness_files) ? o.harness_files.filter((f) => typeof f.path === "string").map((f) => ({ path: String(f.path).slice(0, 200), lines: Number.isInteger(f.lines) ? f.lines : 0 })) : [],
|
|
34867
|
-
executed_output: typeof o.executed_output === "string" ? o.executed_output.slice(0, 2e3) : ""
|
|
34868
|
-
impact: isImpact(o.impact) ? o.impact : "none"
|
|
34894
|
+
executed_output: typeof o.executed_output === "string" ? o.executed_output.slice(0, 2e3) : ""
|
|
34869
34895
|
};
|
|
34870
34896
|
}
|
|
34871
34897
|
} catch {
|
|
@@ -34873,10 +34899,10 @@ function parseReplayOutput(output) {
|
|
|
34873
34899
|
}
|
|
34874
34900
|
return { claim, shellCalls, executedCalls, shellTexts, outputTexts };
|
|
34875
34901
|
}
|
|
34876
|
-
var RESULT_LINE = /REPLAY_RESULT=(reproduced|diverged)\b
|
|
34902
|
+
var RESULT_LINE = /REPLAY_RESULT=(reproduced|diverged)\b/g;
|
|
34877
34903
|
function printedResult(outputTexts) {
|
|
34878
34904
|
let last = null;
|
|
34879
|
-
for (const t of outputTexts) for (const m of t.matchAll(
|
|
34905
|
+
for (const t of outputTexts) for (const m of t.matchAll(RESULT_LINE)) last = m[1];
|
|
34880
34906
|
return last;
|
|
34881
34907
|
}
|
|
34882
34908
|
var RESULT_MISSING = "the harness never printed REPLAY_RESULT=reproduced on its own stdout \u2014 the verdict was the model's word, not the run's";
|
|
@@ -35066,7 +35092,6 @@ async function runReplayInContainer(brief, opts) {
|
|
|
35066
35092
|
...judged,
|
|
35067
35093
|
executedCalls,
|
|
35068
35094
|
shellCalls,
|
|
35069
|
-
...judged.verdict === "reproduced" && parsed.claim ? { impact: parsed.claim.impact } : {},
|
|
35070
35095
|
executedOutput: parsed.claim?.executed_output ?? "",
|
|
35071
35096
|
harnessFiles: parsed.claim?.harness_files ?? [],
|
|
35072
35097
|
usage,
|
|
@@ -35113,6 +35138,11 @@ async function runReplayViaServer(brief, auth2, budgetMs = 24e4) {
|
|
|
35113
35138
|
return { claimed: null, verdict: "unverified", reason: "", downgrade: res.error, executedCalls: 0, shellCalls: 0, verbatim: none, executedOutput: "", harnessFiles: [], usage: null, durationMs: Date.now() - started, containerId: null, error: res.error };
|
|
35114
35139
|
}
|
|
35115
35140
|
const d = res.data ?? {};
|
|
35141
|
+
if (d.enabled === false) {
|
|
35142
|
+
const why = d.reason;
|
|
35143
|
+
const msg = `formal is off for this project (${typeof why === "string" ? why.slice(0, 40) : "disabled"})`;
|
|
35144
|
+
return { claimed: null, verdict: "unverified", reason: "", downgrade: msg, executedCalls: 0, shellCalls: 0, verbatim: none, executedOutput: "", harnessFiles: [], usage: null, durationMs: Date.now() - started, containerId: null, error: msg };
|
|
35145
|
+
}
|
|
35116
35146
|
if (d.error && typeof d.error === "object") {
|
|
35117
35147
|
const e = d.error;
|
|
35118
35148
|
const msg = `${e.code ?? "SERVICE_ERROR"}: ${e.message ?? "the service failed"}`;
|
|
@@ -35125,7 +35155,6 @@ async function runReplayViaServer(brief, auth2, budgetMs = 24e4) {
|
|
|
35125
35155
|
verdict: verdict === "reproduced" || verdict === "diverged" || verdict === "unreplayable" ? verdict : "unverified",
|
|
35126
35156
|
reason: typeof d.reason === "string" ? d.reason : "",
|
|
35127
35157
|
downgrade: typeof d.downgrade === "string" ? d.downgrade : null,
|
|
35128
|
-
...isImpact(d.impact) ? { impact: d.impact } : {},
|
|
35129
35158
|
executedCalls: Number(d.executed_calls ?? 0),
|
|
35130
35159
|
shellCalls: Number(d.shell_calls ?? 0),
|
|
35131
35160
|
verbatim: { covered: Number(v.covered ?? 0), total: Number(v.total ?? 0), ratio: Number(v.ratio ?? 0), basis: v.basis === "symbol" || v.basis === "file" ? v.basis : "none" },
|
|
@@ -35910,9 +35939,10 @@ function verityLeanHome(env = process.env) {
|
|
|
35910
35939
|
function findToolchain(env = process.env) {
|
|
35911
35940
|
const managedBin = (0, import_node_path43.join)(verityLeanHome(env), LEAN_TOOLCHAIN.replace(/^leanprover\/lean4:/, ""), "bin");
|
|
35912
35941
|
const elanBin = (0, import_node_path43.join)(env.HOME ?? (0, import_node_os9.homedir)(), ".elan", "bin");
|
|
35913
|
-
const path = [managedBin, elanBin, env.PATH ?? ""].filter(Boolean).join(import_node_path43.delimiter);
|
|
35914
|
-
const merged = { ...env, PATH: path, ELAN_TOOLCHAIN: env.ELAN_TOOLCHAIN ?? LEAN_TOOLCHAIN };
|
|
35915
35942
|
const elanHas = (0, import_node_fs58.existsSync)((0, import_node_path43.join)(env.HOME ?? (0, import_node_os9.homedir)(), ".elan", "toolchains", LEAN_TOOLCHAIN.replace("/", "--").replace(":", "---")));
|
|
35943
|
+
const userPath = (env.PATH ?? "").split(import_node_path43.delimiter).filter((d) => d && (elanHas || (0, import_node_path43.resolve)(d) !== (0, import_node_path43.resolve)(elanBin)));
|
|
35944
|
+
const path = [managedBin, ...elanHas ? [elanBin] : [], ...userPath].join(import_node_path43.delimiter);
|
|
35945
|
+
const merged = { ...env, PATH: path, ELAN_TOOLCHAIN: env.ELAN_TOOLCHAIN ?? LEAN_TOOLCHAIN };
|
|
35916
35946
|
const exe = process.platform === "win32" ? "lake.exe" : "lake";
|
|
35917
35947
|
const candidates2 = [(0, import_node_path43.join)(managedBin, exe), ...elanHas ? [(0, import_node_path43.join)(elanBin, exe)] : [], exe];
|
|
35918
35948
|
for (const candidate of candidates2) {
|
|
@@ -35989,10 +36019,35 @@ function runAsync(tc, cwd, args2, timeoutMs) {
|
|
|
35989
36019
|
}
|
|
35990
36020
|
var DEFAULT_BUILD_TIMEOUT_MS = 10 * 6e4;
|
|
35991
36021
|
var DEFAULT_CHECK_TIMEOUT_MS = 12e4;
|
|
36022
|
+
function proofsWorkspaceProblem(root) {
|
|
36023
|
+
for (const rel of [".verity", PROOFS_DIR, `${PROOFS_DIR}/.lake`]) {
|
|
36024
|
+
let st;
|
|
36025
|
+
try {
|
|
36026
|
+
st = (0, import_node_fs58.lstatSync)((0, import_node_path43.join)(root, rel));
|
|
36027
|
+
} catch {
|
|
36028
|
+
break;
|
|
36029
|
+
}
|
|
36030
|
+
if (st.isSymbolicLink()) return `${rel} is a symlink; Lean is not run through it`;
|
|
36031
|
+
}
|
|
36032
|
+
const r = runGitSync(root, ["ls-files", "-z", "--", PROOFS_DIR], { timeoutMs: 5e3 });
|
|
36033
|
+
if (!r.ok) return `could not confirm with git that ${PROOFS_DIR} holds no committed files; Lean is not run`;
|
|
36034
|
+
const tracked = r.stdout.split("\0").filter(Boolean);
|
|
36035
|
+
if (tracked.length > 0) {
|
|
36036
|
+
return `${tracked.length} file(s) under ${PROOFS_DIR} are tracked in git (e.g. ${tracked[0]}); Lean is not run on a workspace the repository supplies \u2014 untrack them with: git rm -r --cached ${PROOFS_DIR}`;
|
|
36037
|
+
}
|
|
36038
|
+
return null;
|
|
36039
|
+
}
|
|
36040
|
+
function refused(problem) {
|
|
36041
|
+
return { ok: false, stdout: "", stderr: problem, durationMs: 0, timedOut: false };
|
|
36042
|
+
}
|
|
35992
36043
|
function buildLibrary(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
|
|
36044
|
+
const problem = proofsWorkspaceProblem(root);
|
|
36045
|
+
if (problem) return refused(problem);
|
|
35993
36046
|
return run2(tc, (0, import_node_path43.join)(root, PROOFS_DIR), ["build"], timeoutMs);
|
|
35994
36047
|
}
|
|
35995
|
-
function buildLibraryAsync(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
|
|
36048
|
+
async function buildLibraryAsync(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
|
|
36049
|
+
const problem = proofsWorkspaceProblem(root);
|
|
36050
|
+
if (problem) return refused(problem);
|
|
35996
36051
|
return runAsync(tc, (0, import_node_path43.join)(root, PROOFS_DIR), ["build"], timeoutMs);
|
|
35997
36052
|
}
|
|
35998
36053
|
function isModelInRoot(modelsAbs, rel) {
|
|
@@ -36019,13 +36074,15 @@ function trimDiagnostics(text) {
|
|
|
36019
36074
|
function checkModel(tc, root, modelFile, opts = {}) {
|
|
36020
36075
|
const proofsAbs = (0, import_node_path43.join)(root, PROOFS_DIR);
|
|
36021
36076
|
const rel = (0, import_node_path43.relative)(proofsAbs, modelFile);
|
|
36022
|
-
const
|
|
36077
|
+
const problem = proofsWorkspaceProblem(root);
|
|
36078
|
+
const res = problem ? refused(problem) : run2(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36023
36079
|
return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36024
36080
|
}
|
|
36025
36081
|
async function checkModelAsync(tc, root, modelFile, opts = {}) {
|
|
36026
36082
|
const proofsAbs = (0, import_node_path43.join)(root, PROOFS_DIR);
|
|
36027
36083
|
const rel = (0, import_node_path43.relative)(proofsAbs, modelFile);
|
|
36028
|
-
const
|
|
36084
|
+
const problem = proofsWorkspaceProblem(root);
|
|
36085
|
+
const res = problem ? refused(problem) : await runAsync(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36029
36086
|
return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
36030
36087
|
}
|
|
36031
36088
|
function toReport(res, root, modelFile, timeoutMs) {
|
|
@@ -36080,33 +36137,18 @@ function writeResult(root, report) {
|
|
|
36080
36137
|
function isCrashTrace(trace) {
|
|
36081
36138
|
return trace.some((s) => /\b(crash\w*|kill\w*|restart\w*|interrupt\w*|power|abort\w*|process (dies|exit\w*)|lost (reply|response))\b/i.test(s));
|
|
36082
36139
|
}
|
|
36083
|
-
var SEVERITY_BY_IMPACT = {
|
|
36084
|
-
money: "high",
|
|
36085
|
-
access: "high",
|
|
36086
|
-
data: "high",
|
|
36087
|
-
external: "high",
|
|
36088
|
-
count: "medium",
|
|
36089
|
-
cache: "medium",
|
|
36090
|
-
internal: "medium",
|
|
36091
|
-
// A reproduced finding always has a real code; 'none' here is a malformed answer, so it blocks.
|
|
36092
|
-
none: "high"
|
|
36093
|
-
};
|
|
36094
|
-
function formalSeverity(f) {
|
|
36095
|
-
return f.impact && SEVERITY_BY_IMPACT[f.impact] || "high";
|
|
36096
|
-
}
|
|
36097
36140
|
function mergeFormalFindings(response, outcome, pushedFiles) {
|
|
36098
|
-
if (!outcome) return { blocking: 0
|
|
36141
|
+
if (!outcome) return { blocking: 0 };
|
|
36099
36142
|
const norm2 = (p) => p.replace(/\\/g, "/");
|
|
36100
36143
|
const pushed = new Set(pushedFiles.map(norm2));
|
|
36101
|
-
const
|
|
36102
|
-
if (
|
|
36103
|
-
const blocking = reproduced.filter((f) => formalSeverity(f) === "high");
|
|
36144
|
+
const blocking = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(norm2(f.file)));
|
|
36145
|
+
if (blocking.length === 0) return { blocking: 0 };
|
|
36104
36146
|
const existing = Array.isArray(response.findings) ? response.findings : [];
|
|
36105
36147
|
response.findings = [
|
|
36106
36148
|
...existing,
|
|
36107
|
-
...
|
|
36149
|
+
...blocking.map((f) => ({
|
|
36108
36150
|
pattern_id: "formal-counterexample",
|
|
36109
|
-
severity:
|
|
36151
|
+
severity: "high",
|
|
36110
36152
|
scope: "change",
|
|
36111
36153
|
file: f.file,
|
|
36112
36154
|
line: outcome.target?.lines?.from ?? null,
|
|
@@ -36114,15 +36156,14 @@ function mergeFormalFindings(response, outcome, pushedFiles) {
|
|
|
36114
36156
|
// as a title it read backwards (e2e, 2026-09-26). A trace that needs a
|
|
36115
36157
|
// crash between two writes is labelled: reproducing it means injecting
|
|
36116
36158
|
// the crash, so it is a crash-consistency finding, not a race.
|
|
36117
|
-
title: `Formal: ${terminalText(f.symbol, 128)} breaks "${terminalText(f.desc, 280)}"${isCrashTrace(f.trace) ? " after a crash (crash consistency)" : ""} \u2014 reproduced on the real code
|
|
36159
|
+
title: `Formal: ${terminalText(f.symbol, 128)} breaks "${terminalText(f.desc, 280)}"${isCrashTrace(f.trace) ? " after a crash (crash consistency)" : ""} \u2014 reproduced on the real code`,
|
|
36118
36160
|
description: terminalText(f.reason ?? "", 2e3),
|
|
36119
36161
|
fix: { description: `Trace: ${terminalText(f.trace.join(" \u2192 ") || "(initial state)", 2e3)}. Replay: ${terminalText(f.reason ?? "reproduced in the container", 2e3)}` },
|
|
36120
36162
|
source: "formal"
|
|
36121
36163
|
}))
|
|
36122
36164
|
];
|
|
36123
|
-
if (
|
|
36124
|
-
|
|
36125
|
-
return { blocking: blocking.length, warning: reproduced.length - blocking.length };
|
|
36165
|
+
if (response.gate_decision !== "FAIL") response.gate_decision = "FAIL";
|
|
36166
|
+
return { blocking: blocking.length };
|
|
36126
36167
|
}
|
|
36127
36168
|
|
|
36128
36169
|
// src/lib/prove/scalpel.ts
|
|
@@ -36441,9 +36482,7 @@ async function runFormalPhase(input) {
|
|
|
36441
36482
|
symbol: f.symbol,
|
|
36442
36483
|
verdict: "reproduced",
|
|
36443
36484
|
reason: f.waived ? `waived ${f.waived.at.slice(0, 10)}: ${f.waived.reason} (standing since ${f.at.slice(0, 10)}; the file has not changed)` : `${f.reason ?? "reproduced on the real code"} (standing since ${f.at.slice(0, 10)}; the file has not changed)`,
|
|
36444
|
-
...f.waived ? { waived: true } : {}
|
|
36445
|
-
// A hand-edited file cannot invent a code: unknown values read as absent (HIGH).
|
|
36446
|
-
...isImpact(f.impact) ? { impact: f.impact } : {}
|
|
36485
|
+
...f.waived ? { waived: true } : {}
|
|
36447
36486
|
});
|
|
36448
36487
|
}
|
|
36449
36488
|
}
|
|
@@ -36482,6 +36521,7 @@ async function runFormalPhase(input) {
|
|
|
36482
36521
|
return done({ status: "skipped", skip: "not-eligible", detail: `${best.file}: ${isEligible(best, largePush).why}` });
|
|
36483
36522
|
}
|
|
36484
36523
|
const ranked = await deps.rank(eligible, root, input.changedLines);
|
|
36524
|
+
if (!ranked.ok && ranked.disabled) return done({ status: "skipped", skip: "disabled-by-server", detail: ranked.error });
|
|
36485
36525
|
if (ranked.ok) spend(ranked);
|
|
36486
36526
|
candidates2 = ranked.ranked.slice(0, 5).map((c) => ({ file: c.file, symbol: c.ai?.symbol ?? null, score: c.ai?.score ?? null, criteria: c.ai?.criteria ?? null, reasked: !!c.ai?.reasked, reasons: (c.ai?.reasons ?? []).map((r) => r.slice(0, 200)) }));
|
|
36487
36527
|
if (cancelled()) return done({ status: "skipped", skip: "deadline", detail: "cut off after ranking" });
|
|
@@ -36716,7 +36756,7 @@ async function runFormalPhase(input) {
|
|
|
36716
36756
|
continue;
|
|
36717
36757
|
}
|
|
36718
36758
|
const verdict = r.verdict === "reproduced" ? "reproduced" : r.verdict === "diverged" ? "diverged" : r.verdict === "unreplayable" ? "model-only" : "unverified";
|
|
36719
|
-
findings.push({ ...baseOf(p), verdict, reason: r.reason || r.downgrade || void 0
|
|
36759
|
+
findings.push({ ...baseOf(p), verdict, reason: r.reason || r.downgrade || void 0 });
|
|
36720
36760
|
}
|
|
36721
36761
|
const reproduced = findings.filter((f) => f.verdict === "reproduced");
|
|
36722
36762
|
if (reproduced.length > 0 && !cancelled()) {
|
|
@@ -36724,7 +36764,7 @@ async function runFormalPhase(input) {
|
|
|
36724
36764
|
const others = readPersistedFindings(root).filter((f) => f.file !== top.file);
|
|
36725
36765
|
writePersistedFindings(root, [
|
|
36726
36766
|
...others,
|
|
36727
|
-
...reproduced.map((f) => ({ file: f.file, symbol: f.symbol, sha256: sha2562, property: f.property, desc: f.desc, kind: f.kind, trace: f.trace, model: modelName, reason: f.reason, ...
|
|
36767
|
+
...reproduced.map((f) => ({ file: f.file, symbol: f.symbol, sha256: sha2562, property: f.property, desc: f.desc, kind: f.kind, trace: f.trace, model: modelName, reason: f.reason, ...lines ? { lines } : {}, at: now }))
|
|
36728
36768
|
]);
|
|
36729
36769
|
}
|
|
36730
36770
|
const clean2 = findings.every((f) => f.verdict === "diverged");
|
|
@@ -36845,6 +36885,8 @@ async function prepareFormalPhase(root, env = process.env, auth2, readSource, co
|
|
|
36845
36885
|
const apiKey = direct ? env.OPENAI_API_KEY : void 0;
|
|
36846
36886
|
const toolchain = findToolchain(env);
|
|
36847
36887
|
if (!toolchain) return { ok: false, skip: "no-toolchain" };
|
|
36888
|
+
const untrusted = proofsWorkspaceProblem(root);
|
|
36889
|
+
if (untrusted) return { ok: false, skip: "workspace-untrusted", detail: untrusted.slice(0, 200) };
|
|
36848
36890
|
try {
|
|
36849
36891
|
scaffoldProofs(root);
|
|
36850
36892
|
} catch (err2) {
|
|
@@ -37462,8 +37504,6 @@ async function runGuard(opts, globals) {
|
|
|
37462
37504
|
rounds: formalOutcome.model?.rounds ?? null,
|
|
37463
37505
|
properties: formalOutcome.properties?.map((p) => `${p.id}:${p.result}`) ?? [],
|
|
37464
37506
|
findings: formalOutcome.findings.map((f) => `${f.property}:${f.verdict}`),
|
|
37465
|
-
// Decision 82: the replay's impact code per reproduced finding; count/cache/internal warn, the rest block.
|
|
37466
|
-
impacts: formalOutcome.findings.filter((f) => f.impact).map((f) => `${f.property}:${f.impact}`),
|
|
37467
37507
|
// Decision 81: what each property rests on (a rule, or "line"), what was
|
|
37468
37508
|
// sent back or dropped as ungrounded, and which counterexamples lack their
|
|
37469
37509
|
// property's trigger (shadow). Ids and rule names only, never code.
|
|
@@ -38773,6 +38813,22 @@ var import_promises18 = require("node:stream/promises");
|
|
|
38773
38813
|
var import_node_stream = require("node:stream");
|
|
38774
38814
|
var RELEASES = "https://releases.lean-lang.org/lean4";
|
|
38775
38815
|
var LEAN_DOWNLOAD_SIZE = "\u2248800 MB download, 2.7 GB on disk";
|
|
38816
|
+
var DOWNLOAD_STALL_MS = 6e4;
|
|
38817
|
+
var LEAN_DISK_NEEDED_BYTES = 3.75 * 1024 ** 3;
|
|
38818
|
+
function freeBytes(dir) {
|
|
38819
|
+
let d = dir;
|
|
38820
|
+
for (let i2 = 0; i2 < 64; i2++) {
|
|
38821
|
+
try {
|
|
38822
|
+
const st = (0, import_node_fs63.statfsSync)(d);
|
|
38823
|
+
return Number(st.bavail) * Number(st.bsize);
|
|
38824
|
+
} catch {
|
|
38825
|
+
const up = (0, import_node_path47.join)(d, "..");
|
|
38826
|
+
if (up === d) return null;
|
|
38827
|
+
d = up;
|
|
38828
|
+
}
|
|
38829
|
+
}
|
|
38830
|
+
return null;
|
|
38831
|
+
}
|
|
38776
38832
|
var LEAN_ASSET_SHA256 = {
|
|
38777
38833
|
"v4.34.0": {
|
|
38778
38834
|
"lean-4.34.0-darwin.zip": "75a2a4b8e3d35680e8f51fae5a42c79702f463dd73ee1520fb61e5a9143e336c",
|
|
@@ -38831,17 +38887,32 @@ function unzip(zip, dest) {
|
|
|
38831
38887
|
}
|
|
38832
38888
|
return { ok: false, error: errors.join("; ") };
|
|
38833
38889
|
}
|
|
38834
|
-
async function download(url, dest, onProgress) {
|
|
38835
|
-
const
|
|
38836
|
-
|
|
38837
|
-
const
|
|
38838
|
-
|
|
38839
|
-
|
|
38840
|
-
|
|
38841
|
-
|
|
38842
|
-
|
|
38843
|
-
|
|
38844
|
-
|
|
38890
|
+
async function download(url, dest, onProgress, stallMs = DOWNLOAD_STALL_MS) {
|
|
38891
|
+
const ctrl = new AbortController();
|
|
38892
|
+
let timer;
|
|
38893
|
+
const arm = () => {
|
|
38894
|
+
clearTimeout(timer);
|
|
38895
|
+
timer = setTimeout(() => ctrl.abort(), stallMs);
|
|
38896
|
+
};
|
|
38897
|
+
arm();
|
|
38898
|
+
try {
|
|
38899
|
+
const res = await fetch(url, { redirect: "follow", signal: ctrl.signal });
|
|
38900
|
+
if (!res.ok || !res.body) throw new Error(`download failed: HTTP ${res.status}`);
|
|
38901
|
+
const total = Number(res.headers.get("content-length")) || null;
|
|
38902
|
+
let seen = 0;
|
|
38903
|
+
const stream = import_node_stream.Readable.fromWeb(res.body);
|
|
38904
|
+
stream.on("data", (chunk) => {
|
|
38905
|
+
arm();
|
|
38906
|
+
seen += chunk.length;
|
|
38907
|
+
onProgress(seen, total);
|
|
38908
|
+
});
|
|
38909
|
+
await (0, import_promises18.pipeline)(stream, (0, import_node_fs63.createWriteStream)(dest), { signal: ctrl.signal });
|
|
38910
|
+
} catch (err2) {
|
|
38911
|
+
if (ctrl.signal.aborted) throw new Error(`download stalled: nothing received for ${Math.round(stallMs / 1e3)} s`);
|
|
38912
|
+
throw err2;
|
|
38913
|
+
} finally {
|
|
38914
|
+
clearTimeout(timer);
|
|
38915
|
+
}
|
|
38845
38916
|
}
|
|
38846
38917
|
function inPlaceProgress(label2, write = (s) => process.stderr.write(s)) {
|
|
38847
38918
|
let active = false;
|
|
@@ -38879,6 +38950,12 @@ async function installLean(opts = {}) {
|
|
|
38879
38950
|
const dir = pinnedVersionDir(env);
|
|
38880
38951
|
const url = `${RELEASES}/${pinnedVersion()}/${asset}`;
|
|
38881
38952
|
const home = verityLeanHome(env);
|
|
38953
|
+
const free = freeBytes(home);
|
|
38954
|
+
const needed = opts.minFreeBytes ?? LEAN_DISK_NEEDED_BYTES;
|
|
38955
|
+
if (free !== null && free < needed) {
|
|
38956
|
+
const gb = (n) => (n / 1024 ** 3).toFixed(1);
|
|
38957
|
+
return { ok: false, error: `not enough disk space for Lean: ${gb(free)} GB free where ${home} lives, needs about ${gb(needed)} GB` };
|
|
38958
|
+
}
|
|
38882
38959
|
const tmp = (0, import_node_path47.join)(home, ".tmp");
|
|
38883
38960
|
(0, import_node_fs63.rmSync)(tmp, { recursive: true, force: true });
|
|
38884
38961
|
(0, import_node_fs63.mkdirSync)(tmp, { recursive: true });
|
|
@@ -39187,8 +39264,10 @@ var INTENSITY_CHOICES = [
|
|
|
39187
39264
|
{ id: "thorough", label: "thorough", hint: "all tools, all rules (~15s)" }
|
|
39188
39265
|
];
|
|
39189
39266
|
var FORMAL_CHOICES = [
|
|
39190
|
-
{ id: "yes", label: "yes", hint: "a Lean model of the riskiest changed function; blocks only on a REPRODUCED counterexample"
|
|
39191
|
-
|
|
39267
|
+
{ id: "yes", label: "yes", hint: "a Lean model of the riskiest changed function; blocks only on a REPRODUCED counterexample" },
|
|
39268
|
+
// No is the default (review of #255): "yes" downloads ≈800 MB and unpacks
|
|
39269
|
+
// 2.7 GB, and a bare Enter must never do that.
|
|
39270
|
+
{ id: "no", label: "no", hint: "enable later with: verity config formal push", recommended: true }
|
|
39192
39271
|
];
|
|
39193
39272
|
var MOMENT_CHOICES = [
|
|
39194
39273
|
{ id: "stop", label: "On stop", hint: "after every agent turn \u2014 fast feedback while you work", recommended: true },
|
|
@@ -39594,16 +39673,20 @@ async function offerFormalVerification(defaultsOnly, pluginMode) {
|
|
|
39594
39673
|
printWarn(` Formal verification: off \u2014 ${formalRefusalMessage(cfg.formal_refused)}`);
|
|
39595
39674
|
return;
|
|
39596
39675
|
}
|
|
39597
|
-
const toolchain = findToolchain();
|
|
39598
39676
|
if (cfg.formal === "push") {
|
|
39599
|
-
if (
|
|
39677
|
+
if (findToolchain()) printInfo(" Formal verification at push \u2713");
|
|
39600
39678
|
else printWarn(" Formal verification is on but Lean is missing \u2014 run: verity lean install");
|
|
39601
39679
|
return;
|
|
39602
39680
|
}
|
|
39681
|
+
if (cfg.formal === "off") {
|
|
39682
|
+
printInfo(" Formal verification: off (enable with: verity config formal push)");
|
|
39683
|
+
return;
|
|
39684
|
+
}
|
|
39603
39685
|
if (defaultsOnly || !interactive()) {
|
|
39604
39686
|
printInfo(" Formal verification (a model-based bug finder at push): off \u2014 enable with: verity config formal push");
|
|
39605
39687
|
return;
|
|
39606
39688
|
}
|
|
39689
|
+
const toolchain = findToolchain();
|
|
39607
39690
|
console.log("");
|
|
39608
39691
|
console.log(" Formal verification review \u2014 a model-based bug finder for your riskiest code.");
|
|
39609
39692
|
console.log(" At push, Verity picks the riskiest changed function, checks a small model of");
|
|
@@ -39611,9 +39694,10 @@ async function offerFormalVerification(defaultsOnly, pluginMode) {
|
|
|
39611
39694
|
console.log(" Verity for that). Only a reproduced bug can block; most pushes get no model.");
|
|
39612
39695
|
console.log(` Adds /verity-prove <worry> to check any code on demand.${toolchain ? "" : ` Needs to install Lean 4 (${LEAN_DOWNLOAD_SIZE}) on "yes".`}`);
|
|
39613
39696
|
console.log("");
|
|
39614
|
-
const wants = await promptChoice("Formal verification at push?", FORMAL_CHOICES, "
|
|
39697
|
+
const wants = await promptChoice("Formal verification at push?", FORMAL_CHOICES, "no");
|
|
39615
39698
|
if (wants === "yes" && pluginMode) printInfo(" Plugin: the push check works now; /verity:verity-prove arrives with the plugin's next release.");
|
|
39616
39699
|
if (wants !== "yes") {
|
|
39700
|
+
writeProjectConfig({ formal: "off" });
|
|
39617
39701
|
printInfo(" Formal verification: off (enable later with: verity config formal push)");
|
|
39618
39702
|
return;
|
|
39619
39703
|
}
|
|
@@ -39872,7 +39956,7 @@ function registerInitCommand(program2) {
|
|
|
39872
39956
|
...telemetryChoice ? { telemetry: telemetryChoice } : {},
|
|
39873
39957
|
init: {
|
|
39874
39958
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39875
|
-
cli_version: true ? "0.34.0-experimental.
|
|
39959
|
+
cli_version: true ? "0.34.0-experimental.5235853" : "dev"
|
|
39876
39960
|
}
|
|
39877
39961
|
});
|
|
39878
39962
|
} catch (err2) {
|
|
@@ -41345,8 +41429,8 @@ function registerLeanCommand(program2) {
|
|
|
41345
41429
|
}
|
|
41346
41430
|
|
|
41347
41431
|
// src/cli.ts
|
|
41348
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.34.0-experimental.
|
|
41349
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.0-experimental.
|
|
41432
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.34.0-experimental.5235853").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
|
|
41433
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.0-experimental.5235853");
|
|
41350
41434
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
41351
41435
|
try {
|
|
41352
41436
|
await foldLegacyLocalCredential();
|
package/package.json
CHANGED