@codacy/verity-cli 0.33.3-experimental.52f1656 → 0.33.3-experimental.86b5b6f

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/bin/verity.js CHANGED
@@ -22639,7 +22639,7 @@ function channelSilence(input) {
22639
22639
  // src/lib/cli-version.ts
22640
22640
  function cliVersion() {
22641
22641
  try {
22642
- return true ? "0.33.3-experimental.52f1656" : "dev";
22642
+ return true ? "0.33.3-experimental.86b5b6f" : "dev";
22643
22643
  } catch {
22644
22644
  return "dev";
22645
22645
  }
@@ -26843,7 +26843,7 @@ var RISK_PATTERNS = [
26843
26843
  var SOURCE_EXT = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|go|rs|java|kt|kts|rb|cs|scala|swift|php|ex|exs)$/;
26844
26844
  var EXCLUDED_DIR = /(^|\/)(node_modules|dist|build|bin|out|vendor|coverage|\.verity|\.claude|\.codacy|\.git|__pycache__|target|\.lake)\//;
26845
26845
  var TEST_PATH2 = /(^|\/)(tests?|__tests__|spec|specs|fixtures?|e2e|testdata)\/|\.(test|spec|stories)\.[cm]?[jt]sx?$|_test\.(go|py|rb)$|test_[^/]*\.py$/;
26846
- var GENERATED2 = /\.d\.ts$|\.min\.[jt]s$|\.generated\.|\.pb\.|lock\.json$|\.lock$/;
26846
+ var GENERATED2 = /\.d\.ts$|\.min\.[jt]s$|\.generated\.|\.pb\.|lock\.json$|\.lock$|(^|\/)(integrations\/supabase\/types|database\.types|supabase\.types)\.ts$|(^|\/)__generated__\//;
26847
26847
  function isCandidateSource(file) {
26848
26848
  return SOURCE_EXT.test(file) && !EXCLUDED_DIR.test(file) && !TEST_PATH2.test(file) && !GENERATED2.test(file);
26849
26849
  }
@@ -26982,11 +26982,37 @@ var deterministicRanker = {
26982
26982
  return { ok: true, ranker: "deterministic", ranked: deterministicOrder(candidates2) };
26983
26983
  }
26984
26984
  };
26985
- function excerpt(root, file, bytes, readSource) {
26985
+ function excerpt(root, file, bytes, readSource, changed) {
26986
26986
  const read = readSource ? readSource(file, EXCERPT_SOURCE_CAP_BYTES) : readRegularFileInRoot(root, file, EXCERPT_SOURCE_CAP_BYTES);
26987
26987
  if (!read.ok) return "";
26988
+ if (changed && changed.length > 0) return changedExcerpt(read.content, changed, bytes);
26988
26989
  return read.content.length > bytes ? read.content.slice(0, bytes) + "\n\u2026 (truncated)" : read.content;
26989
26990
  }
26991
+ function changedExcerpt(content, changed, bytes, context = 30) {
26992
+ const lines = content.split("\n");
26993
+ const windows = [...changed].sort((a, b) => a.from - b.from).map((r) => ({ from: Math.max(1, r.from - context), to: Math.min(lines.length, r.to + context) })).reduce((acc, w) => {
26994
+ const last = acc[acc.length - 1];
26995
+ if (last && w.from <= last.to + 1) last.to = Math.max(last.to, w.to);
26996
+ else acc.push({ ...w });
26997
+ return acc;
26998
+ }, []);
26999
+ const isChanged = (n) => changed.some((r) => n >= r.from && n <= r.to);
27000
+ const out = [];
27001
+ let size = 0;
27002
+ for (const w of windows) {
27003
+ if (out.length) out.push(" \u2026");
27004
+ for (let n = w.from; n <= w.to; n++) {
27005
+ const line = `${String(n).padStart(5)}${isChanged(n) ? "*" : " "}| ${lines[n - 1] ?? ""}`;
27006
+ size += line.length + 1;
27007
+ if (size > bytes) {
27008
+ out.push(" \u2026 (truncated)");
27009
+ return out.join("\n");
27010
+ }
27011
+ out.push(line);
27012
+ }
27013
+ }
27014
+ return out.join("\n");
27015
+ }
26990
27016
  var EXCERPT_SOURCE_CAP_BYTES = 512 * 1024;
26991
27017
  var LUNA_SCHEMA = {
26992
27018
  type: "object",
@@ -27012,7 +27038,8 @@ var LUNA_SCHEMA = {
27012
27038
  };
27013
27039
  var LUNA_INSTRUCTIONS = `You select SMALL pieces of code worth building a formal model of (a Lean 4 transition system with bounded exploration). The goal is to find bugs, not to model the codebase.
27014
27040
  For each file you are given its git-history signals and an excerpt. Score each 0-1 for "a model of one function here would likely expose a real bug": high for check-then-act guards, retries, caches, dedup keys, locks/leases, status transitions, money, concurrent writes; low for glue, rendering, configuration, or code whose correctness is obvious from reading.
27015
- Name the single symbol (function) most worth modelling, and the applicable properties from the catalogue only. Reasons: at most 3, each a short concrete sentence about THIS code. Return every file you were given, exactly once.`;
27041
+ Name the single symbol (function) most worth modelling, and the applicable properties from the catalogue only. Reasons: at most 3, each a short concrete sentence about THIS code. Return every file you were given, exactly once.
27042
+ When a file lists \`changed\` lines, this is a review of a push: the excerpt is numbered around the changes (changed lines starred). Name the function that CONTAINS a changed line, and score THAT FUNCTION'S CURRENT CODE as a whole \u2014 how likely a model of it exposes a real bug \u2014 never the edit itself. A one-line change (a log line, a rename, a comment) to a function that checks then acts, retries, dedups, moves money, grants access or changes a status still scores high: the push touched risky code, and a latent bug in it is exactly what this is for. Score low only when the changed function is itself glue, wiring, command registration, rendering, configuration or plainly correct.`;
27016
27043
  var lunaRanker = {
27017
27044
  name: "luna",
27018
27045
  async rank(candidates2, ctx) {
@@ -27025,14 +27052,18 @@ var lunaRanker = {
27025
27052
  if (!apiKey) return { ok: false, ranker: "luna", error: "OPENAI_API_KEY is not set", ranked: fallback };
27026
27053
  const model = env.VERITY_PROVE_MODEL ?? "gpt-6-luna";
27027
27054
  const excerptBytes = ctx.excerptBytes ?? 6 * 1024;
27028
- const payload = candidates2.map((c) => ({
27029
- file: c.file,
27030
- commits: c.commits,
27031
- fix_commits: c.fixCommits,
27032
- risk: c.risk,
27033
- lines: c.lines,
27034
- excerpt: excerpt(ctx.root, c.file, excerptBytes)
27035
- }));
27055
+ const payload = candidates2.map((c) => {
27056
+ const changed = ctx.changedLines?.get(c.file);
27057
+ return {
27058
+ file: c.file,
27059
+ commits: c.commits,
27060
+ fix_commits: c.fixCommits,
27061
+ risk: c.risk,
27062
+ lines: c.lines,
27063
+ excerpt: excerpt(ctx.root, c.file, excerptBytes, ctx.readSource, changed),
27064
+ ...changed?.length ? { changed: changed.map((r) => r.from === r.to ? `${r.from}` : `${r.from}-${r.to}`).join(", ") } : {}
27065
+ };
27066
+ });
27036
27067
  const body = {
27037
27068
  model,
27038
27069
  reasoning: { effort: "low" },
@@ -27087,7 +27118,8 @@ async function rankViaServer(candidates2, ctx, auth2) {
27087
27118
  fix_commits: c.fixCommits,
27088
27119
  risk: c.risk,
27089
27120
  lines: c.lines,
27090
- excerpt: excerpt(ctx.root, c.file, excerptBytes, ctx.readSource)
27121
+ excerpt: excerpt(ctx.root, c.file, excerptBytes, ctx.readSource, ctx.changedLines?.get(c.file)),
27122
+ ...ctx.changedLines?.get(c.file)?.length ? { changed_lines: ctx.changedLines.get(c.file).slice(0, 50) } : {}
27091
27123
  })),
27092
27124
  ...ctx.hint ? { hint: ctx.hint.slice(0, 500) } : {}
27093
27125
  },
@@ -27389,6 +27421,46 @@ function lintModelSource(source) {
27389
27421
  return reasons.length ? { ok: false, reasons: [...new Set(reasons)] } : { ok: true };
27390
27422
  }
27391
27423
 
27424
+ // src/lib/prove/lean-format.ts
27425
+ function joinBrokenLiterals(source) {
27426
+ const raw = source.split("\n");
27427
+ const tok = tokenizeLean(source).code.split("\n");
27428
+ if (raw.length !== tok.length) return source;
27429
+ const outRaw = [];
27430
+ let depth = 0;
27431
+ let i = 0;
27432
+ while (i < raw.length) {
27433
+ let r = raw[i];
27434
+ let t = tok[i];
27435
+ i++;
27436
+ for (; ; ) {
27437
+ const d = depth + braceDelta(t);
27438
+ const end = t.trimEnd();
27439
+ if (!(d > 0 && end.endsWith(",") && i < raw.length)) break;
27440
+ while (i < raw.length && tok[i].trim() === "") i++;
27441
+ if (i >= raw.length) break;
27442
+ const cut = end.length;
27443
+ const nextRaw = raw[i];
27444
+ const nextTok = tok[i];
27445
+ const lead = nextTok.length - nextTok.trimStart().length;
27446
+ r = r.slice(0, cut) + " " + nextRaw.slice(lead);
27447
+ t = t.slice(0, cut) + " " + nextTok.slice(lead);
27448
+ i++;
27449
+ }
27450
+ depth = Math.max(0, depth + braceDelta(t));
27451
+ outRaw.push(r);
27452
+ }
27453
+ return outRaw.join("\n");
27454
+ }
27455
+ function braceDelta(code) {
27456
+ let d = 0;
27457
+ for (const ch of code) {
27458
+ if (ch === "{") d++;
27459
+ else if (ch === "}") d--;
27460
+ }
27461
+ return d;
27462
+ }
27463
+
27392
27464
  // src/lib/prove/generate.ts
27393
27465
  var LIBRARY_API = `The Verity Lean 4 library (already built; \`import Verity\` is the ONLY import allowed):
27394
27466
 
@@ -27460,6 +27532,8 @@ var RULES = `Write ONE Lean 4 model of the code below, in the style of the exemp
27460
27532
  - 2 to 4 properties from this catalogue, all \`kind := .structural\`: idempotence, no lost update, status only moves forward, retries terminate, never fails open, bounded size, check-then-act is atomic, no double effect. Each \`inv\` must be a real claim the explorer can refute; no tautologies. Add an \`intent\` property ONLY if a comment in the source states the expectation, and cite its line in \`source\`.
27461
27533
  - The header: \`abstracts := [{ file, symbol, sha256, lines }]\` with the exact values given below (lines "" when none).
27462
27534
  - Exactly one \`#eval Verity.model\` and one \`#eval Verity.check\` per property, \`{ bound := 6 }\` or less. No other \`#eval\`, no \`IO\`, no \`sorry\`, no attributes, no macros, no \`partial\`. Under 120 lines.
27535
+ - Write every structure literal on ONE line: the \`abstracts\` entry, each \`{ s with \u2026 }\` update, each \`{ field := \u2026 }\` value. Lean reads a literal split across lines by its indentation and refuses it.
27536
+ - A property must be something the function PROMISES. Do not claim idempotence or no double effect for an operation whose job is to apply an effect on every call (a withdrawal, a charge, a send) unless the code dedups it (an idempotency key, a unique insert, an explicit check). When a trace needs a crash between two writes, say so in \`desc\` ("after a crash between X and Y, \u2026").
27463
27537
  - Imports beyond \`Verity\` are refused by a linter; a file that does not elaborate gets ONE more attempt with the compiler's message.
27464
27538
  Answer with JSON only: {"name": "<PascalCase>", "lean_source": "<the whole file>", "summary": "<one sentence: what the model abstracts and which property is most likely to fail>"}.`;
27465
27539
  var SCHEMA = {
@@ -27484,7 +27558,7 @@ function renderGenerateInput(t, previous) {
27484
27558
  parts.push("```");
27485
27559
  if (previous) {
27486
27560
  parts.push("");
27487
- parts.push("# Your previous attempt did not elaborate. Fix it (same model, same properties):");
27561
+ parts.push("# Your previous attempt was not accepted \u2014 the diagnostics say why. Fix it (same model, same properties):");
27488
27562
  parts.push("```lean");
27489
27563
  parts.push(previous.source);
27490
27564
  parts.push("```");
@@ -27497,7 +27571,7 @@ function renderGenerateInput(t, previous) {
27497
27571
  }
27498
27572
  function acceptAnswer(parsed, usage, costUsd = null) {
27499
27573
  const name = typeof parsed.name === "string" && /^[A-Z][A-Za-z0-9]{1,40}$/.test(parsed.name) ? parsed.name : null;
27500
- const source = typeof parsed.lean_source === "string" ? parsed.lean_source.replace(/\r\n/g, "\n") : null;
27574
+ const source = typeof parsed.lean_source === "string" ? joinBrokenLiterals(parsed.lean_source.replace(/\r\n/g, "\n")) : null;
27501
27575
  if (!name || !source) return { ok: false, error: "answer lacked a valid name or lean_source", usage, costUsd };
27502
27576
  const lint = lintModelSource(source);
27503
27577
  if (!lint.ok) return { ok: false, error: `linter refused the model: ${lint.reasons.join("; ")}`, usage, costUsd };
@@ -27510,12 +27584,15 @@ async function generateModel(target, opts) {
27510
27584
  path: "/prove/generate",
27511
27585
  serviceUrl: opts.auth.serviceUrl,
27512
27586
  token: opts.auth.token,
27513
- body: { target, ...opts.previous ? { previous: opts.previous } : {}, ...opts.budgetMs ? { budget_ms: Math.max(2e4, Math.round(opts.budgetMs)) } : {} },
27587
+ // Streamed with a heartbeat so a long generation cannot die idle (decision 53).
27588
+ extraHeaders: { "X-Verity-Keepalive": "1" },
27589
+ body: { target, tier: opts.tier ?? "deep", ...opts.previous ? { previous: opts.previous } : {}, ...opts.budgetMs ? { budget_ms: Math.max(2e4, Math.round(opts.budgetMs)) } : {} },
27514
27590
  timeout: opts.budgetMs ? Math.max(2e4, Math.round(opts.budgetMs)) + 15e3 : opts.timeoutMs ?? 13e4,
27515
27591
  cmd: "prove-generate"
27516
27592
  });
27517
27593
  if (!res.ok) return { ok: false, error: res.error };
27518
27594
  const d = res.data ?? {};
27595
+ if (d.error && typeof d.error === "object") return { ok: false, error: `${d.error.code ?? "SERVICE_ERROR"}: ${d.error.message ?? "the service failed"}` };
27519
27596
  const usage2 = d.usage && typeof d.usage === "object" ? { inputTokens: Number(d.usage.inputTokens ?? 0), outputTokens: Number(d.usage.outputTokens ?? 0) } : null;
27520
27597
  return acceptAnswer(d, usage2, typeof d.cost_usd === "number" ? d.cost_usd : null);
27521
27598
  }
@@ -27525,7 +27602,7 @@ async function generateModel(target, opts) {
27525
27602
  const fetchImpl = opts.fetchImpl ?? fetch;
27526
27603
  const baseUrl = opts.baseUrl ?? "https://api.openai.com/v1";
27527
27604
  const body = {
27528
- model: opts.model ?? "gpt-5.6-terra",
27605
+ model: opts.model ?? (opts.tier === "fast" ? "gpt-6-luna" : "gpt-5.6-terra"),
27529
27606
  instructions: `${RULES}
27530
27607
 
27531
27608
  ${LIBRARY_API}
@@ -27535,7 +27612,7 @@ ${LIBRARY_API}
27535
27612
  ${EXEMPLAR}
27536
27613
  \`\`\``,
27537
27614
  input: [{ role: "user", content: renderGenerateInput(target, opts.previous) }],
27538
- reasoning: { effort: "medium" },
27615
+ reasoning: { effort: opts.tier === "fast" ? "low" : "medium" },
27539
27616
  max_output_tokens: 12e3,
27540
27617
  store: false,
27541
27618
  text: { format: { type: "json_schema", name: "lean_model", schema: SCHEMA, strict: true } }
@@ -27963,6 +28040,8 @@ async function runReplayViaServer(brief, auth2, budgetMs = 24e4) {
27963
28040
  budget_ms: budget
27964
28041
  },
27965
28042
  timeout: budget + 15e3,
28043
+ // Streamed with a heartbeat so a long replay cannot die idle (decision 53).
28044
+ extraHeaders: { "X-Verity-Keepalive": "1" },
27966
28045
  cmd: "prove-replay"
27967
28046
  });
27968
28047
  const none = { covered: 0, total: 0, ratio: 0, basis: "none" };
@@ -27970,6 +28049,11 @@ async function runReplayViaServer(brief, auth2, budgetMs = 24e4) {
27970
28049
  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 };
27971
28050
  }
27972
28051
  const d = res.data ?? {};
28052
+ if (d.error && typeof d.error === "object") {
28053
+ const e = d.error;
28054
+ const msg = `${e.code ?? "SERVICE_ERROR"}: ${e.message ?? "the service failed"}`;
28055
+ 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 };
28056
+ }
27973
28057
  const verdict = d.verdict;
27974
28058
  const v = typeof d.verbatim === "object" && d.verbatim ? d.verbatim : none;
27975
28059
  return {
@@ -28929,7 +29013,7 @@ function writeResult(root, report) {
28929
29013
 
28930
29014
  // src/lib/prove/auto.ts
28931
29015
  var AUTO_MODELS_DIR = `${MODELS_DIR}/.auto`;
28932
- var DEFAULT_MIN_SCORE = 0.7;
29016
+ var DEFAULT_MIN_SCORE = 0.75;
28933
29017
  var LARGE_PUSH_FILES = 25;
28934
29018
  var DAILY_MODEL_CAP = 5;
28935
29019
  var PUSH_COST_CAP_USD = 1;
@@ -29007,7 +29091,11 @@ function bumpLedger(root) {
29007
29091
  const l = readLedger(root);
29008
29092
  writeLedger(root, { ...l, models: l.models + 1 });
29009
29093
  }
29010
- var REPLAY_RESERVE_MS = 75e3;
29094
+ var REPLAY_RESERVE_MS = 45e3;
29095
+ var MAX_REPLAYS = 3;
29096
+ function isNetworkError(message) {
29097
+ return /Network error|fetch failed|ETIMEDOUT|ECONNRESET|ECONNREFUSED|EAI_AGAIN|socket hang up/i.test(message);
29098
+ }
29011
29099
  var GENERATE_RESERVE_MS = 4e4;
29012
29100
  function workingTreeReader(root) {
29013
29101
  return (file, maxBytes) => {
@@ -29021,8 +29109,9 @@ function sha256Of(content) {
29021
29109
  function defaultDeps(opts) {
29022
29110
  const env = opts.env ?? process.env;
29023
29111
  return {
29024
- rank: (candidates2, root) => lunaRanker.rank(candidates2, { root, auth: opts.auth, env, fetchImpl: opts.fetchImpl, readSource: opts.readSource }),
29025
- generate: (target, previous, budgetMs) => generateModel(target, { auth: opts.auth, apiKey: opts.apiKey, env, fetchImpl: opts.fetchImpl, previous, ...budgetMs ? { budgetMs } : {} }),
29112
+ rank: (candidates2, root, changedLines) => lunaRanker.rank(candidates2, { root, auth: opts.auth, env, fetchImpl: opts.fetchImpl, readSource: opts.readSource, ...changedLines ? { changedLines } : {} }),
29113
+ // The push phase writes with the FAST tier; `prove generate` asks for DEEP (decision 56).
29114
+ generate: (target, previous, budgetMs) => generateModel(target, { auth: opts.auth, apiKey: opts.apiKey, env, fetchImpl: opts.fetchImpl, previous, tier: opts.tier ?? "fast", ...budgetMs ? { budgetMs } : {} }),
29026
29115
  check: (root, modelFile) => checkModelAsync(opts.toolchain, root, modelFile),
29027
29116
  replay: (root, report, property, modelSource, budgetMs) => runReplay(buildReplayBrief(root, report, property, modelSource, opts.readSource), { auth: opts.auth, apiKey: opts.apiKey, env, fetchImpl: opts.fetchImpl, maxRounds: 2, ...budgetMs ? { timeoutMs: budgetMs } : {} }),
29028
29117
  now: () => Date.now(),
@@ -29107,33 +29196,56 @@ async function runFormalPhase(input) {
29107
29196
  const best = signals[0];
29108
29197
  return done({ status: "skipped", skip: "not-eligible", detail: `${best.file}: ${isEligible(best, largePush).why}` });
29109
29198
  }
29110
- const ranked = await deps.rank(eligible, root);
29199
+ const ranked = await deps.rank(eligible, root, input.changedLines);
29111
29200
  if (ranked.ok) spend(ranked);
29112
29201
  if (cancelled()) return done({ status: "skipped", skip: "deadline", detail: "cut off after ranking" });
29113
- const top = ranked.ranked.find((c) => c.ai && c.ai.score >= (input.minScore ?? DEFAULT_MIN_SCORE));
29114
- if (!top || !top.ai) return done({ status: "skipped", skip: "no-candidate", detail: ranked.ok ? `best score ${Math.round((ranked.ranked[0]?.ai?.score ?? 0) * 100)}% of ${eligible.length} eligible` : `rank unavailable: ${ranked.error ?? ""}` });
29115
- if (!top.ai.symbol) return done({ status: "skipped", skip: "no-symbol", detail: top.file });
29116
- const read = readSrc(top.file, 96 * 1024);
29117
- if (!read.ok) return done({ status: "skipped", skip: "read-failed", detail: `${top.file}: ${read.reason}` });
29118
- const sha256 = sha256Of(read.content);
29119
- const body = extractSymbolBody(read.content, top.ai.symbol, top.file);
29120
- let lines;
29121
- let numbered;
29122
- if (body) {
29123
- const from = read.content.split("\n").findIndex((_, i, arr) => arr.slice(i, i + body.split("\n").length).join("\n") === body) + 1;
29124
- lines = from > 0 ? { from, to: from + body.split("\n").length - 1 } : void 0;
29125
- numbered = numberLines(body, from > 0 ? from : 1);
29126
- } else {
29127
- numbered = numberLines(read.content.slice(0, 24 * 1024));
29202
+ const minScore = input.minScore ?? DEFAULT_MIN_SCORE;
29203
+ const qualified = ranked.ranked.filter((c) => c.ai && c.ai.score >= minScore);
29204
+ if (qualified.length === 0) return done({ status: "skipped", skip: "no-candidate", detail: ranked.ok ? `best score ${Math.round((ranked.ranked[0]?.ai?.score ?? 0) * 100)}% of ${eligible.length} eligible` : `rank unavailable: ${ranked.error ?? ""}` });
29205
+ let chosen = null;
29206
+ let why = null;
29207
+ const note = (skip, detail) => {
29208
+ if (!why) why = { skip, detail };
29209
+ };
29210
+ for (const c of qualified) {
29211
+ if (!c.ai?.symbol) {
29212
+ note("no-symbol", c.file);
29213
+ continue;
29214
+ }
29215
+ const read = readSrc(c.file, 96 * 1024);
29216
+ if (!read.ok) {
29217
+ note("read-failed", `${c.file}: ${read.reason}`);
29218
+ continue;
29219
+ }
29220
+ const d = describeTarget(read.content, c.ai.symbol, c.file);
29221
+ if (input.changedLines) {
29222
+ const changed = input.changedLines.get(c.file) ?? [];
29223
+ if (changed.length === 0) {
29224
+ note("symbol-not-in-push", `${c.file}: no changed lines in this push`);
29225
+ continue;
29226
+ }
29227
+ if (!d.lines) {
29228
+ note("symbol-not-in-push", `${c.ai.symbol}: body not found, cannot tell whether the push touched it`);
29229
+ continue;
29230
+ }
29231
+ if (!intersects(d.lines, changed)) {
29232
+ note("symbol-not-in-push", `${c.ai.symbol} (lines ${d.lines.from}\u2013${d.lines.to}) unchanged by this push`);
29233
+ continue;
29234
+ }
29235
+ }
29236
+ chosen = { top: c, content: read.content, lines: d.lines, numbered: d.numbered };
29237
+ break;
29128
29238
  }
29239
+ if (!chosen || !chosen.top.ai) {
29240
+ const first = qualified[0];
29241
+ const t0 = first.ai?.symbol ? { file: first.file, symbol: first.ai.symbol, score: first.ai.score, reasons: [...first.reasons, ...first.ai.reasons] } : void 0;
29242
+ return done({ status: "skipped", skip: why?.skip ?? "no-symbol", detail: why?.detail ?? first.file, ...t0 ? { target: t0 } : {} });
29243
+ }
29244
+ const top = chosen.top;
29245
+ const sha256 = sha256Of(chosen.content);
29246
+ const { lines, numbered } = chosen;
29129
29247
  const target = { file: top.file, symbol: top.ai.symbol, lines, score: top.ai.score, reasons: [...top.reasons, ...top.ai.reasons] };
29130
29248
  progress.target = target;
29131
- if (input.changedLines) {
29132
- const changed = input.changedLines.get(top.file) ?? [];
29133
- if (changed.length === 0) return done({ status: "skipped", skip: "symbol-not-in-push", detail: `${top.file}: no changed lines in this push`, target });
29134
- if (!lines) return done({ status: "skipped", skip: "symbol-not-in-push", detail: `${top.ai.symbol}: body not found, cannot tell whether the push touched it`, target });
29135
- if (!intersects(lines, changed)) return done({ status: "skipped", skip: "symbol-not-in-push", detail: `${top.ai.symbol} (lines ${lines.from}\u2013${lines.to}) unchanged by this push`, target });
29136
- }
29137
29249
  if (timeLeft() < GENERATE_RESERVE_MS) return done({ status: "skipped", skip: "not-enough-time", detail: `${Math.round(timeLeft() / 1e3)} s left before generation`, target });
29138
29250
  bumpLedger(root);
29139
29251
  const genTarget = { file: top.file, symbol: top.ai.symbol, lines, sha256, numbered, reasons: target.reasons, properties: top.ai.properties };
@@ -29150,8 +29262,11 @@ async function runFormalPhase(input) {
29150
29262
  spend(gen);
29151
29263
  if (cancelled()) return done({ status: "skipped", skip: "deadline", detail: "cut off during generation", target });
29152
29264
  if (!gen.ok) {
29153
- if (round === 2 || !/linter refused/.test(gen.error)) return done({ status: "skipped", skip: "generation-failed", detail: gen.error, target });
29154
- previous = { source: "", error: gen.error };
29265
+ const network = isNetworkError(gen.error);
29266
+ if (round === 2 || !(network || /linter refused/.test(gen.error)) || timeLeft() < GENERATE_RESERVE_MS) {
29267
+ return done({ status: "skipped", skip: "generation-failed", detail: gen.error, target });
29268
+ }
29269
+ if (!network) previous = { source: "", error: gen.error };
29155
29270
  continue;
29156
29271
  }
29157
29272
  modelName = gen.name;
@@ -29184,27 +29299,51 @@ async function runFormalPhase(input) {
29184
29299
  writeResult(root, report);
29185
29300
  } catch {
29186
29301
  }
29187
- let replays = 0;
29188
- for (const p of report.properties) {
29189
- if (p.result !== "counterexample") continue;
29190
- const base = { property: p.id, desc: p.desc, kind: p.kind, trace: p.trace ?? [], file: top.file, symbol: top.ai.symbol };
29191
- if (replays >= (input.maxReplays ?? 2) || timeLeft() < REPLAY_RESERVE_MS) {
29192
- findings.push({ ...base, verdict: "model-only" });
29193
- continue;
29194
- }
29195
- if (overCap()) {
29196
- findings.push({ ...base, verdict: "model-only", reason: `not replayed: $${usage.costUsd.toFixed(2)} spent, cap $${costCap.toFixed(2)}` });
29197
- continue;
29302
+ const cexes = report.properties.filter((p) => p.result === "counterexample");
29303
+ const baseOf = (p) => ({ property: p.id, desc: p.desc, kind: p.kind, trace: p.trace ?? [], file: top.file, symbol: top.ai.symbol });
29304
+ const maxReplays = input.maxReplays ?? MAX_REPLAYS;
29305
+ let skipWhy = null;
29306
+ if (timeLeft() < REPLAY_RESERVE_MS) skipWhy = `not replayed: ${Math.round(timeLeft() / 1e3)} s left`;
29307
+ else if (overCap()) skipWhy = `not replayed: $${usage.costUsd.toFixed(2)} spent, cap $${costCap.toFixed(2)}`;
29308
+ else if (cancelled()) skipWhy = "not replayed: the phase was cut off";
29309
+ const toReplay = skipWhy ? [] : cexes.slice(0, maxReplays);
29310
+ const budget = Math.max(2e4, timeLeft() - 5e3);
29311
+ const byId = /* @__PURE__ */ new Map();
29312
+ let stoppedEarly = false;
29313
+ await new Promise((resolve8) => {
29314
+ let left = toReplay.length;
29315
+ if (left === 0) return resolve8();
29316
+ const settle = () => {
29317
+ if (--left === 0) resolve8();
29318
+ };
29319
+ for (const p of toReplay) {
29320
+ void (async () => {
29321
+ if (cancelled()) {
29322
+ byId.set(p.id, null);
29323
+ return;
29324
+ }
29325
+ const r = await deps.replay(root, report, p, modelSource.ok ? modelSource.content : "", budget);
29326
+ spend(r);
29327
+ if (!stoppedEarly) byId.set(p.id, r);
29328
+ if (r.verdict === "reproduced" && !stoppedEarly) {
29329
+ stoppedEarly = true;
29330
+ resolve8();
29331
+ }
29332
+ })().catch(() => {
29333
+ byId.set(p.id, null);
29334
+ }).finally(settle);
29198
29335
  }
29199
- if (cancelled()) {
29200
- findings.push({ ...base, verdict: "model-only", reason: "not replayed: the phase was cut off" });
29336
+ });
29337
+ const replayedIds = new Set(toReplay.map((p) => p.id));
29338
+ for (const p of cexes) {
29339
+ const r = byId.get(p.id);
29340
+ if (!r) {
29341
+ const reason = skipWhy ?? (replayedIds.has(p.id) && byId.has(p.id) ? "not replayed: the phase was cut off" : replayedIds.has(p.id) ? "not waited for: another counterexample already reproduced" : `not replayed: ${maxReplays} per push`);
29342
+ findings.push({ ...baseOf(p), verdict: "model-only", reason });
29201
29343
  continue;
29202
29344
  }
29203
- replays++;
29204
- const r = await deps.replay(root, report, p, modelSource.ok ? modelSource.content : "", Math.max(2e4, timeLeft() - 5e3));
29205
- spend(r);
29206
29345
  const verdict = r.verdict === "reproduced" ? "reproduced" : r.verdict === "diverged" ? "diverged" : r.verdict === "unreplayable" ? "model-only" : "unverified";
29207
- findings.push({ ...base, verdict, reason: r.reason || r.downgrade || void 0 });
29346
+ findings.push({ ...baseOf(p), verdict, reason: r.reason || r.downgrade || void 0 });
29208
29347
  }
29209
29348
  const reproduced = findings.filter((f) => f.verdict === "reproduced");
29210
29349
  if (reproduced.length > 0 && !cancelled()) {
@@ -29223,6 +29362,63 @@ async function runFormalPhase(input) {
29223
29362
  findings
29224
29363
  });
29225
29364
  }
29365
+ function describeTarget(content, symbol, file, range) {
29366
+ const all = content.split("\n");
29367
+ if (range) {
29368
+ const from2 = Math.max(1, range.from);
29369
+ const to = Math.min(all.length, range.to);
29370
+ return { lines: { from: from2, to }, numbered: numberLines(all.slice(from2 - 1, to).join("\n"), from2) };
29371
+ }
29372
+ const body = extractSymbolBody(content, symbol, file);
29373
+ if (!body) return { numbered: numberLines(content.slice(0, 24 * 1024)) };
29374
+ const n = body.split("\n").length;
29375
+ const from = all.findIndex((_, i) => all.slice(i, i + n).join("\n") === body) + 1;
29376
+ return { lines: from > 0 ? { from, to: from + n - 1 } : void 0, numbered: numberLines(body, from > 0 ? from : 1) };
29377
+ }
29378
+ async function generateAndCheck(root, req, opts) {
29379
+ const readSrc = opts.deps.readSource ?? workingTreeReader(root);
29380
+ const read = readSrc(req.file, 96 * 1024);
29381
+ if (!read.ok) return { ok: false, error: `${req.file}: ${read.reason}`, rounds: 0, costUsd: 0 };
29382
+ const sha256 = sha256Of(read.content);
29383
+ const { lines, numbered } = describeTarget(read.content, req.symbol, req.file, req.lines);
29384
+ const target = {
29385
+ file: req.file,
29386
+ symbol: req.symbol,
29387
+ lines,
29388
+ sha256,
29389
+ numbered,
29390
+ reasons: req.hint ? [`the user's worry, in their words: ${req.hint.slice(0, 280)}`] : [],
29391
+ properties: []
29392
+ };
29393
+ const dir = opts.modelsDir ?? MODELS_DIR;
29394
+ const maxRounds = opts.maxRounds ?? 3;
29395
+ let previous = req.revise ? { source: req.revise.source, error: `The model elaborated, but its counterexample DIVERGED when replayed on the real code: ${req.revise.note.slice(0, 2e3)}. Revise the model so it matches what the code does; keep the same properties.` } : void 0;
29396
+ let costUsd = 0;
29397
+ let lastError = "no attempt";
29398
+ for (let round = 1; round <= maxRounds; round++) {
29399
+ const gen = await opts.deps.generate(target, previous);
29400
+ if (typeof gen.costUsd === "number") costUsd += gen.costUsd;
29401
+ if (!gen.ok) {
29402
+ lastError = gen.error;
29403
+ const network = isNetworkError(gen.error);
29404
+ if (!network && !/linter refused/.test(gen.error)) return { ok: false, error: gen.error, rounds: round, costUsd };
29405
+ if (!network) previous = { source: "", error: gen.error };
29406
+ continue;
29407
+ }
29408
+ const rel = `${dir}/${gen.name}.lean`;
29409
+ writeFileInRootNoFollow(root, rel, gen.source);
29410
+ const abs = resolveModelFile(root, rel) ?? (0, import_node_path41.join)(root, rel);
29411
+ const report = await opts.deps.check(root, abs);
29412
+ const abstracts = report.header?.abstracts ?? [];
29413
+ const stray = abstracts.find((a) => a.file !== req.file);
29414
+ if (report.ok && report.properties.length > 0 && abstracts.length > 0 && !stray) {
29415
+ return { ok: true, name: gen.name, file: rel, report, rounds: round, costUsd };
29416
+ }
29417
+ lastError = stray ? `the model abstracts ${stray.file}, not ${req.file}` : report.error ?? (report.malformed ? "the model printed malformed VERITY lines" : "the model reported no property");
29418
+ previous = { source: gen.source, error: lastError };
29419
+ }
29420
+ return { ok: false, error: `no model of ${req.symbol} elaborated in ${maxRounds} round(s): ${lastError.slice(0, 300)}`, rounds: maxRounds, costUsd };
29421
+ }
29226
29422
  function skippedOutcome(skip, detail) {
29227
29423
  return { status: "skipped", skip, ...detail ? { detail } : {}, findings: [], usage: { inputTokens: 0, outputTokens: 0, costUsd: 0, unpriced: 0 }, durationMs: 0 };
29228
29424
  }
@@ -29283,6 +29479,9 @@ function describeFormalOutcome(o) {
29283
29479
  }
29284
29480
 
29285
29481
  // src/lib/prove/merge.ts
29482
+ function isCrashTrace(trace) {
29483
+ 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));
29484
+ }
29286
29485
  function mergeFormalFindings(response, outcome, pushedFiles) {
29287
29486
  if (!outcome) return { blocking: 0 };
29288
29487
  const norm = (p) => p.replace(/\\/g, "/");
@@ -29298,7 +29497,11 @@ function mergeFormalFindings(response, outcome, pushedFiles) {
29298
29497
  scope: "change",
29299
29498
  file: f.file,
29300
29499
  line: outcome.target?.lines?.from ?? null,
29301
- title: `Formal: ${terminalText(f.desc, 300)} \u2014 reproduced on the real code (${terminalText(f.symbol, 128)})`,
29500
+ // Worded as the VIOLATION: the property text is what should hold, and
29501
+ // as a title it read backwards (e2e, 2026-09-26). A trace that needs a
29502
+ // crash between two writes is labelled: reproducing it means injecting
29503
+ // the crash, so it is a crash-consistency finding, not a race.
29504
+ 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`,
29302
29505
  description: terminalText(f.reason ?? "", 2e3),
29303
29506
  fix: { description: `Trace: ${terminalText(f.trace.join(" \u2192 ") || "(initial state)", 2e3)}. Replay: ${terminalText(f.reason ?? "reproduced in the container", 2e3)}` },
29304
29507
  source: "formal"
@@ -31036,13 +31239,6 @@ async function promptYes(question, opts) {
31036
31239
  const a = answer.toLowerCase();
31037
31240
  return a === "" || a === "y" || a === "yes";
31038
31241
  }
31039
- async function promptYesDefaultNo(question) {
31040
- if (!interactive()) return false;
31041
- const answer = await ask(question);
31042
- if (answer === null) return false;
31043
- const a = answer.toLowerCase();
31044
- return a === "y" || a === "yes";
31045
- }
31046
31242
  function printOptions(question, choices) {
31047
31243
  console.log("");
31048
31244
  console.log(` ${question}`);
@@ -31162,6 +31358,32 @@ async function download(url, dest, onProgress) {
31162
31358
  });
31163
31359
  await (0, import_promises18.pipeline)(stream, (0, import_node_fs60.createWriteStream)(dest));
31164
31360
  }
31361
+ function inPlaceProgress(label2, write = (s) => process.stderr.write(s)) {
31362
+ let active = false;
31363
+ let lastAt = 0;
31364
+ let lastPct = -1;
31365
+ const mb = (n) => (n / 1048576).toFixed(0);
31366
+ return {
31367
+ update(seen, total) {
31368
+ const now = Date.now();
31369
+ const pct = total ? Math.min(100, Math.floor(seen / total * 100)) : -1;
31370
+ if (now - lastAt < 100 && pct !== 100 && pct === lastPct) return;
31371
+ lastAt = now;
31372
+ lastPct = pct;
31373
+ active = true;
31374
+ const width = 24;
31375
+ const filled = pct >= 0 ? Math.round(pct / 100 * width) : 0;
31376
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
31377
+ const tail = total ? `${String(pct).padStart(3)}% ${mb(seen)}/${mb(total)} MB` : `${mb(seen)} MB`;
31378
+ write(`\r\x1B[2K${GREEN}[verity]${NC} ${label2} ${bar} ${tail}`);
31379
+ },
31380
+ done() {
31381
+ if (!active) return;
31382
+ active = false;
31383
+ write("\n");
31384
+ }
31385
+ };
31386
+ }
31165
31387
  async function installLean(opts = {}) {
31166
31388
  const say = opts.onStatus ?? (() => {
31167
31389
  });
@@ -31180,6 +31402,7 @@ async function installLean(opts = {}) {
31180
31402
  let lastPct = -1;
31181
31403
  try {
31182
31404
  await download(url, zip, (seen, total) => {
31405
+ if (opts.onProgress) return opts.onProgress(seen, total);
31183
31406
  const pct = total ? Math.floor(seen / total * 100) : -1;
31184
31407
  if (pct !== lastPct && pct % 10 === 0) {
31185
31408
  lastPct = pct;
@@ -31478,6 +31701,10 @@ var INTENSITY_CHOICES = [
31478
31701
  { id: "balanced", label: "balanced", hint: "security + quality (~8s)", recommended: true },
31479
31702
  { id: "thorough", label: "thorough", hint: "all tools, all rules (~15s)" }
31480
31703
  ];
31704
+ var FORMAL_CHOICES = [
31705
+ { id: "yes", label: "yes", hint: "a Lean model of the riskiest changed function; blocks only on a REPRODUCED counterexample", recommended: true },
31706
+ { id: "no", label: "no", hint: "enable later with: verity config formal push" }
31707
+ ];
31481
31708
  var MOMENT_CHOICES = [
31482
31709
  { id: "stop", label: "On stop", hint: "after every agent turn \u2014 fast feedback while you work", recommended: true },
31483
31710
  { id: "pre-commit", label: "Before commit", hint: "reviews the staged diff, blocks the commit on FAIL" },
@@ -31804,7 +32031,7 @@ async function checkPrerequisites(step) {
31804
32031
  }
31805
32032
  return prereqs.checks.some((c) => c.id === "claude" && c.status === "ok");
31806
32033
  }
31807
- async function scaffoldProject(step, defaultsOnly) {
32034
+ async function scaffoldProject(step, defaultsOnly, pluginMode) {
31808
32035
  step("Knowledge base, .gitignore and CLAUDE.md");
31809
32036
  await (0, import_promises20.mkdir)(VERITY_DIR, { recursive: true });
31810
32037
  await ensureMemoryDir();
@@ -31859,9 +32086,9 @@ async function scaffoldProject(step, defaultsOnly) {
31859
32086
  } catch (err) {
31860
32087
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
31861
32088
  }
31862
- await offerFormalVerification(defaultsOnly);
32089
+ await offerFormalVerification(defaultsOnly, pluginMode);
31863
32090
  }
31864
- async function offerFormalVerification(defaultsOnly) {
32091
+ async function offerFormalVerification(defaultsOnly, pluginMode) {
31865
32092
  const cfg = readProjectConfig();
31866
32093
  const toolchain = findToolchain();
31867
32094
  if (cfg.formal === "push") {
@@ -31869,19 +32096,25 @@ async function offerFormalVerification(defaultsOnly) {
31869
32096
  else printWarn(" Formal verification is on but Lean is missing \u2014 run: verity lean install");
31870
32097
  return;
31871
32098
  }
31872
- if (defaultsOnly) {
31873
- printInfo(" Formal verification (Lean models of risky code at push): off \u2014 enable with: verity config formal push");
32099
+ if (defaultsOnly || !interactive()) {
32100
+ printInfo(" Formal verification (a model-based bug finder at push): off \u2014 enable with: verity config formal push");
31874
32101
  return;
31875
32102
  }
31876
- const wants = await promptYesDefaultNo(
31877
- ` Enable formal verification at push? Verity writes a Lean model of the riskiest changed code, checks it here, and replays counterexamples on the real code \u2014 the changed function's source is sent to the Verity service and to OpenAI's container for that (uses your Verity login)${toolchain ? "" : `; downloads Lean 4: ${LEAN_DOWNLOAD_SIZE}`} [y/N] `
31878
- );
31879
- if (!wants) {
32103
+ console.log("");
32104
+ console.log(" Formal verification review \u2014 a model-based bug finder for your riskiest code.");
32105
+ console.log(" At push, Verity picks the riskiest changed function, checks a small model of");
32106
+ console.log(" it, and replays any counterexample on the real code (its source is sent to");
32107
+ console.log(" Verity for that). Only a reproduced bug can block; most pushes get no model.");
32108
+ console.log(` Adds /verity-prove <worry> to check any code on demand.${toolchain ? "" : ` Needs to install Lean 4 (${LEAN_DOWNLOAD_SIZE}) on "yes".`}`);
32109
+ console.log("");
32110
+ const wants = await promptChoice("Formal verification at push?", FORMAL_CHOICES, "yes");
32111
+ if (wants === "yes" && pluginMode) printInfo(" Plugin: the push check works now; /verity:verity-prove arrives with the plugin's next release.");
32112
+ if (wants !== "yes") {
31880
32113
  printInfo(" Formal verification: off (enable later with: verity config formal push)");
31881
32114
  return;
31882
32115
  }
31883
32116
  if (!toolchain) {
31884
- const result = await installLean({ onStatus: (line) => printInfo(` ${line}`) });
32117
+ const result = await installLeanWithProgress();
31885
32118
  if (!result.ok) {
31886
32119
  printWarn(` Lean install failed: ${result.error} \u2014 formal verification left off; retry with: verity lean install`);
31887
32120
  return;
@@ -31891,6 +32124,18 @@ async function offerFormalVerification(defaultsOnly) {
31891
32124
  writeProjectConfig({ formal: "push" });
31892
32125
  printInfo(" Formal verification at push \u2713 (this checkout; `verity config formal off` turns it off)");
31893
32126
  }
32127
+ async function installLeanWithProgress() {
32128
+ const progress = process.stderr.isTTY ? inPlaceProgress(`downloading ${leanAssetName() ?? "Lean 4"}`) : null;
32129
+ const result = await installLean({
32130
+ onStatus: (line) => {
32131
+ progress?.done();
32132
+ printInfo(` ${line}`);
32133
+ },
32134
+ ...progress ? { onProgress: progress.update } : {}
32135
+ });
32136
+ progress?.done();
32137
+ return result;
32138
+ }
31894
32139
  function registerInitCommand(program2) {
31895
32140
  program2.command("init").alias("setup").description("Set up Verity in the current project (asks the setup questions, then hands off to /verity-setup)").option("--force", "Reinstall the skills even when they are already up to date (hooks are always reconciled)").option("-y, --yes", "Take the recommended answer for every question (no prompts)").option("--no-setup", "Skip the /verity-setup handoff at the end").option(
31896
32141
  "--plugin-mode",
@@ -32001,7 +32246,7 @@ function registerInitCommand(program2) {
32001
32246
  if (defaultsOnly) {
32002
32247
  printInfo(` intensity: ${intensity} \xB7 moments: ${moments.join(", ") || "none"} (no questions asked)`);
32003
32248
  }
32004
- await scaffoldProject(step, defaultsOnly);
32249
+ await scaffoldProject(step, defaultsOnly, pluginMode);
32005
32250
  const globalVerityDir = (0, import_node_path46.join)(process.env.HOME ?? "", ".verity");
32006
32251
  await (0, import_promises20.mkdir)(globalVerityDir, { recursive: true });
32007
32252
  console.log("");
@@ -32101,7 +32346,7 @@ function registerInitCommand(program2) {
32101
32346
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
32102
32347
  init: {
32103
32348
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
32104
- cli_version: true ? "0.33.3-experimental.52f1656" : "dev"
32349
+ cli_version: true ? "0.33.3-experimental.86b5b6f" : "dev"
32105
32350
  }
32106
32351
  });
32107
32352
  } catch (err) {
@@ -33026,6 +33271,124 @@ function registerTelemetryCommands(program2) {
33026
33271
  // src/commands/prove.ts
33027
33272
  var import_node_fs66 = require("node:fs");
33028
33273
  var import_node_path49 = require("node:path");
33274
+
33275
+ // src/lib/prove/hint-pool.ts
33276
+ var STOP2 = /* @__PURE__ */ new Set([
33277
+ "about",
33278
+ "worried",
33279
+ "worry",
33280
+ "worries",
33281
+ "that",
33282
+ "this",
33283
+ "with",
33284
+ "from",
33285
+ "into",
33286
+ "code",
33287
+ "function",
33288
+ "functions",
33289
+ "functionality",
33290
+ "feature",
33291
+ "features",
33292
+ "logic",
33293
+ "module",
33294
+ "file",
33295
+ "files",
33296
+ "when",
33297
+ "what",
33298
+ "does",
33299
+ "could",
33300
+ "should",
33301
+ "would",
33302
+ "have",
33303
+ "been",
33304
+ "being",
33305
+ "will",
33306
+ "just",
33307
+ "like",
33308
+ "also",
33309
+ "very",
33310
+ "really",
33311
+ "make",
33312
+ "sure",
33313
+ "check",
33314
+ "verify",
33315
+ "prove",
33316
+ "test",
33317
+ "tests",
33318
+ "bugs",
33319
+ "issue",
33320
+ "issues",
33321
+ "race",
33322
+ "races",
33323
+ "there",
33324
+ "here",
33325
+ "some",
33326
+ "their",
33327
+ "your",
33328
+ "them",
33329
+ "they",
33330
+ "think",
33331
+ "want",
33332
+ "need",
33333
+ "please",
33334
+ "something",
33335
+ "anything",
33336
+ "part",
33337
+ "around"
33338
+ ]);
33339
+ function hintKeywords(hint) {
33340
+ const out = [];
33341
+ const seen = /* @__PURE__ */ new Set();
33342
+ for (const raw of hint.toLowerCase().match(/[a-z][a-z0-9_-]{3,}/g) ?? []) {
33343
+ const forms = [raw];
33344
+ if (raw.length > 4 && raw.endsWith("s")) forms.push(raw.slice(0, -1));
33345
+ for (const w of forms) {
33346
+ if (STOP2.has(w) || seen.has(w)) continue;
33347
+ seen.add(w);
33348
+ out.push(w);
33349
+ }
33350
+ }
33351
+ return out.slice(0, 8);
33352
+ }
33353
+ function hintMatches(root, hint, max = 30) {
33354
+ const keywords = hintKeywords(hint);
33355
+ if (keywords.length === 0) return { keywords, files: [] };
33356
+ const ls = runGitSync(root, ["ls-files", "-z", "--", "."], { timeoutMs: 3e4 });
33357
+ const tracked = ls.ok ? ls.stdout.split("\0").filter((f) => f && isCandidateSource(f)) : [];
33358
+ const files = [];
33359
+ const seen = /* @__PURE__ */ new Set();
33360
+ const add = (f) => {
33361
+ if (seen.has(f) || !isCandidateSource(f)) return;
33362
+ seen.add(f);
33363
+ files.push(f);
33364
+ };
33365
+ for (const f of tracked) if (keywords.some((k) => f.toLowerCase().includes(k))) add(f);
33366
+ for (const k of keywords) {
33367
+ if (files.length >= max) break;
33368
+ const res = runGitSync(root, ["grep", "-i", "-l", "-e", k, "--", "."], { timeoutMs: 3e4 });
33369
+ if (!res.ok) continue;
33370
+ for (const f of res.stdout.split("\n")) if (f) add(f);
33371
+ }
33372
+ return { keywords, files: files.slice(0, max) };
33373
+ }
33374
+
33375
+ // src/lib/prove/pool.ts
33376
+ async function mapPool(items, limit, fn) {
33377
+ const out = new Array(items.length);
33378
+ let next = 0;
33379
+ const worker = async () => {
33380
+ while (next < items.length) {
33381
+ const i = next++;
33382
+ out[i] = await fn(items[i]);
33383
+ }
33384
+ };
33385
+ await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
33386
+ return out;
33387
+ }
33388
+
33389
+ // src/commands/prove.ts
33390
+ var HINT_POOL_MAX = 20;
33391
+ var REPLAY_CONCURRENCY = 3;
33029
33392
  async function proveAuth(program2) {
33030
33393
  const globals = program2.opts();
33031
33394
  const token = await resolveToken(globals.token);
@@ -33043,7 +33406,19 @@ function registerProveCommand(program2) {
33043
33406
  process.exit(2);
33044
33407
  }
33045
33408
  const limit = Math.max(1, Math.min(50, Number(opts.limit) || 10));
33046
- const signals = collectRiskSignals(root, { since: opts.since, limit, paths: paths.map((p) => (0, import_node_path49.relative)(root, (0, import_node_path49.resolve)(p)) || ".") });
33409
+ let signals = collectRiskSignals(root, { since: opts.since, limit, paths: paths.map((p) => (0, import_node_path49.relative)(root, (0, import_node_path49.resolve)(p)) || ".") });
33410
+ if (opts.hint && paths.length === 0) {
33411
+ const hit = hintMatches(root, opts.hint);
33412
+ if (hit.files.length > 0) {
33413
+ const byFile = new Map(collectRiskSignals(root, { since: opts.since, limit: hit.files.length, paths: hit.files }).map((s) => [s.file, s]));
33414
+ const named = hit.files.map((f) => byFile.get(f)).filter((s) => !!s);
33415
+ const seen = new Set(named.map((s) => s.file));
33416
+ signals = [...named, ...signals.filter((s) => !seen.has(s.file))].slice(0, HINT_POOL_MAX);
33417
+ if (!opts.json) printInfo(`hint: ${hit.keywords.join(", ")} \u2192 ${Math.min(named.length, HINT_POOL_MAX)} file(s) named by path or content lead the pool of ${signals.length}`);
33418
+ } else if (!opts.json && hit.keywords.length > 0) {
33419
+ printInfo(`hint: no file names or contents match ${hit.keywords.join(", ")}; ranking the history pool`);
33420
+ }
33421
+ }
33047
33422
  const outcome = await ranker.rank(signals, { root, hint: opts.hint, auth: await proveAuth(program2) ?? void 0 });
33048
33423
  if (!outcome.ok) printWarn(`${outcome.ranker} ranker unavailable (${outcome.error}); showing the deterministic order`);
33049
33424
  if (opts.json) {
@@ -33179,12 +33554,10 @@ ${report.error ?? ""}`);
33179
33554
  return;
33180
33555
  }
33181
33556
  const modelSource = readRegularFileInRoot(root, (0, import_node_path49.relative)(root, file), 512 * 1024);
33182
- const outcomes = [];
33183
- for (const p of targets) {
33557
+ if (!opts.json) printInfo(`replaying ${targets.length} counterexample(s) of ${model} in network-less containers (${Math.min(targets.length, REPLAY_CONCURRENCY)} at a time)\u2026`);
33558
+ const outcomes = await mapPool(targets, REPLAY_CONCURRENCY, async (p) => {
33184
33559
  const brief = buildReplayBrief(root, report, p, modelSource.ok ? modelSource.content : "");
33185
- if (!opts.json) printInfo(`replaying ${model} ${p.id} (${p.trace?.length ?? 0} steps) in a network-less container\u2026`);
33186
33560
  const outcome = await runReplay(brief, { auth: auth2 ?? void 0, apiKey: process.env.OPENAI_API_KEY, model: opts.modelName, timeoutMs: Math.max(1e4, Number(opts.timeout) || 24e4) });
33187
- outcomes.push({ property: p.id, outcome });
33188
33561
  if (!opts.json) process.stdout.write(renderReplay(model, p, outcome) + "\n");
33189
33562
  try {
33190
33563
  writeReplayResult(root, report.model, p.id, outcome);
@@ -33192,7 +33565,8 @@ ${report.error ?? ""}`);
33192
33565
  if (!(err instanceof SafeWriteError)) throw err;
33193
33566
  printWarn(`${err.message} \u2014 replay result not saved`);
33194
33567
  }
33195
- }
33568
+ return { property: p.id, outcome };
33569
+ });
33196
33570
  if (opts.json) printJson({ model, replays: outcomes.map((o) => ({ property: o.property, ...toStoredReplay(o.outcome) })) });
33197
33571
  if (outcomes.some((o) => o.outcome.error)) process.exit(2);
33198
33572
  if (outcomes.some((o) => o.outcome.verdict !== "reproduced")) process.exit(1);
@@ -33228,6 +33602,74 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
33228
33602
  }
33229
33603
  process.exit(outcome.findings.some((f) => f.verdict === "reproduced") ? 1 : 0);
33230
33604
  });
33605
+ prove.command("generate <target>").description("Have the Verity service write a Lean model of one symbol (file#symbol), lint it, and check it here \u2014 up to 3 rounds").option("--lines <a-b>", "Model a region of the file (1-based, inclusive) instead of a named symbol").option("--hint <text>", "The user's worry, in their words (may become an intent property)").option("--revise <model>", "Revise this existing model after a diverged replay (with --note)").option("--note <text>", "What the replay showed was different (with --revise)").option("--json", "Machine-readable output").action(async (targetArg, opts) => {
33606
+ const root = repoRoot();
33607
+ const auth2 = await proveAuth(program2);
33608
+ if (!auth2 && !(process.env.VERITY_PROVE_DIRECT === "1" && process.env.OPENAI_API_KEY)) {
33609
+ printError("Not logged in \u2014 models are written by the Verity service. Run `verity login` first.");
33610
+ process.exit(2);
33611
+ }
33612
+ const tc = findToolchain();
33613
+ if (!tc) {
33614
+ printError(INSTALL_HINT);
33615
+ process.exit(2);
33616
+ }
33617
+ const hash = targetArg.indexOf("#");
33618
+ const file = (hash >= 0 ? targetArg.slice(0, hash) : targetArg).replace(/\\/g, "/");
33619
+ const symbol = hash >= 0 ? targetArg.slice(hash + 1) : "";
33620
+ let lines;
33621
+ if (opts.lines) {
33622
+ const m = /^(\d+)-(\d+)$/.exec(opts.lines);
33623
+ if (!m || Number(m[1]) < 1 || Number(m[2]) < Number(m[1])) {
33624
+ printError("--lines must be a-b with 1 \u2264 a \u2264 b");
33625
+ process.exit(2);
33626
+ }
33627
+ lines = { from: Number(m[1]), to: Number(m[2]) };
33628
+ }
33629
+ if (!file || !symbol && !lines) {
33630
+ printError("Target is file#symbol, or file with --lines a-b");
33631
+ process.exit(2);
33632
+ }
33633
+ let revise;
33634
+ if (opts.revise) {
33635
+ const existing = resolveModelFile(root, opts.revise);
33636
+ const src = existing ? readRegularFileInRoot(root, (0, import_node_path49.relative)(root, existing), 64 * 1024) : null;
33637
+ if (!src || !src.ok || !opts.note) {
33638
+ printError('--revise needs an existing model under Models/ and --note "<what the replay showed>"');
33639
+ process.exit(2);
33640
+ }
33641
+ revise = { source: src.content, note: opts.note };
33642
+ }
33643
+ refreshScaffold(root);
33644
+ const build = buildLibrary(tc, root);
33645
+ if (!build.ok) {
33646
+ printError("Verity library did not build");
33647
+ process.exit(2);
33648
+ }
33649
+ const deps = defaultDeps({ auth: auth2 ?? void 0, apiKey: process.env.VERITY_PROVE_DIRECT === "1" ? process.env.OPENAI_API_KEY : void 0, toolchain: tc, tier: "deep" });
33650
+ if (!opts.json) printInfo(`asking the Verity service for a model of ${symbol || `${file}:${opts.lines}`} \u2026`);
33651
+ const out = await generateAndCheck(root, { file, symbol: symbol || `${file}:${opts.lines}`, lines, hint: opts.hint, revise }, { deps, maxRounds: 3 });
33652
+ if (!out.ok) {
33653
+ if (opts.json) printJson(out);
33654
+ else printError(`${out.error} (${out.rounds} round(s)${out.costUsd ? `, $${out.costUsd.toFixed(2)}` : ""}) \u2014 write the model by hand (Models/README.md) or narrow the target`);
33655
+ process.exit(2);
33656
+ }
33657
+ try {
33658
+ writeResult(root, out.report);
33659
+ } catch (err) {
33660
+ if (!(err instanceof SafeWriteError)) throw err;
33661
+ }
33662
+ if (opts.json) {
33663
+ printJson({ name: out.name, file: out.file, rounds: out.rounds, cost_usd: out.costUsd, report: { ...out.report, passes: reportPasses(out.report), summary: summarize(out.report) } });
33664
+ return;
33665
+ }
33666
+ printInfo(`wrote ${out.file} (${out.rounds} round(s)${out.costUsd ? `, $${out.costUsd.toFixed(2)}` : ""})`);
33667
+ process.stdout.write(renderReport(out.report) + "\n");
33668
+ const s = summarize(out.report);
33669
+ if (s.counterexamples > 0) process.stdout.write(`${DIM}next: verity prove replay ${out.name}${NC}
33670
+ `);
33671
+ process.exit(0);
33672
+ });
33231
33673
  prove.command("waive <file>").description("Dismiss the remembered formal finding(s) on a file for its current bytes \u2014 the accepted-risk path for a formal block").requiredOption("--reason <text>", "The human disposition this records (reviewer finding, ADR, \u2026)").action((file, opts) => {
33232
33674
  const root = repoRoot();
33233
33675
  const rel0 = (0, import_node_path49.relative)(root, (0, import_node_path49.isAbsolute)(file) ? file : (0, import_node_path49.join)(root, file)).replace(/\\/g, "/");
@@ -33338,7 +33780,15 @@ function registerLeanCommand(program2) {
33338
33780
  return;
33339
33781
  }
33340
33782
  }
33341
- const result = await installLean({ onStatus: (line) => printInfo(line) });
33783
+ const progress = process.stderr.isTTY ? inPlaceProgress(`downloading ${asset}`) : null;
33784
+ const result = await installLean({
33785
+ onStatus: (line) => {
33786
+ progress?.done();
33787
+ printInfo(line);
33788
+ },
33789
+ ...progress ? { onProgress: progress.update } : {}
33790
+ });
33791
+ progress?.done();
33342
33792
  if (!result.ok) {
33343
33793
  printError(result.error);
33344
33794
  process.exit(2);
@@ -33358,8 +33808,8 @@ function registerLeanCommand(program2) {
33358
33808
  }
33359
33809
 
33360
33810
  // src/cli.ts
33361
- program.name("verity").description("CLI for Verity quality gate service").version("0.33.3-experimental.52f1656").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) => {
33362
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.3-experimental.52f1656");
33811
+ program.name("verity").description("CLI for Verity quality gate service").version("0.33.3-experimental.86b5b6f").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) => {
33812
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.3-experimental.86b5b6f");
33363
33813
  setUserNamedServiceUrl(program.opts().serviceUrl);
33364
33814
  try {
33365
33815
  await foldLegacyLocalCredential();
@@ -1,174 +1,135 @@
1
1
  ---
2
2
  description: >-
3
- Formally verify a small, risky piece of code with a Lean 4 model: pick candidates from git
4
- history, CONFIRM with the user which code to model, write the model, run it locally with
5
- `verity prove check`, and replay any counterexample on the real code before calling it a bug.
6
- Use when the user asks to verify, prove, or model-check code, or asks "can this race / double
7
- run / fail open?". Anything typed after /verity-prove is the user's hint: the file, function,
8
- or worry to check.
3
+ Formally verify a small, risky piece of code with a Lean 4 model written by the
4
+ Verity service: rank candidates from git history and the user's hint, CONFIRM with
5
+ the user which code to model, have the service generate the model, check it locally
6
+ with `verity prove check`, and replay any counterexample on the real code before
7
+ calling it a bug. Use when the user asks to verify, prove, or model-check code, or
8
+ asks "can this race / double run / fail open?". Anything typed after /verity-prove
9
+ is the user's hint: the file, function, or worry to check.
9
10
  ---
10
11
  # /verity-prove — Formal verification of small pieces of code
11
12
 
12
13
  **The words after `/verity-prove` are the hint.** `/verity-prove` alone means "find me
13
14
  something worth checking"; `/verity-prove can the Stripe webhook double-charge on a
14
- redelivered event?` means that is the target and, if it is a worry, the property.
15
- Carry it through every step below as `$ARGUMENTS`.
15
+ redelivered event?` names the target and the worry. Carry it through every step as
16
+ `$ARGUMENTS`.
16
17
 
17
- Verity keeps **Lean 4 models of the riskiest code**, not of the codebase. A model is
18
- another tool for finding bugs: a few dozen lines that abstract ONE function and check
19
- a few properties by exhaustive bounded search. When a property fails you get the
20
- **shortest sequence of actions** that breaks it — a hypothesis to replay on the real
21
- code. When it holds you know exactly how far it was checked.
18
+ Verity keeps **Lean 4 models of the riskiest code**, not of the codebase: a few dozen
19
+ lines that abstract ONE function and check a few properties by exhaustive bounded
20
+ search. When a property fails you get the **shortest sequence of actions** that breaks
21
+ it — a hypothesis to replay on the real code.
22
22
 
23
- This skill is the **explicit, user-driven** entry; nothing in it blocks a commit or a
24
- push. (The push-time phase — `verity config formal push` — is a separate, opt-in path
25
- that uses the same tools.) Lean runs on this machine because the user asked for it.
23
+ **The service does the work; you orchestrate and present.** Ranking, writing the model
24
+ and replaying its counterexample all run on the Verity service through the CLI. You
25
+ do not search the codebase by hand, read the Lean library, or write Lean yourself
26
+ unless a step below says so. Each step is one command; read its output, decide, move on.
27
+ Lean runs on this machine only because the user asked for it; nothing here blocks a
28
+ commit or a push.
26
29
 
27
30
  ## The flow
28
31
 
29
- ### 1. Find what deserves a model
32
+ ### 1. Candidates — one command
30
33
 
31
34
  ```bash
32
- verity prove candidates --limit 8 # git history + risk signals
33
- verity prove candidates --rank luna --json # + a cheap model re-ranks and names a symbol
34
- verity prove candidates src/payments # scoped to a path
35
+ verity prove candidates --hint "$ARGUMENTS" --json # hint given
36
+ verity prove candidates --limit 8 --json # no hint
35
37
  ```
36
38
 
37
- Each candidate says why: fix commits over commits, check-then-act, retries, caches,
38
- locks/leases, status transitions, money.
39
+ The hint **widens the pool** (files it names by path or content come first) and the
40
+ service ranks it, naming a symbol and the properties worth checking per file. Do not
41
+ search for the code yourself: if the ranker's list does not contain what the user
42
+ meant, say so and ask them for the file or function. A named file or function in the
43
+ hint skips ranking: go straight to step 2 with it.
39
44
 
40
- **With a hint** (`$ARGUMENTS` non-empty) — a file, a function, or a worry in the user's
41
- own words ("can the webhook double-charge?", "does the retry ever lose the feedback?"):
45
+ ### 2. Confirm with the user — always
42
46
 
43
- ```bash
44
- verity prove candidates --hint "$ARGUMENTS"
45
- ```
47
+ **Do not generate a model until the user has said which code.** Present at most three
48
+ candidates in one short message: file, the symbol the ranker named, the first property
49
+ it suggested, the reason. Ask. Their answer is the scope; do not widen it. If the hint
50
+ is a worry in the user's words, it becomes an `intent` property — that needs no further
51
+ confirmation, they just gave it.
46
52
 
47
- A named file or function skips the ranking entirely: go to step 2 with it. A *worry*
48
- becomes an `intent` property in step 4, with `source := "user, <date>: <their words>"`;
49
- it needs no further confirmation, they just gave it. The candidates list is still shown,
50
- so the user sees what else the history says — but the hint decides.
53
+ A hint that names the code, or a candidate the user saw in an earlier session, is
54
+ **not** a confirmation of the exact symbol and property. Still ask, in one line:
55
+ "I'll model `<file>#<symbol>` for `<property>` — go ahead?" It costs the user one word.
51
56
 
52
- ### 2. Confirm with the user — always
57
+ ### 3. Set up once per repo
53
58
 
54
- **Do not write a model until the user has said which code.** Present at most three
55
- candidates in one short message, each as: file, the function you would model, the
56
- property you would check first, and the reason from the signals. Then ask. The user may
57
- pick one, name something else, or narrow the property. Their answer is the scope; do not
58
- widen it.
59
+ ```bash
60
+ verity prove init
61
+ ```
59
62
 
60
- A model is a claim about what the code *means*. Only the person who owns the code can
61
- say whether that claim is worth checking.
63
+ If Lean is missing, say what will be installed and where before running
64
+ `verity lean install` (official release, ≈800 MB download, 2.7 GB on disk, into
65
+ `~/.verity/lean`). Everything else needs only `verity login`.
62
66
 
63
- ### 3. Set up the project (once per repo)
67
+ ### 4. Generate the model — one command
64
68
 
65
69
  ```bash
66
- verity prove init
70
+ verity prove generate src/pay.ts#charge --hint "$ARGUMENTS"
71
+ verity prove generate src/handler.ts --lines 120-180 --hint "$ARGUMENTS" # a region
67
72
  ```
68
73
 
69
- Creates `.verity/proofs/` — a Lake project with Verity's library (`Verity.System`,
70
- `Verity.check`, a bounded explorer). Re-running refreshes the library and never touches
71
- `Models/`. If Lean is missing, install it only after telling the user what will be
72
- installed and where: `verity lean install` (official release, ≈800 MB download, 2.7 GB
73
- on disk, into `~/.verity/lean`). Ranking (`--hint`, `--rank luna`) and `verity prove
74
- replay` run on the Verity service with the user's login (`verity login`); without a
75
- login, ranking is deterministic and replay is by hand (step 6.2).
76
-
77
- ### 4. Write the model — `Models/<Name>.lean`
78
-
79
- Rules that keep models small and honest:
80
-
81
- - **One symbol.** The header names it: `Verity.model { name, abstracts := [{ file, symbol, sha256 }] }`.
82
- Get the hash with `verity prove hash <file>`. `verity prove check` reports drift when the file changes.
83
- When the code is a *region* rather than a named function (a branch inside a large
84
- handler), add `lines := "a-b"` (1-based, inclusive) — `verity prove replay` needs it
85
- to know which real lines must run.
86
- - **State = only what the property reads.** Actions = the operations that can interleave
87
- (a retry, a second call, a concurrent writer). Steps = what the code does, not what it should.
88
- - **Properties are tagged.** `kind := .structural` for the catalogue (idempotence, no lost
89
- update, status only moves forward, retries terminate, never fails open, bounded size,
90
- check-then-act is atomic, no double effect). `kind := .intent` **only** when a human
91
- wrote the expectation down — a doc comment, an ADR, a ticket, a Standard rule — and
92
- `source :=` cites it. Never invent intent.
93
- - **End with the checks.** `#eval Verity.model …` then one `#eval Verity.check …` per
94
- property (bounded exploration), `Verity.checkData` for input properties, `Verity.proved`
95
- only beside a real theorem. `sorry` fails the run.
96
- - Keep it under ~120 lines. If it wants more, the scope is wrong — go back to step 2.
97
-
98
- `Models/README.md` has the skeleton; `cli/tests/fixtures/proofs/SeedRunner.lean` in
99
- Verity's own repo is a worked example (the VRT-243 seed-duplication bug).
100
-
101
- ### 5. Check it
74
+ The service writes the model, the linter fences it, this machine elaborates it, with
75
+ up to **3** rounds on compiler errors — all inside the command. It prints the check:
76
+ per property `✓ holds to bound k`, `✗ COUNTEREXAMPLE` with the trace, or `? inconclusive`.
77
+ Only if it ends with "no model elaborated" do you write one by hand from
78
+ `.verity/proofs/Models/README.md` (one symbol, ≤ ~120 lines, `#eval Verity.model …`
79
+ header, one `#eval Verity.check …` per property, tagged `.structural` or `.intent` with
80
+ `source :=`); check it with `verity prove check <Name>`.
81
+
82
+ ### 5. Replay before you call it a bug — one command
102
83
 
103
84
  ```bash
104
- verity prove check <Name> # one model
105
- verity prove check # every model
106
- verity prove check <Name> --json # for scripts
85
+ verity prove replay <Name> # every counterexample
86
+ verity prove replay <Name> --property P2 # one
107
87
  ```
108
88
 
109
- Per property: `✓ holds to bound k`, `✓ proved`, `✗ COUNTEREXAMPLE` with the trace and
110
- the state after each step, or `? inconclusive` (state cap).
111
-
112
- **Hard caps — never loop:**
113
- - elaboration errors: at most **3** fix rounds, then stop and say the model could not be
114
- built;
115
- - a counterexample that **diverges** on replay (the model was wrong about the code): at
116
- most **2** model revisions, each naming what the replay showed was different; after
117
- that, stop and report "no faithful model found for `<symbol>`" — that is a valid result;
118
- - never weaken or delete a property to make it pass; never add a property to make
119
- something fail.
120
-
121
- A wrong model is expected some of the time. It costs a replay and is discarded; it never
122
- costs a false bug, because only a *reproduced* counterexample is reported (step 6).
123
-
124
- ### 6. Replay before you call it a bug
125
-
126
- A counterexample is a fact about the **model**. Before reporting a bug:
127
-
128
- 1. Run the replay in OpenAI's disposable container (network off, deleted afterwards):
129
-
130
- ```bash
131
- verity prove replay <Name> # every counterexample
132
- verity prove replay <Name> --property P2 # one
133
- ```
134
-
135
- It hands a model the trace, the Lean file and the real source — the abstracted
136
- file's content leaves the machine for the Verity service and OpenAI's container;
137
- only git-tracked files are ever read — and requires three things before accepting
138
- its answer: code actually ran (a shell call with output), the real function's lines
139
- appear verbatim in what it wrote, and the harness itself printed
140
- `REPLAY_RESULT=reproduced`. `REPRODUCED` → the
141
- real code does this. `DIVERGED` → the model is wrong about the code. `UNVERIFIED` →
142
- not evidence either way. `UNREPLAYABLE` → it could not run the function faithfully.
143
- Read its reason: the stubs it chose are where a false reproduction hides.
144
- 2. When the replay is unverified or unreplayable — or the code needs a runtime the
145
- container lacks (Deno edge functions, a real database) — replay by hand: read the
146
- real function again, map every trace action to a real call, and reproduce it with a
147
- throwaway harness in a scratch directory that loads the real module and stubs only
148
- I/O boundaries. **Never edit the user's source files to do this.**
149
- 3. Reproduced → report it as a bug, with the trace and the reproduction. Not reproduced
150
- → the model is wrong (`model-divergence`): say so, fix the model, do not report a bug.
151
-
152
- This is the rule Verity applies everywhere (memory: *replay formal counterexamples
153
- against changed code before blocking*). A model that finds a bug in itself is noise.
154
-
155
- ### 7. Report, in a few lines
156
-
157
- State what was modelled, what was checked to what bound, what was refuted and whether
158
- it replayed, and what remains `intent` awaiting the user's confirmation. **Never offer
159
- to commit the model** — `.verity/proofs/` is machine-local and ignores itself; models
160
- would only confuse reviewers. What reaches the team is the bug report and, when asked,
161
- the fix. Then record the outcome:
89
+ The service replays the trace on the real code in a network-less container and accepts
90
+ an answer only when code ran, the real function's lines appear verbatim in the harness,
91
+ and the harness itself printed `REPLAY_RESULT=reproduced`. A model's counterexamples
92
+ replay in parallel, so the command takes about as long as one replay (~30 s). **Run it
93
+ in the foreground and wait for it**: do not background it, do other work, or report
94
+ before every verdict is in. Read the verdict:
95
+
96
+ - **REPRODUCED** — the real code does this. **This is final: do not replay by hand
97
+ as well.** Report it as a bug (step 6).
98
+ - **DIVERGED** — the model is wrong about the code. Revise it **at most twice**, each
99
+ time saying what the replay showed:
100
+ `verity prove generate <target> --revise <Name> --note "<what the replay showed>"`,
101
+ then replay again. After two divergences, report "no faithful model found for
102
+ `<symbol>`" — that is a valid result, not a bug.
103
+ - **UNVERIFIED / UNREPLAYABLE** — not evidence either way. Only now replay by hand:
104
+ a throwaway harness in a scratch directory that loads the real module and stubs only
105
+ I/O boundaries **in memory**. Never edit the user's source files, and never read from
106
+ or write to the user's databases, services or local stacks to do this — ask first if
107
+ a stub cannot be faithful.
108
+
109
+ Never weaken or delete a property to get green; never add one to make something fail.
110
+
111
+ ### 6. Report, in a few lines
112
+
113
+ What was modelled, what was checked to what bound, what was refuted and whether it
114
+ replayed, and what remains `intent` awaiting the user's confirmation. **Never offer to
115
+ commit the model** — `.verity/proofs/` is machine-local and ignores itself. What reaches
116
+ the team is the bug report and, when asked, the fix. Then record the outcome:
162
117
 
163
118
  ```bash
164
119
  verity reflect --user-input "Modelled runSeed (cli/src/lib/seed-runner.ts): P1 at-most-once refuted in 2 steps, replayed — guard checks a path the server never writes." --kind gotcha
165
120
  ```
166
121
 
122
+ The abstracted file's content leaves the machine for the Verity service and its
123
+ replay container; only git-tracked files are ever read.
124
+
167
125
  ## Do not
168
126
 
127
+ - Search the codebase yourself before the ranker has answered, or instead of asking the user.
169
128
  - Model code the user did not confirm.
170
- - Present a model-only counterexample as a bug.
171
- - Put `sorry` in a model, or weaken a property to get green.
172
- - Touch source files, run Lean from a hook, or install anything silently.
129
+ - Write Lean by hand while `verity prove generate` can still try.
130
+ - Replay by hand after a REPRODUCED verdict, or present a model-only counterexample as a bug.
131
+ - Background `verity prove replay`, or report before its verdicts are in.
132
+ - Write to the user's databases or local services, even throwaway rows.
133
+ - Put `sorry` in a model, weaken a property to get green, or touch source files.
134
+ - Run Lean from a hook, or install anything silently.
173
135
  - Commit, stage, or un-ignore anything under `.verity/proofs/`.
174
- - Model a whole module. If the state has more than ~6 fields, narrow it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.33.3-experimental.52f1656",
3
+ "version": "0.33.3-experimental.86b5b6f",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",