@codacy/verity-cli 0.34.0-experimental.37a6dcf → 0.34.0-experimental.421a631

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +0 -77
  2. package/bin/verity.js +78 -138
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -64,78 +64,6 @@ 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
-
139
67
  ### 🎯 `/verity:init` does everything `verity init` does, in the session
140
68
 
141
69
  Under the plugin, setup used to stop short of the terminal flow: `/verity:init`
@@ -161,11 +89,6 @@ sign-in meant leaving Claude Code for a terminal.
161
89
  - **`verity telemetry install|uninstall` update `.verity/setup.json`**, so a
162
90
  request deferred until sign-in stops being reported as still to do.
163
91
 
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
-
169
92
  ## [0.33.3] — 2026-09-24
170
93
 
171
94
  **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" || raw.formal === "off" ? { formal: raw.formal } : {}
17224
+ ...raw.formal === "push" ? { formal: "push" } : {}
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 refused2 = formalRefusal(cfg.path);
17254
- if (!refused2) return cfg;
17253
+ const refused = formalRefusal(cfg.path);
17254
+ if (!refused) return cfg;
17255
17255
  const { formal: _dropped, ...rest } = cfg;
17256
- return { ...rest, formal_refused: refused2 };
17256
+ return { ...rest, formal_refused: refused };
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 === "push" ? "push" : "off") + "\n");
24155
+ process.stdout.write((cfg.formal ?? "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" : "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)}`);
24163
+ writeProjectConfig({ formal: state === "push" ? "push" : void 0 });
24164
+ const refused = state === "push" ? readProjectConfig().formal_refused : void 0;
24165
+ if (refused) {
24166
+ printWarn(`Formal verification at push: still off \u2014 ${formalRefusalMessage(refused)}`);
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.37a6dcf" : "dev";
27076
+ return true ? "0.34.0-experimental.421a631" : "dev";
27077
27077
  } catch {
27078
27078
  return "dev";
27079
27079
  }
@@ -33869,11 +33869,6 @@ async function rankViaServer(candidates2, ctx, auth2) {
33869
33869
  cmd: "prove-rank"
33870
33870
  });
33871
33871
  if (!res.ok) return { ok: false, ranker: "luna", error: res.error, ranked: fallback };
33872
- const off = res.data;
33873
- if (off?.enabled === false) {
33874
- const why = typeof off.reason === "string" ? off.reason.slice(0, 40) : "disabled";
33875
- return { ok: false, ranker: "luna", error: `formal is off for this project (${why})`, ranked: fallback, disabled: why };
33876
- }
33877
33872
  const items = Array.isArray(res.data?.items) ? res.data.items : [];
33878
33873
  return { ok: true, ranker: "luna", ranked: applyAnnotations(candidates2, items), costUsd: typeof res.data?.cost_usd === "number" ? res.data.cost_usd : null };
33879
33874
  }
@@ -34598,8 +34593,6 @@ async function generateModel(target, opts) {
34598
34593
  });
34599
34594
  if (!res.ok) return { ok: false, error: res.error };
34600
34595
  const d = res.data ?? {};
34601
- const off = d;
34602
- if (off.enabled === false) return { ok: false, error: `formal is off for this project (${typeof off.reason === "string" ? off.reason.slice(0, 40) : "disabled"})` };
34603
34596
  if (d.error && typeof d.error === "object") return { ok: false, error: `${d.error.code ?? "SERVICE_ERROR"}: ${d.error.message ?? "the service failed"}` };
34604
34597
  const usage2 = d.usage && typeof d.usage === "object" ? { inputTokens: Number(d.usage.inputTokens ?? 0), outputTokens: Number(d.usage.outputTokens ?? 0) } : null;
34605
34598
  return acceptAnswer(d, usage2, typeof d.cost_usd === "number" ? d.cost_usd : null);
@@ -34779,7 +34772,8 @@ Rules:
34779
34772
  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.
34780
34773
  6. Nothing you run can reach the network. Do not try.
34781
34774
  7. Keep it under about eight shell calls. Output tokens are time.
34782
- 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).
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), impact (one word, below).
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.
34783
34777
 
34784
34778
  "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.`;
34785
34779
  var REPLAY_SCHEMA = {
@@ -34797,9 +34791,10 @@ var REPLAY_SCHEMA = {
34797
34791
  required: ["path", "lines"]
34798
34792
  }
34799
34793
  },
34800
- executed_output: { type: "string" }
34794
+ executed_output: { type: "string" },
34795
+ impact: { type: "string", enum: ["money", "access", "data", "external", "count", "cache", "internal", "none"] }
34801
34796
  },
34802
- required: ["verdict", "reason", "harness_files", "executed_output"]
34797
+ required: ["verdict", "reason", "harness_files", "executed_output", "impact"]
34803
34798
  };
34804
34799
  function renderBriefInput(brief) {
34805
34800
  const p = brief.property;
@@ -34826,6 +34821,8 @@ function renderBriefInput(brief) {
34826
34821
  }
34827
34822
  return parts2.join("\n");
34828
34823
  }
34824
+ var IMPACTS = ["money", "access", "data", "external", "count", "cache", "internal", "none"];
34825
+ var isImpact = (v) => typeof v === "string" && IMPACTS.includes(v);
34829
34826
  function stringsIn(value, out2 = [], depth = 0) {
34830
34827
  if (depth > 8) return out2;
34831
34828
  if (typeof value === "string") out2.push(value);
@@ -34867,7 +34864,8 @@ function parseReplayOutput(output) {
34867
34864
  verdict,
34868
34865
  reason: typeof o.reason === "string" ? o.reason.slice(0, 2e3) : "",
34869
34866
  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 })) : [],
34870
- executed_output: typeof o.executed_output === "string" ? o.executed_output.slice(0, 2e3) : ""
34867
+ executed_output: typeof o.executed_output === "string" ? o.executed_output.slice(0, 2e3) : "",
34868
+ impact: isImpact(o.impact) ? o.impact : "none"
34871
34869
  };
34872
34870
  }
34873
34871
  } catch {
@@ -34875,10 +34873,10 @@ function parseReplayOutput(output) {
34875
34873
  }
34876
34874
  return { claim, shellCalls, executedCalls, shellTexts, outputTexts };
34877
34875
  }
34878
- var RESULT_LINE = /REPLAY_RESULT=(reproduced|diverged)\b/g;
34876
+ var RESULT_LINE = /REPLAY_RESULT=(reproduced|diverged)\b/;
34879
34877
  function printedResult(outputTexts) {
34880
34878
  let last = null;
34881
- for (const t of outputTexts) for (const m of t.matchAll(RESULT_LINE)) last = m[1];
34879
+ for (const t of outputTexts) for (const m of t.matchAll(new RegExp(RESULT_LINE.source, "g"))) last = m[1];
34882
34880
  return last;
34883
34881
  }
34884
34882
  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";
@@ -35068,6 +35066,7 @@ async function runReplayInContainer(brief, opts) {
35068
35066
  ...judged,
35069
35067
  executedCalls,
35070
35068
  shellCalls,
35069
+ ...judged.verdict === "reproduced" && parsed.claim ? { impact: parsed.claim.impact } : {},
35071
35070
  executedOutput: parsed.claim?.executed_output ?? "",
35072
35071
  harnessFiles: parsed.claim?.harness_files ?? [],
35073
35072
  usage,
@@ -35114,11 +35113,6 @@ async function runReplayViaServer(brief, auth2, budgetMs = 24e4) {
35114
35113
  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 };
35115
35114
  }
35116
35115
  const d = res.data ?? {};
35117
- if (d.enabled === false) {
35118
- const why = d.reason;
35119
- const msg = `formal is off for this project (${typeof why === "string" ? why.slice(0, 40) : "disabled"})`;
35120
- 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 };
35121
- }
35122
35116
  if (d.error && typeof d.error === "object") {
35123
35117
  const e = d.error;
35124
35118
  const msg = `${e.code ?? "SERVICE_ERROR"}: ${e.message ?? "the service failed"}`;
@@ -35131,6 +35125,7 @@ async function runReplayViaServer(brief, auth2, budgetMs = 24e4) {
35131
35125
  verdict: verdict === "reproduced" || verdict === "diverged" || verdict === "unreplayable" ? verdict : "unverified",
35132
35126
  reason: typeof d.reason === "string" ? d.reason : "",
35133
35127
  downgrade: typeof d.downgrade === "string" ? d.downgrade : null,
35128
+ ...isImpact(d.impact) ? { impact: d.impact } : {},
35134
35129
  executedCalls: Number(d.executed_calls ?? 0),
35135
35130
  shellCalls: Number(d.shell_calls ?? 0),
35136
35131
  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" },
@@ -35915,10 +35910,9 @@ function verityLeanHome(env = process.env) {
35915
35910
  function findToolchain(env = process.env) {
35916
35911
  const managedBin = (0, import_node_path43.join)(verityLeanHome(env), LEAN_TOOLCHAIN.replace(/^leanprover\/lean4:/, ""), "bin");
35917
35912
  const elanBin = (0, import_node_path43.join)(env.HOME ?? (0, import_node_os9.homedir)(), ".elan", "bin");
35918
- 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(":", "---")));
35919
- 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)));
35920
- const path = [managedBin, ...elanHas ? [elanBin] : [], ...userPath].join(import_node_path43.delimiter);
35913
+ const path = [managedBin, elanBin, env.PATH ?? ""].filter(Boolean).join(import_node_path43.delimiter);
35921
35914
  const merged = { ...env, PATH: path, ELAN_TOOLCHAIN: env.ELAN_TOOLCHAIN ?? LEAN_TOOLCHAIN };
35915
+ 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(":", "---")));
35922
35916
  const exe = process.platform === "win32" ? "lake.exe" : "lake";
35923
35917
  const candidates2 = [(0, import_node_path43.join)(managedBin, exe), ...elanHas ? [(0, import_node_path43.join)(elanBin, exe)] : [], exe];
35924
35918
  for (const candidate of candidates2) {
@@ -35995,35 +35989,10 @@ function runAsync(tc, cwd, args2, timeoutMs) {
35995
35989
  }
35996
35990
  var DEFAULT_BUILD_TIMEOUT_MS = 10 * 6e4;
35997
35991
  var DEFAULT_CHECK_TIMEOUT_MS = 12e4;
35998
- function proofsWorkspaceProblem(root) {
35999
- for (const rel of [".verity", PROOFS_DIR, `${PROOFS_DIR}/.lake`]) {
36000
- let st;
36001
- try {
36002
- st = (0, import_node_fs58.lstatSync)((0, import_node_path43.join)(root, rel));
36003
- } catch {
36004
- break;
36005
- }
36006
- if (st.isSymbolicLink()) return `${rel} is a symlink; Lean is not run through it`;
36007
- }
36008
- const r = runGitSync(root, ["ls-files", "-z", "--", PROOFS_DIR], { timeoutMs: 5e3 });
36009
- if (!r.ok) return `could not confirm with git that ${PROOFS_DIR} holds no committed files; Lean is not run`;
36010
- const tracked = r.stdout.split("\0").filter(Boolean);
36011
- if (tracked.length > 0) {
36012
- 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}`;
36013
- }
36014
- return null;
36015
- }
36016
- function refused(problem) {
36017
- return { ok: false, stdout: "", stderr: problem, durationMs: 0, timedOut: false };
36018
- }
36019
35992
  function buildLibrary(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
36020
- const problem = proofsWorkspaceProblem(root);
36021
- if (problem) return refused(problem);
36022
35993
  return run2(tc, (0, import_node_path43.join)(root, PROOFS_DIR), ["build"], timeoutMs);
36023
35994
  }
36024
- async function buildLibraryAsync(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
36025
- const problem = proofsWorkspaceProblem(root);
36026
- if (problem) return refused(problem);
35995
+ function buildLibraryAsync(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
36027
35996
  return runAsync(tc, (0, import_node_path43.join)(root, PROOFS_DIR), ["build"], timeoutMs);
36028
35997
  }
36029
35998
  function isModelInRoot(modelsAbs, rel) {
@@ -36050,15 +36019,13 @@ function trimDiagnostics(text) {
36050
36019
  function checkModel(tc, root, modelFile, opts = {}) {
36051
36020
  const proofsAbs = (0, import_node_path43.join)(root, PROOFS_DIR);
36052
36021
  const rel = (0, import_node_path43.relative)(proofsAbs, modelFile);
36053
- const problem = proofsWorkspaceProblem(root);
36054
- const res = problem ? refused(problem) : run2(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
36022
+ const res = run2(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
36055
36023
  return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
36056
36024
  }
36057
36025
  async function checkModelAsync(tc, root, modelFile, opts = {}) {
36058
36026
  const proofsAbs = (0, import_node_path43.join)(root, PROOFS_DIR);
36059
36027
  const rel = (0, import_node_path43.relative)(proofsAbs, modelFile);
36060
- const problem = proofsWorkspaceProblem(root);
36061
- const res = problem ? refused(problem) : await runAsync(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
36028
+ const res = await runAsync(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
36062
36029
  return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
36063
36030
  }
36064
36031
  function toReport(res, root, modelFile, timeoutMs) {
@@ -36113,18 +36080,33 @@ function writeResult(root, report) {
36113
36080
  function isCrashTrace(trace) {
36114
36081
  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));
36115
36082
  }
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
+ }
36116
36097
  function mergeFormalFindings(response, outcome, pushedFiles) {
36117
- if (!outcome) return { blocking: 0 };
36098
+ if (!outcome) return { blocking: 0, warning: 0 };
36118
36099
  const norm2 = (p) => p.replace(/\\/g, "/");
36119
36100
  const pushed = new Set(pushedFiles.map(norm2));
36120
- const blocking = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(norm2(f.file)));
36121
- if (blocking.length === 0) return { blocking: 0 };
36101
+ const reproduced = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(norm2(f.file)));
36102
+ if (reproduced.length === 0) return { blocking: 0, warning: 0 };
36103
+ const blocking = reproduced.filter((f) => formalSeverity(f) === "high");
36122
36104
  const existing = Array.isArray(response.findings) ? response.findings : [];
36123
36105
  response.findings = [
36124
36106
  ...existing,
36125
- ...blocking.map((f) => ({
36107
+ ...reproduced.map((f) => ({
36126
36108
  pattern_id: "formal-counterexample",
36127
- severity: "high",
36109
+ severity: formalSeverity(f),
36128
36110
  scope: "change",
36129
36111
  file: f.file,
36130
36112
  line: outcome.target?.lines?.from ?? null,
@@ -36132,14 +36114,15 @@ function mergeFormalFindings(response, outcome, pushedFiles) {
36132
36114
  // as a title it read backwards (e2e, 2026-09-26). A trace that needs a
36133
36115
  // crash between two writes is labelled: reproducing it means injecting
36134
36116
  // the crash, so it is a crash-consistency finding, not a race.
36135
- 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`,
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${f.impact && f.impact !== "none" ? ` (impact: ${f.impact})` : ""}`,
36136
36118
  description: terminalText(f.reason ?? "", 2e3),
36137
36119
  fix: { description: `Trace: ${terminalText(f.trace.join(" \u2192 ") || "(initial state)", 2e3)}. Replay: ${terminalText(f.reason ?? "reproduced in the container", 2e3)}` },
36138
36120
  source: "formal"
36139
36121
  }))
36140
36122
  ];
36141
- if (response.gate_decision !== "FAIL") response.gate_decision = "FAIL";
36142
- return { blocking: blocking.length };
36123
+ if (blocking.length > 0) response.gate_decision = "FAIL";
36124
+ else if (response.gate_decision === "PASS" || response.gate_decision == null) response.gate_decision = "WARN";
36125
+ return { blocking: blocking.length, warning: reproduced.length - blocking.length };
36143
36126
  }
36144
36127
 
36145
36128
  // src/lib/prove/scalpel.ts
@@ -36458,7 +36441,9 @@ async function runFormalPhase(input) {
36458
36441
  symbol: f.symbol,
36459
36442
  verdict: "reproduced",
36460
36443
  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)`,
36461
- ...f.waived ? { waived: true } : {}
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 } : {}
36462
36447
  });
36463
36448
  }
36464
36449
  }
@@ -36497,7 +36482,6 @@ async function runFormalPhase(input) {
36497
36482
  return done({ status: "skipped", skip: "not-eligible", detail: `${best.file}: ${isEligible(best, largePush).why}` });
36498
36483
  }
36499
36484
  const ranked = await deps.rank(eligible, root, input.changedLines);
36500
- if (!ranked.ok && ranked.disabled) return done({ status: "skipped", skip: "disabled-by-server", detail: ranked.error });
36501
36485
  if (ranked.ok) spend(ranked);
36502
36486
  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)) }));
36503
36487
  if (cancelled()) return done({ status: "skipped", skip: "deadline", detail: "cut off after ranking" });
@@ -36732,7 +36716,7 @@ async function runFormalPhase(input) {
36732
36716
  continue;
36733
36717
  }
36734
36718
  const verdict = r.verdict === "reproduced" ? "reproduced" : r.verdict === "diverged" ? "diverged" : r.verdict === "unreplayable" ? "model-only" : "unverified";
36735
- findings.push({ ...baseOf(p), verdict, reason: r.reason || r.downgrade || void 0 });
36719
+ findings.push({ ...baseOf(p), verdict, reason: r.reason || r.downgrade || void 0, ...verdict === "reproduced" && r.impact ? { impact: r.impact } : {} });
36736
36720
  }
36737
36721
  const reproduced = findings.filter((f) => f.verdict === "reproduced");
36738
36722
  if (reproduced.length > 0 && !cancelled()) {
@@ -36740,7 +36724,7 @@ async function runFormalPhase(input) {
36740
36724
  const others = readPersistedFindings(root).filter((f) => f.file !== top.file);
36741
36725
  writePersistedFindings(root, [
36742
36726
  ...others,
36743
- ...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 }))
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, ...f.impact ? { impact: f.impact } : {}, ...lines ? { lines } : {}, at: now }))
36744
36728
  ]);
36745
36729
  }
36746
36730
  const clean2 = findings.every((f) => f.verdict === "diverged");
@@ -36861,8 +36845,6 @@ async function prepareFormalPhase(root, env = process.env, auth2, readSource, co
36861
36845
  const apiKey = direct ? env.OPENAI_API_KEY : void 0;
36862
36846
  const toolchain = findToolchain(env);
36863
36847
  if (!toolchain) return { ok: false, skip: "no-toolchain" };
36864
- const untrusted = proofsWorkspaceProblem(root);
36865
- if (untrusted) return { ok: false, skip: "workspace-untrusted", detail: untrusted.slice(0, 200) };
36866
36848
  try {
36867
36849
  scaffoldProofs(root);
36868
36850
  } catch (err2) {
@@ -37480,6 +37462,8 @@ async function runGuard(opts, globals) {
37480
37462
  rounds: formalOutcome.model?.rounds ?? null,
37481
37463
  properties: formalOutcome.properties?.map((p) => `${p.id}:${p.result}`) ?? [],
37482
37464
  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}`),
37483
37467
  // Decision 81: what each property rests on (a rule, or "line"), what was
37484
37468
  // sent back or dropped as ungrounded, and which counterexamples lack their
37485
37469
  // property's trigger (shadow). Ids and rule names only, never code.
@@ -38789,22 +38773,6 @@ var import_promises18 = require("node:stream/promises");
38789
38773
  var import_node_stream = require("node:stream");
38790
38774
  var RELEASES = "https://releases.lean-lang.org/lean4";
38791
38775
  var LEAN_DOWNLOAD_SIZE = "\u2248800 MB download, 2.7 GB on disk";
38792
- var DOWNLOAD_STALL_MS = 6e4;
38793
- var LEAN_DISK_NEEDED_BYTES = 3.75 * 1024 ** 3;
38794
- function freeBytes(dir) {
38795
- let d = dir;
38796
- for (let i2 = 0; i2 < 64; i2++) {
38797
- try {
38798
- const st = (0, import_node_fs63.statfsSync)(d);
38799
- return Number(st.bavail) * Number(st.bsize);
38800
- } catch {
38801
- const up = (0, import_node_path47.join)(d, "..");
38802
- if (up === d) return null;
38803
- d = up;
38804
- }
38805
- }
38806
- return null;
38807
- }
38808
38776
  var LEAN_ASSET_SHA256 = {
38809
38777
  "v4.34.0": {
38810
38778
  "lean-4.34.0-darwin.zip": "75a2a4b8e3d35680e8f51fae5a42c79702f463dd73ee1520fb61e5a9143e336c",
@@ -38863,32 +38831,17 @@ function unzip(zip, dest) {
38863
38831
  }
38864
38832
  return { ok: false, error: errors.join("; ") };
38865
38833
  }
38866
- async function download(url, dest, onProgress, stallMs = DOWNLOAD_STALL_MS) {
38867
- const ctrl = new AbortController();
38868
- let timer;
38869
- const arm = () => {
38870
- clearTimeout(timer);
38871
- timer = setTimeout(() => ctrl.abort(), stallMs);
38872
- };
38873
- arm();
38874
- try {
38875
- const res = await fetch(url, { redirect: "follow", signal: ctrl.signal });
38876
- if (!res.ok || !res.body) throw new Error(`download failed: HTTP ${res.status}`);
38877
- const total = Number(res.headers.get("content-length")) || null;
38878
- let seen = 0;
38879
- const stream = import_node_stream.Readable.fromWeb(res.body);
38880
- stream.on("data", (chunk) => {
38881
- arm();
38882
- seen += chunk.length;
38883
- onProgress(seen, total);
38884
- });
38885
- await (0, import_promises18.pipeline)(stream, (0, import_node_fs63.createWriteStream)(dest), { signal: ctrl.signal });
38886
- } catch (err2) {
38887
- if (ctrl.signal.aborted) throw new Error(`download stalled: nothing received for ${Math.round(stallMs / 1e3)} s`);
38888
- throw err2;
38889
- } finally {
38890
- clearTimeout(timer);
38891
- }
38834
+ async function download(url, dest, onProgress) {
38835
+ const res = await fetch(url, { redirect: "follow" });
38836
+ if (!res.ok || !res.body) throw new Error(`download failed: HTTP ${res.status}`);
38837
+ const total = Number(res.headers.get("content-length")) || null;
38838
+ let seen = 0;
38839
+ const stream = import_node_stream.Readable.fromWeb(res.body);
38840
+ stream.on("data", (chunk) => {
38841
+ seen += chunk.length;
38842
+ onProgress(seen, total);
38843
+ });
38844
+ await (0, import_promises18.pipeline)(stream, (0, import_node_fs63.createWriteStream)(dest));
38892
38845
  }
38893
38846
  function inPlaceProgress(label2, write = (s) => process.stderr.write(s)) {
38894
38847
  let active = false;
@@ -38926,12 +38879,6 @@ async function installLean(opts = {}) {
38926
38879
  const dir = pinnedVersionDir(env);
38927
38880
  const url = `${RELEASES}/${pinnedVersion()}/${asset}`;
38928
38881
  const home = verityLeanHome(env);
38929
- const free = freeBytes(home);
38930
- const needed = opts.minFreeBytes ?? LEAN_DISK_NEEDED_BYTES;
38931
- if (free !== null && free < needed) {
38932
- const gb = (n) => (n / 1024 ** 3).toFixed(1);
38933
- return { ok: false, error: `not enough disk space for Lean: ${gb(free)} GB free where ${home} lives, needs about ${gb(needed)} GB` };
38934
- }
38935
38882
  const tmp = (0, import_node_path47.join)(home, ".tmp");
38936
38883
  (0, import_node_fs63.rmSync)(tmp, { recursive: true, force: true });
38937
38884
  (0, import_node_fs63.mkdirSync)(tmp, { recursive: true });
@@ -39240,10 +39187,8 @@ var INTENSITY_CHOICES = [
39240
39187
  { id: "thorough", label: "thorough", hint: "all tools, all rules (~15s)" }
39241
39188
  ];
39242
39189
  var FORMAL_CHOICES = [
39243
- { id: "yes", label: "yes", hint: "a Lean model of the riskiest changed function; blocks only on a REPRODUCED counterexample" },
39244
- // No is the default (review of #255): "yes" downloads ≈800 MB and unpacks
39245
- // 2.7 GB, and a bare Enter must never do that.
39246
- { id: "no", label: "no", hint: "enable later with: verity config formal push", recommended: true }
39190
+ { id: "yes", label: "yes", hint: "a Lean model of the riskiest changed function; blocks only on a REPRODUCED counterexample", recommended: true },
39191
+ { id: "no", label: "no", hint: "enable later with: verity config formal push" }
39247
39192
  ];
39248
39193
  var MOMENT_CHOICES = [
39249
39194
  { id: "stop", label: "On stop", hint: "after every agent turn \u2014 fast feedback while you work", recommended: true },
@@ -39649,20 +39594,16 @@ async function offerFormalVerification(defaultsOnly, pluginMode) {
39649
39594
  printWarn(` Formal verification: off \u2014 ${formalRefusalMessage(cfg.formal_refused)}`);
39650
39595
  return;
39651
39596
  }
39597
+ const toolchain = findToolchain();
39652
39598
  if (cfg.formal === "push") {
39653
- if (findToolchain()) printInfo(" Formal verification at push \u2713");
39599
+ if (toolchain) printInfo(" Formal verification at push \u2713");
39654
39600
  else printWarn(" Formal verification is on but Lean is missing \u2014 run: verity lean install");
39655
39601
  return;
39656
39602
  }
39657
- if (cfg.formal === "off") {
39658
- printInfo(" Formal verification: off (enable with: verity config formal push)");
39659
- return;
39660
- }
39661
39603
  if (defaultsOnly || !interactive()) {
39662
39604
  printInfo(" Formal verification (a model-based bug finder at push): off \u2014 enable with: verity config formal push");
39663
39605
  return;
39664
39606
  }
39665
- const toolchain = findToolchain();
39666
39607
  console.log("");
39667
39608
  console.log(" Formal verification review \u2014 a model-based bug finder for your riskiest code.");
39668
39609
  console.log(" At push, Verity picks the riskiest changed function, checks a small model of");
@@ -39670,10 +39611,9 @@ async function offerFormalVerification(defaultsOnly, pluginMode) {
39670
39611
  console.log(" Verity for that). Only a reproduced bug can block; most pushes get no model.");
39671
39612
  console.log(` Adds /verity-prove <worry> to check any code on demand.${toolchain ? "" : ` Needs to install Lean 4 (${LEAN_DOWNLOAD_SIZE}) on "yes".`}`);
39672
39613
  console.log("");
39673
- const wants = await promptChoice("Formal verification at push?", FORMAL_CHOICES, "no");
39614
+ const wants = await promptChoice("Formal verification at push?", FORMAL_CHOICES, "yes");
39674
39615
  if (wants === "yes" && pluginMode) printInfo(" Plugin: the push check works now; /verity:verity-prove arrives with the plugin's next release.");
39675
39616
  if (wants !== "yes") {
39676
- writeProjectConfig({ formal: "off" });
39677
39617
  printInfo(" Formal verification: off (enable later with: verity config formal push)");
39678
39618
  return;
39679
39619
  }
@@ -39932,7 +39872,7 @@ function registerInitCommand(program2) {
39932
39872
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
39933
39873
  init: {
39934
39874
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
39935
- cli_version: true ? "0.34.0-experimental.37a6dcf" : "dev"
39875
+ cli_version: true ? "0.34.0-experimental.421a631" : "dev"
39936
39876
  }
39937
39877
  });
39938
39878
  } catch (err2) {
@@ -41405,8 +41345,8 @@ function registerLeanCommand(program2) {
41405
41345
  }
41406
41346
 
41407
41347
  // src/cli.ts
41408
- program.name("verity").description("CLI for Verity quality gate service").version("0.34.0-experimental.37a6dcf").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) => {
41409
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.0-experimental.37a6dcf");
41348
+ program.name("verity").description("CLI for Verity quality gate service").version("0.34.0-experimental.421a631").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) => {
41349
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.0-experimental.421a631");
41410
41350
  setUserNamedServiceUrl(program.opts().serviceUrl);
41411
41351
  try {
41412
41352
  await foldLegacyLocalCredential();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.34.0-experimental.37a6dcf",
3
+ "version": "0.34.0-experimental.421a631",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",