@codacy/verity-cli 0.33.3-experimental.911f315 → 0.33.3-experimental.9d77663
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 +141 -44
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -27021,7 +27021,7 @@ function channelSilence(input) {
|
|
|
27021
27021
|
// src/lib/cli-version.ts
|
|
27022
27022
|
function cliVersion() {
|
|
27023
27023
|
try {
|
|
27024
|
-
return true ? "0.33.3-experimental.
|
|
27024
|
+
return true ? "0.33.3-experimental.9d77663" : "dev";
|
|
27025
27025
|
} catch {
|
|
27026
27026
|
return "dev";
|
|
27027
27027
|
}
|
|
@@ -27856,11 +27856,11 @@ var rootCandidateCache = /* @__PURE__ */ new Map();
|
|
|
27856
27856
|
function candidateRoots(repoRoot2) {
|
|
27857
27857
|
const cached2 = rootCandidateCache.get(repoRoot2);
|
|
27858
27858
|
if (cached2) return cached2;
|
|
27859
|
-
const
|
|
27860
|
-
const out2 = [
|
|
27859
|
+
const norm2 = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
27860
|
+
const out2 = [norm2];
|
|
27861
27861
|
try {
|
|
27862
|
-
const real = import_node_fs42.realpathSync.native(
|
|
27863
|
-
if (real !==
|
|
27862
|
+
const real = import_node_fs42.realpathSync.native(norm2).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
27863
|
+
if (real !== norm2) out2.push(real);
|
|
27864
27864
|
} catch {
|
|
27865
27865
|
}
|
|
27866
27866
|
for (const r of [...out2]) {
|
|
@@ -33555,6 +33555,45 @@ function collectRiskSignals(root, opts = {}) {
|
|
|
33555
33555
|
return out2.slice(0, opts.limit ?? 10);
|
|
33556
33556
|
}
|
|
33557
33557
|
|
|
33558
|
+
// src/lib/prove/grounding.ts
|
|
33559
|
+
var BASIS_RULES = ["conservation", "once", "no-lost-update", "fail-closed", "progress"];
|
|
33560
|
+
var RULE = /^rule:([a-z-]+)$/;
|
|
33561
|
+
var LINE = /^(?:([^\s:]+):)?L?(\d{1,6})(?::\s*|\s+)(?:comment\s+)?([\s\S]+)$/;
|
|
33562
|
+
var norm = (s) => s.replace(/\s+/g, " ").trim().toLowerCase();
|
|
33563
|
+
function basisOf(source2, files) {
|
|
33564
|
+
const s = source2.trim();
|
|
33565
|
+
const r = RULE.exec(s);
|
|
33566
|
+
if (r) return BASIS_RULES.includes(r[1]) ? { ok: true, basis: s } : { ok: false, why: `"rule:${r[1]}" is not one of the rules` };
|
|
33567
|
+
const m = LINE.exec(s);
|
|
33568
|
+
if (!m) return { ok: false, why: s ? `"${s.slice(0, 60)}" is neither L<n>: <words> nor a rule` : "no source" };
|
|
33569
|
+
const path = m[1];
|
|
33570
|
+
const n = Number(m[2]);
|
|
33571
|
+
const quoted = m[3].trim().replace(/^["'`“”]+|["'`“”]+$/g, "");
|
|
33572
|
+
const fragments = quoted.split(/…|\.\.\./).map(norm).filter((f) => f.length >= 4);
|
|
33573
|
+
if (fragments.length === 0) return { ok: false, why: `L${n}: too few words to find on the line` };
|
|
33574
|
+
const pool = path ? [...files].filter(([f]) => f === path || f.endsWith(`/${path}`)).map(([, c]) => c) : [...files.values()];
|
|
33575
|
+
if (path && pool.length === 0) return { ok: false, why: `${path} is not one of the given files` };
|
|
33576
|
+
for (const content of pool) {
|
|
33577
|
+
const line2 = content.split("\n")[n - 1];
|
|
33578
|
+
if (line2 !== void 0 && fragments.every((f) => norm(line2).includes(f))) return { ok: true, basis: `L${n}: ${quoted.slice(0, 120)}` };
|
|
33579
|
+
}
|
|
33580
|
+
return { ok: false, why: `line ${n} does not contain "${quoted.slice(0, 60)}"` };
|
|
33581
|
+
}
|
|
33582
|
+
var NEEDS_FAILURE = /(?<!\beven )\b(?:after|when|if|once|on)\b[^,.;:]{0,60}?\b(?:fails?|failed|errors?|throws?|times? out|timed out|rejects?|rejected|is unavailable|is down)\b|\bon (?:an? )?(?:error|failure|timeout)\b|\ba failed\b/;
|
|
33583
|
+
var NEEDS_CRASH = /(?<!\beven )\b(?:after|when|if|on)\b[^,.;:]{0,60}?\bcrash/;
|
|
33584
|
+
var HAS_FAILURE = /fail|error|throw|timeout|timedout|timesout|reject|exception|unavailable|down/i;
|
|
33585
|
+
var HAS_CRASH = /crash|die|kill|abort|restart/i;
|
|
33586
|
+
function missingTrigger(desc, trace) {
|
|
33587
|
+
const d = desc.toLowerCase();
|
|
33588
|
+
const t = trace.join(" ");
|
|
33589
|
+
if (NEEDS_CRASH.test(d) && !HAS_CRASH.test(t)) return "a crash";
|
|
33590
|
+
if (NEEDS_FAILURE.test(d) && !HAS_FAILURE.test(t)) return "a failure";
|
|
33591
|
+
return null;
|
|
33592
|
+
}
|
|
33593
|
+
function ungroundedMessage(list2) {
|
|
33594
|
+
return `These properties do not say what they rest on \u2014 ${list2}. Set each property's \`source\` to "L<n>: <words copied from that line>" (a line of the numbered source that states the promise) or one rule: ${BASIS_RULES.map((r) => `rule:${r}`).join(", ")}. Drop a property nothing supports; keep the model otherwise the same.`;
|
|
33595
|
+
}
|
|
33596
|
+
|
|
33558
33597
|
// src/lib/prove/ranker.ts
|
|
33559
33598
|
var STRUCTURAL_CATALOGUE = [
|
|
33560
33599
|
"idempotence",
|
|
@@ -34121,6 +34160,8 @@ var MAX_CONTEXT_EFFECTS = 20;
|
|
|
34121
34160
|
var MAX_CO_ACCESSORS = 2;
|
|
34122
34161
|
var MAX_HELPERS2 = 3;
|
|
34123
34162
|
var MAX_CONTEXT_LINES = 120;
|
|
34163
|
+
var MAX_CALLERS2 = 3;
|
|
34164
|
+
var MAX_CALLER_LINES = 9;
|
|
34124
34165
|
var MAX_HELPER_LINES = 80;
|
|
34125
34166
|
var MAX_CONTEXT_BYTES = 48 * 1024;
|
|
34126
34167
|
var LIBRARY_API = `The Verity Lean 4 library (already built; \`import Verity\` is the ONLY import allowed):
|
|
@@ -34180,7 +34221,7 @@ def model : Model :=
|
|
|
34180
34221
|
{ name := "SeedRunner"
|
|
34181
34222
|
abstracts := [ { file := "cli/src/lib/seed-runner.ts", symbol := "runSeed", sha256 := "<sha256>" } ] }
|
|
34182
34223
|
|
|
34183
|
-
def P1 : Property := { id := "P1", kind := .structural, desc := "seeding runs at most once: posts never exceed 1" }
|
|
34224
|
+
def P1 : Property := { id := "P1", kind := .structural, desc := "seeding runs at most once: posts never exceed 1", source := "rule:once" }
|
|
34184
34225
|
|
|
34185
34226
|
#eval Verity.model model
|
|
34186
34227
|
#eval Verity.check model P1 sys (fun s => s.posts \u2264 1) { bound := 4 }
|
|
@@ -34240,8 +34281,8 @@ def model : Model :=
|
|
|
34240
34281
|
{ name := "Withdraw"
|
|
34241
34282
|
abstracts := [ { file := "src/accounts/withdraw.ts", symbol := "withdraw", sha256 := "<sha256>", lines := "5-12" } ] }
|
|
34242
34283
|
|
|
34243
|
-
def P1 : Property := { id := "no_overdraft_under_concurrent_withdrawals", kind := .structural, desc := "two concurrent withdrawals never pay out more than the balance held: the balance check and the debit act as one step" }
|
|
34244
|
-
def P2 : Property := { id := "balance_matches_payouts", kind := .structural, desc := "once both calls are done, the balance plus everything paid out equals the starting balance (no lost update)" }
|
|
34284
|
+
def P1 : Property := { id := "no_overdraft_under_concurrent_withdrawals", kind := .structural, desc := "two concurrent withdrawals never pay out more than the balance held: the balance check and the debit act as one step", source := "rule:conservation" }
|
|
34285
|
+
def P2 : Property := { id := "balance_matches_payouts", kind := .structural, desc := "once both calls are done, the balance plus everything paid out equals the starting balance (no lost update)", source := "rule:no-lost-update" }
|
|
34245
34286
|
|
|
34246
34287
|
#eval Verity.model model
|
|
34247
34288
|
#eval Verity.check model P1 sys (fun s => s.paidOut \u2264 10) { bound := 6 }
|
|
@@ -34264,7 +34305,7 @@ def model : Model :=
|
|
|
34264
34305
|
{ name := "Split"
|
|
34265
34306
|
abstracts := [ { file := "src/payouts/split.ts", symbol := "splitPayout", sha256 := "<sha256>", lines := "4-8" } ] }
|
|
34266
34307
|
|
|
34267
|
-
def P1 : Property := { id := "parts_sum_to_total", kind := .structural, desc := "the parts of a payout always add up to the total paid out" }
|
|
34308
|
+
def P1 : Property := { id := "parts_sum_to_total", kind := .structural, desc := "the parts of a payout always add up to the total paid out", source := "rule:conservation" }
|
|
34268
34309
|
|
|
34269
34310
|
#eval Verity.model model
|
|
34270
34311
|
#eval Verity.checkData model P1 [(100, [1, 1, 1]), (2, [1, 1, 1]), (10, [1, 2]), (7, [3, 4])] (fun (x : Nat \xD7 List Nat) => (split x.1 x.2).foldl (fun a b => a + b) 0 == x.1)
|
|
@@ -34295,6 +34336,8 @@ STEP 3 \u2014 Properties: 2 to 4, in this order of priority:
|
|
|
34295
34336
|
A counterexample must run the real function: a trace made only of environment moves (another writer, a clock) says nothing about this code. A promise about the END result (nothing lost, nothing charged twice, totals add up) is checked on states where every invocation has finished or crashed \u2014 guard the \`inv\` with the program counters, as exemplar 2's second property does; a violation that exists only while work is still pending is not a bug.
|
|
34296
34337
|
A property is about an OUTCOME someone would call a bug \u2014 money, stock or records wrong, access granted, work lost or done twice, a job stuck forever \u2014 never about the mechanism's internals (a key's format, two timestamps being distinct, a lease's exact boundary tick). A comment that says HOW the code works ("make each attempt's key unique") is not the promise; the promise is what that mechanism is for.
|
|
34297
34338
|
A comment that states a SAFE design choice ("a spent token presented again ends every session", "consumers dedupe, so publishing twice is harmless") is the design: never claim the opposite. A comment never makes an UNSAFE outcome safe: granting access on an error, losing or double-counting money, stock or records is a bug whatever the comment says ("don't block people when the permission service is flaky" describes a fail-open bug).
|
|
34339
|
+
Every property says what it rests on, in \`source\`: either \`L<n>: <words copied from that line>\` \u2014 a line of the numbered source above (a comment, docstring, check, error message or name) that states the promise \u2014 or one rule: \`rule:conservation\` (money, stock or counts add up), \`rule:once\` (no double effect, only where the code dedups, as above), \`rule:no-lost-update\`, \`rule:fail-closed\` (an error never grants access or reports success), \`rule:progress\` (nothing stuck forever). A property that neither a line nor one of these rules supports is not a promise: do not state it. A property whose \`source\` is empty, or cites words its line does not contain, sends the model back.
|
|
34340
|
+
When the input lists where the function is called, a caller's guard bounds its inputs: never build a counterexample from an input no caller can pass (an empty list a caller refuses).
|
|
34298
34341
|
Property ids name the promise in snake_case (refund_total_within_capture) \u2014 never P1. \`desc\` states the promise in the code's own terms and names the move the trace needs ("two concurrent calls \u2026", "after the charge succeeded and the call timed out \u2026").
|
|
34299
34342
|
A PURE computation (no effect sites) is checked with \`Verity.checkData\` on small concrete inputs, including the edge cases (exemplar 3).
|
|
34300
34343
|
|
|
@@ -34434,6 +34477,10 @@ ${t.effects.slice(0, MAX_CONTEXT_EFFECTS).map((e) => ` ${e}`).join("\n")}`);
|
|
|
34434
34477
|
parts2.push("", "# Helpers it calls (their effects happen at the line that calls them)");
|
|
34435
34478
|
for (const h of t.helpers.slice(0, MAX_HELPERS2)) parts2.push("", `## ${h.file} \u2014 \`${h.symbol}\` (lines ${h.lines.from}\u2013${h.lines.to})`, "```", h.numbered, "```");
|
|
34436
34479
|
}
|
|
34480
|
+
if (t.callers?.length) {
|
|
34481
|
+
parts2.push("", "# Where it is called (a caller's guard bounds the inputs it can get)");
|
|
34482
|
+
for (const c of t.callers.slice(0, MAX_CALLERS2)) parts2.push("", `## ${c.file}:${c.line}${c.caller ? ` \u2014 in \`${c.caller}\`` : ""}`, "```", c.numbered, "```");
|
|
34483
|
+
}
|
|
34437
34484
|
if (previous) {
|
|
34438
34485
|
parts2.push("", "# Your previous attempt was not accepted \u2014 the diagnostics say why. Fix it (same model, same properties):", "```lean", previous.source.slice(0, 32 * 1024), "```", "Diagnostics:", "```", previous.error.slice(0, 4e3), "```");
|
|
34439
34486
|
}
|
|
@@ -34668,11 +34715,10 @@ Rules:
|
|
|
34668
34715
|
- A deadlock or a hang: run under a timeout (asyncio.wait_for, Promise.race with a timer) and treat the timeout as the observation.
|
|
34669
34716
|
The harness must CALL the real function: a trace replayed on the stubs alone is not a reproduction \u2014 answer unreplayable.
|
|
34670
34717
|
4. COMPUTE the verdict in the harness: print the observable state after each action; let every timer, promise and retry the trace did not crash or cancel run to completion; then evaluate the property on the final real state AS ITS DESCRIPTION STATES \u2014 the harm it names, including what must happen first ("after X fails", "after a crash", "two concurrent calls"): a run that never does that thing has not violated it. Drive the code only with inputs its own callers in the source can pass. Print exactly one line, REPLAY_RESULT=reproduced when the property is violated or REPLAY_RESULT=diverged when it holds \u2014 chosen by an if/else or a conditional expression in the harness, never a fixed string.
|
|
34671
|
-
5.
|
|
34672
|
-
6.
|
|
34673
|
-
7.
|
|
34674
|
-
8.
|
|
34675
|
-
9. 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).
|
|
34718
|
+
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.
|
|
34719
|
+
6. Nothing you run can reach the network. Do not try.
|
|
34720
|
+
7. Keep it under about eight shell calls. Output tokens are time.
|
|
34721
|
+
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).
|
|
34676
34722
|
|
|
34677
34723
|
"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.`;
|
|
34678
34724
|
var REPLAY_SCHEMA = {
|
|
@@ -34775,15 +34821,6 @@ function printedResult(outputTexts) {
|
|
|
34775
34821
|
return last;
|
|
34776
34822
|
}
|
|
34777
34823
|
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";
|
|
34778
|
-
var CONTROL_LINE2 = /REPLAY_CONTROL=(holds|violated|none)\b/g;
|
|
34779
|
-
function printedControl(outputTexts) {
|
|
34780
|
-
let last = null;
|
|
34781
|
-
for (const t of outputTexts) for (const m of t.matchAll(CONTROL_LINE2)) last = m[1];
|
|
34782
|
-
return last;
|
|
34783
|
-
}
|
|
34784
|
-
var CONTROL_MISSING = "the harness never printed REPLAY_CONTROL \u2014 a reproduction counts only beside a control run of the same calls without the hazard";
|
|
34785
|
-
var FIXED_CONTROL = 'the harness printed a fixed control line \u2014 it never contains the "violated" outcome, so the control was not computed from a run';
|
|
34786
|
-
var CONTROL_VIOLATED = "the property fails in the control run too (the same calls without the trace's hazard): the violation is ordinary behaviour, not the bug the trace claims \u2014 the model misreads the code";
|
|
34787
34824
|
var FIXED_VERDICT = 'the harness printed a fixed verdict \u2014 it never contains the "diverged" outcome, so the result line was not computed from the real run';
|
|
34788
34825
|
var CONTAINER_RETRY_BACKOFF_MS = [1e3, 3e3];
|
|
34789
34826
|
function judgeReplay(brief, parsed) {
|
|
@@ -34827,14 +34864,6 @@ function judgeReplay(brief, parsed) {
|
|
|
34827
34864
|
if (claimed === "reproduced" && primary && harnessInvokes(texts, primary.symbol, /\.py$/.test(primary.file), copied) === false) {
|
|
34828
34865
|
return { claimed, verdict: "unverified", reason: parsed.claim.reason, downgrade: NOT_INVOKED, verbatim };
|
|
34829
34866
|
}
|
|
34830
|
-
if (claimed === "reproduced") {
|
|
34831
|
-
const control = printedControl(parsed.outputTexts);
|
|
34832
|
-
if (control === null) return { claimed, verdict: "unverified", reason: parsed.claim.reason, downgrade: CONTROL_MISSING, verbatim };
|
|
34833
|
-
if (control === "violated") return { claimed, verdict: "diverged", reason: parsed.claim.reason, downgrade: CONTROL_VIOLATED, verbatim };
|
|
34834
|
-
if (control === "holds" && !texts.some((t) => t.split("\n").some((l) => !copied.has(l.trim()) && l.includes("violated")))) {
|
|
34835
|
-
return { claimed, verdict: "unverified", reason: parsed.claim.reason, downgrade: FIXED_CONTROL, verbatim };
|
|
34836
|
-
}
|
|
34837
|
-
}
|
|
34838
34867
|
return { claimed, verdict: claimed, reason: parsed.claim.reason, downgrade: null, verbatim };
|
|
34839
34868
|
}
|
|
34840
34869
|
async function runReplayInContainer(brief, opts) {
|
|
@@ -34954,8 +34983,7 @@ async function runReplayInContainer(brief, opts) {
|
|
|
34954
34983
|
const v = judged2.verbatim;
|
|
34955
34984
|
const fixedVerdict = judged2.downgrade === FIXED_VERDICT;
|
|
34956
34985
|
const notInvoked = judged2.downgrade === NOT_INVOKED;
|
|
34957
|
-
const
|
|
34958
|
-
const resultMissing = judged2.downgrade === RESULT_MISSING || fixedVerdict || notInvoked || controlMissing;
|
|
34986
|
+
const resultMissing = judged2.downgrade === RESULT_MISSING || fixedVerdict || notInvoked;
|
|
34959
34987
|
if (!resultMissing && (v.total === 0 || v.ratio >= (v.basis === "symbol" ? VERBATIM_THRESHOLD_SYMBOL : VERBATIM_THRESHOLD_FILE))) break;
|
|
34960
34988
|
const region = brief.sources[0];
|
|
34961
34989
|
const what = region ? `\`${region.symbol}\`${region.lines ? ` (lines ${region.lines.from}\u2013${region.lines.to})` : ""} of ${region.file}` : "the function under test";
|
|
@@ -34963,8 +34991,6 @@ async function runReplayInContainer(brief, opts) {
|
|
|
34963
34991
|
|
|
34964
34992
|
` + renderBriefInput(brief) : fixedVerdict ? `Your previous attempt in this same container was NOT accepted: the harness prints a FIXED verdict \u2014 it never contains the "diverged" outcome, so the result line was not computed from the run. The files you created are still present. Make the harness evaluate the property's condition on the final real state and print REPLAY_RESULT=reproduced or REPLAY_RESULT=diverged from an if/else, run it, and answer with what it printed.
|
|
34965
34993
|
|
|
34966
|
-
` + renderBriefInput(brief) : controlMissing ? `Your previous attempt in this same container was NOT accepted: ${judged2.downgrade}. The files you created are still present. Add the control run (rule 5): the same calls on fresh stub state without the trace's hazard, printing REPLAY_CONTROL=holds or REPLAY_CONTROL=violated from an if/else (REPLAY_CONTROL=none only for a pure computation). Run it and answer with what it printed.
|
|
34967
|
-
|
|
34968
34994
|
` + renderBriefInput(brief) : resultMissing ? `Your previous attempt in this same container was NOT accepted: you answered "reproduced" but no shell output contained the line REPLAY_RESULT=reproduced. The files you created are still present. Run the harness again so that it prints REPLAY_RESULT=reproduced or REPLAY_RESULT=diverged as the last line of its own stdout, then answer with what it printed.
|
|
34969
34995
|
|
|
34970
34996
|
` + renderBriefInput(brief) : `Your previous attempt in this same container was NOT accepted: only ${v.covered} of the ${v.total} significant lines of the real ${v.basis} appeared verbatim in what you wrote` + (last.parsed.claim ? ` (you answered "${last.parsed.claim.verdict}")` : "") + `. The files you created are still present. Rewrite the harness so that ${what} is copied in FULL, line for line from the numbered source below, with helpers, imports and variables it needs stubbed OUTSIDE the copied lines. Then run it again and answer.
|
|
@@ -35995,9 +36021,9 @@ function isCrashTrace(trace) {
|
|
|
35995
36021
|
}
|
|
35996
36022
|
function mergeFormalFindings(response, outcome, pushedFiles) {
|
|
35997
36023
|
if (!outcome) return { blocking: 0 };
|
|
35998
|
-
const
|
|
35999
|
-
const pushed = new Set(pushedFiles.map(
|
|
36000
|
-
const blocking = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(
|
|
36024
|
+
const norm2 = (p) => p.replace(/\\/g, "/");
|
|
36025
|
+
const pushed = new Set(pushedFiles.map(norm2));
|
|
36026
|
+
const blocking = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(norm2(f.file)));
|
|
36001
36027
|
if (blocking.length === 0) return { blocking: 0 };
|
|
36002
36028
|
const existing = Array.isArray(response.findings) ? response.findings : [];
|
|
36003
36029
|
response.findings = [
|
|
@@ -36079,6 +36105,7 @@ function recordVerified(root, rec, now = Date.now()) {
|
|
|
36079
36105
|
var import_node_crypto17 = require("node:crypto");
|
|
36080
36106
|
var CONTEXT_BUDGET_MS = 2e3;
|
|
36081
36107
|
var sha256 = (s) => (0, import_node_crypto17.createHash)("sha256").update(s).digest("hex");
|
|
36108
|
+
var TEST_PATH4 = /(^|\/)(tests?|__tests__|spec)\/|\.(test|spec)\.[a-z]+$/;
|
|
36082
36109
|
function slice(content, f, maxLines) {
|
|
36083
36110
|
const all = content.split("\n");
|
|
36084
36111
|
const from = f.start.line;
|
|
@@ -36116,12 +36143,37 @@ function targetContext(opts) {
|
|
|
36116
36143
|
budget -= s.numbered.length;
|
|
36117
36144
|
helpers.push({ file: opts.file, symbol: h.qualified, lines: s.lines, sha256: sha256(opts.content), numbered: s.numbered, touches: [] });
|
|
36118
36145
|
}
|
|
36146
|
+
const callers = [];
|
|
36147
|
+
if (opts.codeSource) {
|
|
36148
|
+
const hits = opts.codeMap.callers(fn.value.name, { definedIn: opts.file, source: opts.codeSource, deadlineAt: Date.now() + 800, limit: 8 });
|
|
36149
|
+
const found = hits.source === "tree-sitter" ? hits.value.callers : [];
|
|
36150
|
+
const ranked = [...found.filter((c) => c.provenance !== "name"), ...found.filter((c) => c.provenance === "name")];
|
|
36151
|
+
for (const c of ranked) {
|
|
36152
|
+
if (callers.length >= MAX_CALLERS2) break;
|
|
36153
|
+
if (TEST_PATH4.test(c.path) || c.path === opts.file && c.line >= fn.value.start.line && c.line <= fn.value.end.line) continue;
|
|
36154
|
+
let content = contents.get(c.path);
|
|
36155
|
+
if (content === void 0) {
|
|
36156
|
+
const read2 = opts.codeSource.read(c.path, 512 * 1024);
|
|
36157
|
+
if (!read2.ok) continue;
|
|
36158
|
+
content = read2.content;
|
|
36159
|
+
contents.set(c.path, content);
|
|
36160
|
+
}
|
|
36161
|
+
const all = content.split("\n");
|
|
36162
|
+
const half = Math.floor(MAX_CALLER_LINES / 2);
|
|
36163
|
+
const from = Math.max(1, c.line - half);
|
|
36164
|
+
const numbered2 = numberLines(all.slice(from - 1, Math.min(all.length, from + MAX_CALLER_LINES - 1)).join("\n"), from);
|
|
36165
|
+
if (numbered2.length > budget) continue;
|
|
36166
|
+
budget -= numbered2.length;
|
|
36167
|
+
callers.push({ file: c.path, line: c.line, caller: c.caller, numbered: numbered2 });
|
|
36168
|
+
}
|
|
36169
|
+
}
|
|
36119
36170
|
const effects = a.effects.slice(0, MAX_CONTEXT_EFFECTS).map(renderEffect);
|
|
36120
36171
|
return {
|
|
36121
36172
|
fields: {
|
|
36122
36173
|
...effects.length ? { effects } : {},
|
|
36123
36174
|
...coAccessors.length ? { coAccessors } : {},
|
|
36124
|
-
...helpers.length ? { helpers } : {}
|
|
36175
|
+
...helpers.length ? { helpers } : {},
|
|
36176
|
+
...callers.length ? { callers } : {}
|
|
36125
36177
|
},
|
|
36126
36178
|
files: [...new Set(coAccessors.map((c) => c.file))],
|
|
36127
36179
|
signals: a.signals,
|
|
@@ -36447,6 +36499,7 @@ async function runFormalPhase(input) {
|
|
|
36447
36499
|
}
|
|
36448
36500
|
const top = chosen.top;
|
|
36449
36501
|
const sha2562 = sha256Of(chosen.content);
|
|
36502
|
+
const chosenContent = chosen.content;
|
|
36450
36503
|
const { lines, numbered: numbered2 } = chosen;
|
|
36451
36504
|
const target = { file: top.file, symbol: top.ai.symbol, lines, score: top.ai.score, reasons: [...top.reasons, ...top.ai.reasons], extent: chosen.extent, ...chosen.context ? { context: chosen.context.stats } : {}, ...floor ? { floor: true } : {} };
|
|
36452
36505
|
progress.target = target;
|
|
@@ -36488,6 +36541,25 @@ async function runFormalPhase(input) {
|
|
|
36488
36541
|
if (abstracts.length === 0 || stray || abstracts[0].file !== top.file) {
|
|
36489
36542
|
return done({ status: "skipped", skip: "model-header-mismatch", detail: `model abstracts ${stray?.file ?? "nothing"}, target was ${top.file}`, target, model: { name: gen.name, file: rel, rounds: round } });
|
|
36490
36543
|
}
|
|
36544
|
+
const files2 = /* @__PURE__ */ new Map([[top.file, chosenContent]]);
|
|
36545
|
+
for (const f of headerFiles) if (!files2.has(f)) {
|
|
36546
|
+
const r = readSrc(f, 512 * 1024);
|
|
36547
|
+
if (r.ok) files2.set(f, r.content);
|
|
36548
|
+
}
|
|
36549
|
+
const bad = report.properties.flatMap((p) => {
|
|
36550
|
+
const g = basisOf(p.source, files2);
|
|
36551
|
+
return g.ok ? [] : [`${p.id}: ${g.why}`];
|
|
36552
|
+
});
|
|
36553
|
+
if (bad.length > 0) {
|
|
36554
|
+
if (round < 2 && timeLeft() >= GENERATE_RESERVE_MS / 2) {
|
|
36555
|
+
previous = { source: gen.source, error: ungroundedMessage(bad.join("; ")) };
|
|
36556
|
+
report = null;
|
|
36557
|
+
continue;
|
|
36558
|
+
}
|
|
36559
|
+
const grounded = report.properties.filter((p) => basisOf(p.source, files2).ok);
|
|
36560
|
+
if (grounded.length === 0) return done({ status: "skipped", skip: "model-did-not-elaborate", detail: `no property says what it rests on: ${bad.join("; ").slice(0, 240)}`, target, model: { name: gen.name, file: rel, rounds: round } });
|
|
36561
|
+
report = { ...report, properties: grounded };
|
|
36562
|
+
}
|
|
36491
36563
|
break;
|
|
36492
36564
|
}
|
|
36493
36565
|
previous = { source: gen.source, error: report.error ?? (report.malformed ? "the model printed malformed VERITY lines" : "the model reported no property") };
|
|
@@ -36506,7 +36578,11 @@ async function runFormalPhase(input) {
|
|
|
36506
36578
|
} catch {
|
|
36507
36579
|
}
|
|
36508
36580
|
const isCrash = (p) => isCrashTrace(p.trace ?? []) || /\bcrash/i.test(p.desc);
|
|
36509
|
-
const
|
|
36581
|
+
const skipsTrigger = new Map(report.properties.filter((p) => p.result === "counterexample").flatMap((p) => {
|
|
36582
|
+
const missing = missingTrigger(p.desc, p.trace ?? []);
|
|
36583
|
+
return missing ? [[p.id, missing]] : [];
|
|
36584
|
+
}));
|
|
36585
|
+
const cexes = report.properties.filter((p) => p.result === "counterexample" && !skipsTrigger.has(p.id)).map((p, i2) => ({ p, i: i2 })).sort((a, b) => Number(isCrash(a.p)) - Number(isCrash(b.p)) || a.i - b.i).map(({ p }) => p);
|
|
36510
36586
|
const baseOf = (p) => ({ property: p.id, desc: p.desc, kind: p.kind, trace: p.trace ?? [], file: top.file, symbol: top.ai.symbol });
|
|
36511
36587
|
const maxReplays = input.maxReplays ?? MAX_REPLAYS;
|
|
36512
36588
|
let skipWhy = null;
|
|
@@ -36541,6 +36617,10 @@ async function runFormalPhase(input) {
|
|
|
36541
36617
|
}).finally(settle);
|
|
36542
36618
|
}
|
|
36543
36619
|
});
|
|
36620
|
+
for (const p of report.properties) {
|
|
36621
|
+
const missing = skipsTrigger.get(p.id);
|
|
36622
|
+
if (missing) findings.push({ ...baseOf(p), verdict: "model-only", reason: `not replayed: the property needs ${missing} first, and the counterexample has none` });
|
|
36623
|
+
}
|
|
36544
36624
|
const replayedIds = new Set(toReplay.map((p) => p.id));
|
|
36545
36625
|
for (const p of cexes) {
|
|
36546
36626
|
const r = byId.get(p.id);
|
|
@@ -36646,7 +36726,24 @@ async function generateAndCheck(root, req, opts) {
|
|
|
36646
36726
|
const abstracts = report.header?.abstracts ?? [];
|
|
36647
36727
|
const stray = abstracts.find((a) => !headerFiles.has(a.file)) ?? (abstracts[0] && abstracts[0].file !== req.file ? abstracts[0] : void 0);
|
|
36648
36728
|
if (report.ok && report.properties.length > 0 && abstracts.length > 0 && !stray) {
|
|
36649
|
-
|
|
36729
|
+
const files = /* @__PURE__ */ new Map([[req.file, read2.content]]);
|
|
36730
|
+
for (const f of headerFiles) if (!files.has(f)) {
|
|
36731
|
+
const r = readSrc(f, 512 * 1024);
|
|
36732
|
+
if (r.ok) files.set(f, r.content);
|
|
36733
|
+
}
|
|
36734
|
+
const bad = report.properties.flatMap((p) => {
|
|
36735
|
+
const g = basisOf(p.source, files);
|
|
36736
|
+
return g.ok ? [] : [`${p.id}: ${g.why}`];
|
|
36737
|
+
});
|
|
36738
|
+
if (bad.length === 0) return { ok: true, name: gen.name, file: rel, report, rounds: round, costUsd };
|
|
36739
|
+
lastError = ungroundedMessage(bad.join("; "));
|
|
36740
|
+
if (round < maxRounds) {
|
|
36741
|
+
previous = { source: gen.source, error: lastError };
|
|
36742
|
+
continue;
|
|
36743
|
+
}
|
|
36744
|
+
const grounded = report.properties.filter((p) => basisOf(p.source, files).ok);
|
|
36745
|
+
if (grounded.length > 0) return { ok: true, name: gen.name, file: rel, report: { ...report, properties: grounded }, rounds: round, costUsd };
|
|
36746
|
+
break;
|
|
36650
36747
|
}
|
|
36651
36748
|
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");
|
|
36652
36749
|
previous = { source: gen.source, error: lastError };
|
|
@@ -39671,7 +39768,7 @@ function registerInitCommand(program2) {
|
|
|
39671
39768
|
...telemetryChoice ? { telemetry: telemetryChoice } : {},
|
|
39672
39769
|
init: {
|
|
39673
39770
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39674
|
-
cli_version: true ? "0.33.3-experimental.
|
|
39771
|
+
cli_version: true ? "0.33.3-experimental.9d77663" : "dev"
|
|
39675
39772
|
}
|
|
39676
39773
|
});
|
|
39677
39774
|
} catch (err2) {
|
|
@@ -41144,8 +41241,8 @@ function registerLeanCommand(program2) {
|
|
|
41144
41241
|
}
|
|
41145
41242
|
|
|
41146
41243
|
// src/cli.ts
|
|
41147
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.33.3-experimental.
|
|
41148
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.3-experimental.
|
|
41244
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.33.3-experimental.9d77663").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) => {
|
|
41245
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.3-experimental.9d77663");
|
|
41149
41246
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
41150
41247
|
try {
|
|
41151
41248
|
await foldLegacyLocalCredential();
|
package/package.json
CHANGED