@codacy/verity-cli 0.34.3 → 0.34.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/verity.js +505 -267
  3. package/package.json +1 -1
package/bin/verity.js CHANGED
@@ -2928,7 +2928,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2928
2928
  * @param {(string | Function)} text - string to add, or a function returning a string
2929
2929
  * @return {Command} `this` command for chaining
2930
2930
  */
2931
- addHelpText(position, text) {
2931
+ addHelpText(position, text2) {
2932
2932
  const allowedValues = ["beforeAll", "before", "after", "afterAll"];
2933
2933
  if (!allowedValues.includes(position)) {
2934
2934
  throw new Error(`Unexpected value for position to addHelpText.
@@ -2937,10 +2937,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2937
2937
  const helpEvent = `${position}Help`;
2938
2938
  this.on(helpEvent, (context) => {
2939
2939
  let helpStr;
2940
- if (typeof text === "function") {
2941
- helpStr = text({ error: context.error, command: context.command });
2940
+ if (typeof text2 === "function") {
2941
+ helpStr = text2({ error: context.error, command: context.command });
2942
2942
  } else {
2943
- helpStr = text;
2943
+ helpStr = text2;
2944
2944
  }
2945
2945
  if (helpStr) {
2946
2946
  context.write(`${helpStr}
@@ -3982,14 +3982,14 @@ var require_foldFlowLines = __commonJS2({
3982
3982
  var FOLD_FLOW = "flow";
3983
3983
  var FOLD_BLOCK = "block";
3984
3984
  var FOLD_QUOTED = "quoted";
3985
- function foldFlowLines(text, indent, mode2 = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
3985
+ function foldFlowLines(text2, indent, mode2 = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
3986
3986
  if (!lineWidth || lineWidth < 0)
3987
- return text;
3987
+ return text2;
3988
3988
  if (lineWidth < minContentWidth)
3989
3989
  minContentWidth = 0;
3990
3990
  const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
3991
- if (text.length <= endStep)
3992
- return text;
3991
+ if (text2.length <= endStep)
3992
+ return text2;
3993
3993
  const folds = [];
3994
3994
  const escapedFolds = {};
3995
3995
  let end = lineWidth - indent.length;
@@ -4006,14 +4006,14 @@ var require_foldFlowLines = __commonJS2({
4006
4006
  let escStart = -1;
4007
4007
  let escEnd = -1;
4008
4008
  if (mode2 === FOLD_BLOCK) {
4009
- i2 = consumeMoreIndentedLines(text, i2, indent.length);
4009
+ i2 = consumeMoreIndentedLines(text2, i2, indent.length);
4010
4010
  if (i2 !== -1)
4011
4011
  end = i2 + endStep;
4012
4012
  }
4013
- for (let ch; ch = text[i2 += 1]; ) {
4013
+ for (let ch; ch = text2[i2 += 1]; ) {
4014
4014
  if (mode2 === FOLD_QUOTED && ch === "\\") {
4015
4015
  escStart = i2;
4016
- switch (text[i2 + 1]) {
4016
+ switch (text2[i2 + 1]) {
4017
4017
  case "x":
4018
4018
  i2 += 3;
4019
4019
  break;
@@ -4030,12 +4030,12 @@ var require_foldFlowLines = __commonJS2({
4030
4030
  }
4031
4031
  if (ch === "\n") {
4032
4032
  if (mode2 === FOLD_BLOCK)
4033
- i2 = consumeMoreIndentedLines(text, i2, indent.length);
4033
+ i2 = consumeMoreIndentedLines(text2, i2, indent.length);
4034
4034
  end = i2 + indent.length + endStep;
4035
4035
  split = void 0;
4036
4036
  } else {
4037
4037
  if (ch === " " && prev && prev !== " " && prev !== "\n" && prev !== " ") {
4038
- const next = text[i2 + 1];
4038
+ const next = text2[i2 + 1];
4039
4039
  if (next && next !== " " && next !== "\n" && next !== " ")
4040
4040
  split = i2;
4041
4041
  }
@@ -4047,12 +4047,12 @@ var require_foldFlowLines = __commonJS2({
4047
4047
  } else if (mode2 === FOLD_QUOTED) {
4048
4048
  while (prev === " " || prev === " ") {
4049
4049
  prev = ch;
4050
- ch = text[i2 += 1];
4050
+ ch = text2[i2 += 1];
4051
4051
  overflow = true;
4052
4052
  }
4053
4053
  const j = i2 > escEnd + 1 ? i2 - 2 : escStart - 1;
4054
4054
  if (escapedFolds[j])
4055
- return text;
4055
+ return text2;
4056
4056
  folds.push(j);
4057
4057
  escapedFolds[j] = true;
4058
4058
  end = j + endStep;
@@ -4067,39 +4067,39 @@ var require_foldFlowLines = __commonJS2({
4067
4067
  if (overflow && onOverflow)
4068
4068
  onOverflow();
4069
4069
  if (folds.length === 0)
4070
- return text;
4070
+ return text2;
4071
4071
  if (onFold)
4072
4072
  onFold();
4073
- let res = text.slice(0, folds[0]);
4073
+ let res = text2.slice(0, folds[0]);
4074
4074
  for (let i3 = 0; i3 < folds.length; ++i3) {
4075
4075
  const fold2 = folds[i3];
4076
- const end2 = folds[i3 + 1] || text.length;
4076
+ const end2 = folds[i3 + 1] || text2.length;
4077
4077
  if (fold2 === 0)
4078
4078
  res = `
4079
- ${indent}${text.slice(0, end2)}`;
4079
+ ${indent}${text2.slice(0, end2)}`;
4080
4080
  else {
4081
4081
  if (mode2 === FOLD_QUOTED && escapedFolds[fold2])
4082
- res += `${text[fold2]}\\`;
4082
+ res += `${text2[fold2]}\\`;
4083
4083
  res += `
4084
- ${indent}${text.slice(fold2 + 1, end2)}`;
4084
+ ${indent}${text2.slice(fold2 + 1, end2)}`;
4085
4085
  }
4086
4086
  }
4087
4087
  return res;
4088
4088
  }
4089
- function consumeMoreIndentedLines(text, i2, indent) {
4089
+ function consumeMoreIndentedLines(text2, i2, indent) {
4090
4090
  let end = i2;
4091
4091
  let start2 = i2 + 1;
4092
- let ch = text[start2];
4092
+ let ch = text2[start2];
4093
4093
  while (ch === " " || ch === " ") {
4094
4094
  if (i2 < start2 + indent) {
4095
- ch = text[++i2];
4095
+ ch = text2[++i2];
4096
4096
  } else {
4097
4097
  do {
4098
- ch = text[++i2];
4098
+ ch = text2[++i2];
4099
4099
  } while (ch && ch !== "\n");
4100
4100
  end = i2;
4101
4101
  start2 = i2 + 1;
4102
- ch = text[start2];
4102
+ ch = text2[start2];
4103
4103
  }
4104
4104
  }
4105
4105
  return end;
@@ -13882,11 +13882,11 @@ ${JSON.stringify(symbolNames, null, 2)}`);
13882
13882
  for (const c of captures) {
13883
13883
  if (c.name === captureName) nodes.push(c.node.text);
13884
13884
  }
13885
- const test = /* @__PURE__ */ __name((text, positive) => {
13886
- return positive ? regex.test(text) : !regex.test(text);
13885
+ const test = /* @__PURE__ */ __name((text2, positive) => {
13886
+ return positive ? regex.test(text2) : !regex.test(text2);
13887
13887
  }, "test");
13888
13888
  if (nodes.length === 0) return !isPositive;
13889
- return matchAll2 ? nodes.every((text) => test(text, isPositive)) : nodes.some((text) => test(text, isPositive));
13889
+ return matchAll2 ? nodes.every((text2) => test(text2, isPositive)) : nodes.some((text2) => test(text2, isPositive));
13890
13890
  });
13891
13891
  }
13892
13892
  __name(parseMatchPredicate, "parseMatchPredicate");
@@ -13916,7 +13916,7 @@ ${JSON.stringify(symbolNames, null, 2)}`);
13916
13916
  if (c.name === captureName) nodes.push(c.node.text);
13917
13917
  }
13918
13918
  if (nodes.length === 0) return !isPositive;
13919
- return nodes.every((text) => values.includes(text)) === isPositive;
13919
+ return nodes.every((text2) => values.includes(text2)) === isPositive;
13920
13920
  });
13921
13921
  }
13922
13922
  __name(parseAnyOfPredicate, "parseAnyOfPredicate");
@@ -15712,11 +15712,11 @@ function startSpinner(label2, opts = {}) {
15712
15712
  },
15713
15713
  succeed(message) {
15714
15714
  finish(() => {
15715
- const text = message ?? current;
15715
+ const text2 = message ?? current;
15716
15716
  const clock = showElapsed ? ` (${secondsSince(start2)})` : "";
15717
15717
  stream.write(
15718
- color ? ` ${GREEN2}\u2713${RESET} ${text}${DIM2}${clock}${RESET}
15719
- ` : ` \u2713 ${text}${clock}
15718
+ color ? ` ${GREEN2}\u2713${RESET} ${text2}${DIM2}${clock}${RESET}
15719
+ ` : ` \u2713 ${text2}${clock}
15720
15720
  `
15721
15721
  );
15722
15722
  });
@@ -17444,13 +17444,13 @@ function scrub(s) {
17444
17444
  var installed = false;
17445
17445
  var wroteBanner = false;
17446
17446
  var banner = "";
17447
- function append(text) {
17447
+ function append(text2) {
17448
17448
  try {
17449
17449
  const dir = projectPath(DEBUG_LOG_DIR);
17450
17450
  const file = projectPath(STDERR_LOG_FILE);
17451
17451
  (0, import_node_fs9.mkdirSync)(dir, { recursive: true });
17452
17452
  rotateIfNeeded(file);
17453
- (0, import_node_fs9.appendFileSync)(file, text);
17453
+ (0, import_node_fs9.appendFileSync)(file, text2);
17454
17454
  } catch {
17455
17455
  }
17456
17456
  }
@@ -17472,19 +17472,19 @@ function installStderrLog(cmd, argv, version) {
17472
17472
  const result = original(...args2);
17473
17473
  try {
17474
17474
  const chunk = args2[0];
17475
- const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : String(chunk);
17475
+ const text2 = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : String(chunk);
17476
17476
  ensureBanner();
17477
- append(scrub(text));
17477
+ append(scrub(text2));
17478
17478
  } catch {
17479
17479
  }
17480
17480
  return result;
17481
17481
  };
17482
17482
  process.stderr.write = tee;
17483
17483
  }
17484
- function logToFileOnly(text) {
17484
+ function logToFileOnly(text2) {
17485
17485
  if (!installed) return;
17486
17486
  ensureBanner();
17487
- append(text.endsWith("\n") ? text : `${text}
17487
+ append(text2.endsWith("\n") ? text2 : `${text2}
17488
17488
  `);
17489
17489
  }
17490
17490
 
@@ -18055,8 +18055,8 @@ var import_promises5 = require("node:fs/promises");
18055
18055
  var import_node_fs12 = require("node:fs");
18056
18056
  var import_node_child_process6 = require("node:child_process");
18057
18057
  var import_node_crypto2 = require("node:crypto");
18058
- function stripImageReferences(text) {
18059
- return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
18058
+ function stripImageReferences(text2) {
18059
+ return text2.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
18060
18060
  }
18061
18061
  function bufferTmpPath() {
18062
18062
  return `${CONVERSATION_BUFFER_FILE}.${process.pid}.${(0, import_node_crypto2.randomBytes)(4).toString("hex")}.tmp`;
@@ -18922,9 +18922,9 @@ var STOPWORDS = /* @__PURE__ */ new Set([
18922
18922
  "within",
18923
18923
  "without"
18924
18924
  ]);
18925
- function tokenize(text) {
18925
+ function tokenize(text2) {
18926
18926
  return new Set(
18927
- text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w))
18927
+ text2.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w))
18928
18928
  );
18929
18929
  }
18930
18930
  function jaccardKeywords(a, b) {
@@ -19001,9 +19001,9 @@ var MAX_BUDGET_TOKENS = 4e3;
19001
19001
  var MAX_NODE_FILE_BYTES = 64 * 1024;
19002
19002
  var MIN_SCORE = RELEVANCE_FLOOR * 0.5;
19003
19003
  var ORG_MIN_SHARED_TERMS = 2;
19004
- function orgTerms(text) {
19004
+ function orgTerms(text2) {
19005
19005
  return new Set(
19006
- text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
19006
+ text2.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
19007
19007
  );
19008
19008
  }
19009
19009
  function sharedTerms(a, b) {
@@ -19286,15 +19286,15 @@ function runHook(hooks) {
19286
19286
  return false;
19287
19287
  }
19288
19288
  }
19289
- function appendInRoot(root, name2, text, hooks = {}) {
19289
+ function appendInRoot(root, name2, text2, hooks = {}) {
19290
19290
  return writeVerified(root, name2, hooks, true, (fd) => {
19291
- (0, import_node_fs17.writeSync)(fd, text);
19291
+ (0, import_node_fs17.writeSync)(fd, text2);
19292
19292
  });
19293
19293
  }
19294
- function replaceInRoot(root, name2, text, hooks = {}) {
19294
+ function replaceInRoot(root, name2, text2, hooks = {}) {
19295
19295
  return writeVerified(root, name2, hooks, false, (fd) => {
19296
19296
  (0, import_node_fs17.ftruncateSync)(fd, 0);
19297
- (0, import_node_fs17.writeSync)(fd, text, 0);
19297
+ (0, import_node_fs17.writeSync)(fd, text2, 0);
19298
19298
  });
19299
19299
  }
19300
19300
 
@@ -19484,12 +19484,12 @@ function ensureVerityGitignore() {
19484
19484
  if (hasSupersededMemory && !memoryIsCommitted) {
19485
19485
  next = fenceMemoryLines(next);
19486
19486
  }
19487
- let text = next.join("\n");
19487
+ let text2 = next.join("\n");
19488
19488
  if (!hasMarker) {
19489
- const sep7 = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
19490
- text = text + sep7 + VERITY_GITIGNORE_BLOCK;
19489
+ const sep7 = text2 === "" ? "" : text2.endsWith("\n") ? "\n" : "\n\n";
19490
+ text2 = text2 + sep7 + VERITY_GITIGNORE_BLOCK;
19491
19491
  }
19492
- if (text !== content) (0, import_node_fs19.writeFileSync)(".gitignore", text);
19492
+ if (text2 !== content) (0, import_node_fs19.writeFileSync)(".gitignore", text2);
19493
19493
  return verified(
19494
19494
  memoryIsCommitted ? "memory-tracked" : hasSupersededMemory ? "memory-fenced" : needsRepair ? "repaired" : "added"
19495
19495
  );
@@ -19515,13 +19515,13 @@ function writeFencedBlock() {
19515
19515
  const hasMarker = content.includes(VERITY_GITIGNORE_MARKER);
19516
19516
  let lines = content.split("\n").map((l) => BREAKING_ENTRIES.has(l.trim()) ? ".verity/*" : l);
19517
19517
  lines = fenceMemoryLines(lines);
19518
- let text = lines.join("\n");
19518
+ let text2 = lines.join("\n");
19519
19519
  if (!hasMarker) {
19520
- const sep7 = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
19521
- text = text + sep7 + VERITY_GITIGNORE_BLOCK;
19520
+ const sep7 = text2 === "" ? "" : text2.endsWith("\n") ? "\n" : "\n\n";
19521
+ text2 = text2 + sep7 + VERITY_GITIGNORE_BLOCK;
19522
19522
  }
19523
19523
  try {
19524
- (0, import_node_fs19.writeFileSync)(".gitignore", text);
19524
+ (0, import_node_fs19.writeFileSync)(".gitignore", text2);
19525
19525
  return true;
19526
19526
  } catch {
19527
19527
  return false;
@@ -20500,26 +20500,26 @@ var CLAUDE_MD_PROSE_LEGACY = [
20500
20500
  "superseded \u2014 ignore them unless investigating history."
20501
20501
  ].join("\n");
20502
20502
  var PRESERVE_PLACEHOLDER = "<!-- Add binding, hand-curated guidance here; it survives Verity regeneration. -->";
20503
- function* scanLines(text) {
20503
+ function* scanLines(text2) {
20504
20504
  let offset = 0;
20505
20505
  let inFence = false;
20506
- for (const line2 of text.split("\n")) {
20506
+ for (const line2 of text2.split("\n")) {
20507
20507
  const isFence = /^(`{3,}|~{3,})/.test(line2.trim());
20508
20508
  if (isFence) inFence = !inFence;
20509
20509
  yield { line: line2, start: offset, inFence: inFence || isFence };
20510
20510
  offset += line2.length + 1;
20511
20511
  }
20512
20512
  }
20513
- function findMarker(text, marker, from = 0) {
20514
- for (const { line: line2, start: start2, inFence } of scanLines(text)) {
20513
+ function findMarker(text2, marker, from = 0) {
20514
+ for (const { line: line2, start: start2, inFence } of scanLines(text2)) {
20515
20515
  if (!inFence && start2 >= from && line2.startsWith(marker)) {
20516
20516
  return start2;
20517
20517
  }
20518
20518
  }
20519
20519
  return -1;
20520
20520
  }
20521
- function hasLegacyMemoryBlock(text) {
20522
- return findMarker(text, LEGACY_MD_START) !== -1;
20521
+ function hasLegacyMemoryBlock(text2) {
20522
+ return findMarker(text2, LEGACY_MD_START) !== -1;
20523
20523
  }
20524
20524
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
20525
20525
  const claudeMdPath = (0, import_node_path19.join)(cwd, "CLAUDE.md");
@@ -20719,10 +20719,10 @@ var STOP = /* @__PURE__ */ new Set([
20719
20719
  "verify",
20720
20720
  "check"
20721
20721
  ]);
20722
- function pendingTokens(text) {
20723
- if (!text || typeof text !== "string") return [];
20722
+ function pendingTokens(text2) {
20723
+ if (!text2 || typeof text2 !== "string") return [];
20724
20724
  const out2 = /* @__PURE__ */ new Set();
20725
- for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
20725
+ for (const raw of text2.toLowerCase().split(/[^a-z0-9]+/)) {
20726
20726
  if (raw.length <= 3) continue;
20727
20727
  if (STOP.has(raw)) continue;
20728
20728
  out2.add(raw);
@@ -20737,8 +20737,8 @@ function overlapCoefficient(a, b) {
20737
20737
  for (const t of a) if (setB.has(t)) shared++;
20738
20738
  return shared / Math.min(a.length, b.length);
20739
20739
  }
20740
- function isRepeatOfAny(text, priorFingerprints, threshold = REPEAT_THRESHOLD) {
20741
- const tokens = pendingTokens(text);
20740
+ function isRepeatOfAny(text2, priorFingerprints, threshold = REPEAT_THRESHOLD) {
20741
+ const tokens = pendingTokens(text2);
20742
20742
  if (tokens.length === 0) return false;
20743
20743
  return priorFingerprints.some((prior) => overlapCoefficient(tokens, prior) >= threshold);
20744
20744
  }
@@ -21428,8 +21428,8 @@ function assessContinuity(i2) {
21428
21428
  // src/lib/dossier/reanchor.ts
21429
21429
  var import_node_crypto8 = require("node:crypto");
21430
21430
  var import_node_fs25 = require("node:fs");
21431
- function lineSha(text) {
21432
- return (0, import_node_crypto8.createHash)("sha256").update(text.trim()).digest("hex").slice(0, HASH_WIDTH);
21431
+ function lineSha(text2) {
21432
+ return (0, import_node_crypto8.createHash)("sha256").update(text2.trim()).digest("hex").slice(0, HASH_WIDTH);
21433
21433
  }
21434
21434
  function fileHash(path) {
21435
21435
  try {
@@ -21902,10 +21902,10 @@ function recordGoal(d, prompt, source2 = "prompt") {
21902
21902
  appendEvent(d, { k: "goal_continue", text: prompt.slice(0, 64) });
21903
21903
  return;
21904
21904
  }
21905
- const text = prompt.slice(0, MAX_GOAL_CHARS);
21905
+ const text2 = prompt.slice(0, MAX_GOAL_CHARS);
21906
21906
  appendEvent(d, {
21907
21907
  k: "goal",
21908
- text,
21908
+ text: text2,
21909
21909
  text_len: prompt.length,
21910
21910
  source: source2,
21911
21911
  ...prompt.length > MAX_GOAL_CHARS && { truncated: true }
@@ -23005,8 +23005,8 @@ function buildCompactionContext(session) {
23005
23005
  `${r.projection.fold.refused} earlier finding(s) were dropped because the code they pointed at no longer matches. They are resolved; do not revisit them.`
23006
23006
  );
23007
23007
  }
23008
- const text = lines.join("\n");
23009
- if (text.length <= MAX_CHARS) return text;
23008
+ const text2 = lines.join("\n");
23009
+ if (text2.length <= MAX_CHARS) return text2;
23010
23010
  const trimmed = [...lines.slice(0, 5), ...lines.slice(5, 5 + 2)].join("\n");
23011
23011
  return trimmed.length <= MAX_CHARS ? trimmed : null;
23012
23012
  }
@@ -23324,9 +23324,9 @@ function declaredDependencies(root, files) {
23324
23324
  for (const path of pythonManifests) {
23325
23325
  if (!(0, import_node_fs31.existsSync)(path)) continue;
23326
23326
  try {
23327
- const text = (0, import_node_fs31.readFileSync)(path, "utf-8");
23328
- for (const m of text.matchAll(/^\s*["']?([A-Za-z][A-Za-z0-9._-]+)/gm)) names2.push(m[1]);
23329
- for (const line2 of text.split("\n")) {
23327
+ const text2 = (0, import_node_fs31.readFileSync)(path, "utf-8");
23328
+ for (const m of text2.matchAll(/^\s*["']?([A-Za-z][A-Za-z0-9._-]+)/gm)) names2.push(m[1]);
23329
+ for (const line2 of text2.split("\n")) {
23330
23330
  if (!/dependencies\s*=/.test(line2)) continue;
23331
23331
  for (const m of line2.matchAll(/["']([A-Za-z][A-Za-z0-9._-]+)/g)) names2.push(m[1]);
23332
23332
  }
@@ -23340,8 +23340,8 @@ function declaredDependencies(root, files) {
23340
23340
  for (const path of goMods) {
23341
23341
  if (!(0, import_node_fs31.existsSync)(path)) continue;
23342
23342
  try {
23343
- const text = (0, import_node_fs31.readFileSync)(path, "utf-8");
23344
- for (const m of text.matchAll(/^\s+([\w.-]+\/[\w./-]+)\s+v/gm)) {
23343
+ const text2 = (0, import_node_fs31.readFileSync)(path, "utf-8");
23344
+ for (const m of text2.matchAll(/^\s+([\w.-]+\/[\w./-]+)\s+v/gm)) {
23345
23345
  names2.push(m[1].replace(/^github\.com\//, ""));
23346
23346
  }
23347
23347
  } catch {
@@ -23351,8 +23351,8 @@ function declaredDependencies(root, files) {
23351
23351
  const path = (0, import_node_path28.join)(root, file);
23352
23352
  if (!(0, import_node_fs31.existsSync)(path)) continue;
23353
23353
  try {
23354
- const text = (0, import_node_fs31.readFileSync)(path, "utf-8");
23355
- for (const m of text.matchAll(/["'<]([A-Za-z][A-Za-z0-9._-]{2,})["'>]/g)) names2.push(m[1]);
23354
+ const text2 = (0, import_node_fs31.readFileSync)(path, "utf-8");
23355
+ for (const m of text2.matchAll(/["'<]([A-Za-z][A-Za-z0-9._-]{2,})["'>]/g)) names2.push(m[1]);
23356
23356
  } catch {
23357
23357
  }
23358
23358
  }
@@ -25060,9 +25060,9 @@ function readTextInput(value, flag, readStdin2 = () => (0, import_node_fs39.read
25060
25060
  if (raw.length > MAX_STDIN_BYTES) {
25061
25061
  return { ok: false, error: `${flag} from stdin is ${raw.length} bytes; the limit is ${MAX_STDIN_BYTES}.` };
25062
25062
  }
25063
- const text = raw.toString("utf8").replace(/\r?\n$/, "");
25064
- if (!text.trim()) return { ok: false, error: `${flag} from stdin is empty.` };
25065
- return { ok: true, text };
25063
+ const text2 = raw.toString("utf8").replace(/\r?\n$/, "");
25064
+ if (!text2.trim()) return { ok: false, error: `${flag} from stdin is empty.` };
25065
+ return { ok: true, text: text2 };
25066
25066
  }
25067
25067
 
25068
25068
  // src/lib/terminal-text.ts
@@ -25080,8 +25080,8 @@ function terminalUrl(value) {
25080
25080
  // src/commands/feedback.ts
25081
25081
  function registerFeedbackCommand(program2) {
25082
25082
  const feedbackCmd = program2.command("feedback").description("Send feedback to the Verity team");
25083
- feedbackCmd.command("message <text>").description("Send general feedback (- reads the message from stdin, so the words never pass through shell quoting)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (text, opts) => {
25084
- await sendMessageArg(text, opts, program2.opts());
25083
+ feedbackCmd.command("message <text>").description("Send general feedback (- reads the message from stdin, so the words never pass through shell quoting)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (text2, opts) => {
25084
+ await sendMessageArg(text2, opts, program2.opts());
25085
25085
  });
25086
25086
  feedbackCmd.command("finding <run-id> <pattern-id> <action> [note]").description("Submit per-finding feedback (false_positive, acknowledged, useful, etc.); a note of - is read from stdin").option("--file <path>", "File path the finding applies to").option("--line <n>", "Line number", parseInt).action(async (runId, patternId, action, inlineNote, opts) => {
25087
25087
  const globals = program2.opts();
@@ -25686,28 +25686,28 @@ function parseRgLine(line2) {
25686
25686
  return { file: line2.slice(0, first), line: n2, text: line2.slice(second + 1) };
25687
25687
  }
25688
25688
  var isWordChar = (c) => c !== void 0 && /[A-Za-z0-9_]/.test(c);
25689
- function wordHit(text, symbol) {
25689
+ function wordHit(text2, symbol) {
25690
25690
  let from = 0;
25691
25691
  for (; ; ) {
25692
- const at = text.indexOf(symbol, from);
25692
+ const at = text2.indexOf(symbol, from);
25693
25693
  if (at < 0) return false;
25694
- const before = at === 0 ? void 0 : text[at - 1];
25695
- const after = text[at + symbol.length];
25694
+ const before = at === 0 ? void 0 : text2[at - 1];
25695
+ const after = text2[at + symbol.length];
25696
25696
  if (!isWordChar(before) && !isWordChar(after)) return true;
25697
25697
  from = at + 1;
25698
25698
  }
25699
25699
  }
25700
- function isContractLine(text, symbol) {
25700
+ function isContractLine(text2, symbol) {
25701
25701
  for (const kw of ["implements", "extends"]) {
25702
25702
  let from = 0;
25703
25703
  for (; ; ) {
25704
- const at = text.indexOf(kw, from);
25704
+ const at = text2.indexOf(kw, from);
25705
25705
  if (at < 0) break;
25706
25706
  from = at + 1;
25707
- const before = at === 0 ? void 0 : text[at - 1];
25708
- const after = text[at + kw.length];
25707
+ const before = at === 0 ? void 0 : text2[at - 1];
25708
+ const after = text2[at + kw.length];
25709
25709
  if (isWordChar(before) || isWordChar(after)) continue;
25710
- let clause = text.slice(at + kw.length);
25710
+ let clause = text2.slice(at + kw.length);
25711
25711
  const stop2 = Math.min(
25712
25712
  ...[clause.indexOf(";"), clause.indexOf("{")].filter((i2) => i2 >= 0)
25713
25713
  );
@@ -25739,20 +25739,20 @@ function partitionSites(rgLines, symbols, opts) {
25739
25739
  if (opts.sentPaths.has(h.file)) continue;
25740
25740
  if (opts.isExcluded(h.file)) continue;
25741
25741
  if (!isCodeSiteFile(h.file)) continue;
25742
- const text = h.text.trim();
25743
- if (text.length === 0 || IMPORT_LINE.test(h.text)) continue;
25744
- if (COMMENT_LINE.test(text)) continue;
25745
- if (BARE_MEMBER_LINE.test(text) || GO_IMPORT_PATH_LINE.test(text)) continue;
25742
+ const text2 = h.text.trim();
25743
+ if (text2.length === 0 || IMPORT_LINE.test(h.text)) continue;
25744
+ if (COMMENT_LINE.test(text2)) continue;
25745
+ if (BARE_MEMBER_LINE.test(text2) || GO_IMPORT_PATH_LINE.test(text2)) continue;
25746
25746
  const n2 = perFile.get(h.file) ?? 0;
25747
25747
  if (n2 >= MAX_SITES_PER_FILE) continue;
25748
25748
  const site = {
25749
25749
  file: h.file,
25750
25750
  line: h.line,
25751
- text: text.slice(0, SITE_TEXT_MAX),
25751
+ text: text2.slice(0, SITE_TEXT_MAX),
25752
25752
  symbol: h.symbol,
25753
25753
  // R2 falls out of R1 for free: a word search for `Sym` already matches
25754
25754
  // `implements Sym` / `extends Sym` lines — classification is all R2 is.
25755
- ...isContractLine(text, h.symbol) ? { kind: "contract" } : {}
25755
+ ...isContractLine(text2, h.symbol) ? { kind: "contract" } : {}
25756
25756
  };
25757
25757
  if (isTestPath(h.file)) {
25758
25758
  if (tests.length < MAX_TEST_SLOTS) {
@@ -25772,8 +25772,8 @@ var MAX_IMPORTERS_PER_FILE = 2;
25772
25772
  function moduleKey(path) {
25773
25773
  return path.replace(/\.(ts|tsx|js|jsx|mjs|cjs|mts|cts)$/i, "").replace(/\/index$/, "");
25774
25774
  }
25775
- function importSpecifier(text) {
25776
- const m = /(?:\bfrom|\brequire\s*\(|\bimport\s*\(|^\s*import)\s*['"]([^'"\n]+)['"]/.exec(text);
25775
+ function importSpecifier(text2) {
25776
+ const m = /(?:\bfrom|\brequire\s*\(|\bimport\s*\(|^\s*import)\s*['"]([^'"\n]+)['"]/.exec(text2);
25777
25777
  return m?.[1] ?? null;
25778
25778
  }
25779
25779
  function resolveSpecifier(fromFile, spec) {
@@ -26043,8 +26043,8 @@ function extractEnclosingExcerpt(content, siteLine, path) {
26043
26043
  start2 = Math.max(1, siteLine - (EXCERPT_MAX_LINES - 6));
26044
26044
  }
26045
26045
  const end = Math.min(lines.length, start2 + EXCERPT_MAX_LINES - 1);
26046
- const text = lines.slice(start2 - 1, end).join("\n").slice(0, EXCERPT_MAX_CHARS);
26047
- return { start_line: start2, text };
26046
+ const text2 = lines.slice(start2 - 1, end).join("\n").slice(0, EXCERPT_MAX_CHARS);
26047
+ return { start_line: start2, text: text2 };
26048
26048
  }
26049
26049
  function upgradeToExcerpts(rc, opts) {
26050
26050
  if (rc.state !== "ok") return;
@@ -27141,12 +27141,12 @@ function parseKnowledgeNote(raw) {
27141
27141
  if (!raw || typeof raw !== "object") return null;
27142
27142
  const o = raw;
27143
27143
  const node_id = typeof o.node_id === "string" ? o.node_id.trim() : "";
27144
- const text = typeof o.text === "string" ? o.text.trim() : "";
27145
- if (!node_id || !text) return null;
27146
- if (text.length > 1e3) return null;
27144
+ const text2 = typeof o.text === "string" ? o.text.trim() : "";
27145
+ if (!node_id || !text2) return null;
27146
+ if (text2.length > 1e3) return null;
27147
27147
  return {
27148
27148
  node_id,
27149
- text,
27149
+ text: text2,
27150
27150
  file: typeof o.file === "string" && o.file ? o.file : null,
27151
27151
  line: typeof o.line === "number" && Number.isFinite(o.line) && o.line > 0 ? Math.floor(o.line) : null
27152
27152
  };
@@ -27184,10 +27184,10 @@ function episodeForWire(e, humanSpoke) {
27184
27184
  var MAX_AGENT_CONTEXT_CHARS = 1500;
27185
27185
  var MAX_AGENT_ITEMS = 5;
27186
27186
  var REPORT_PREFIX = "Verity review (a report on the code just written \u2014 relay what matters to the user; it is not an instruction to you):";
27187
- function renderItem(label2, text, patternId, file, line2) {
27187
+ function renderItem(label2, text2, patternId, file, line2) {
27188
27188
  const where2 = file ? ` (${file}${line2 !== void 0 && line2 !== "" ? `:${line2}` : ""})` : "";
27189
27189
  const id = patternId ? ` [${patternId}]` : "";
27190
- return `- ${label2}${text}${where2}${id}`;
27190
+ return `- ${label2}${text2}${where2}${id}`;
27191
27191
  }
27192
27192
  function channelInputFrom(response, intentRepeat = 0, priorPendingFingerprints = []) {
27193
27193
  const metadata2 = response.metadata ?? {};
@@ -27269,11 +27269,11 @@ function buildAgentContext(input) {
27269
27269
  for (const p of input.pendingItems ?? []) {
27270
27270
  if (lines.length >= MAX_AGENT_ITEMS) break;
27271
27271
  if (p.pattern_id === "intent-misalignment") continue;
27272
- const text = p.description ?? p.title ?? p.reason;
27273
- if (!text) continue;
27274
- const seenBefore = isRepeatOfAny(text, input.priorPendingFingerprints ?? []);
27272
+ const text2 = p.description ?? p.title ?? p.reason;
27273
+ if (!text2) continue;
27274
+ const seenBefore = isRepeatOfAny(text2, input.priorPendingFingerprints ?? []);
27275
27275
  lines.push(
27276
- renderItem("", text, p.pattern_id, p.file, p.line) + (seenBefore ? "\n (raised earlier this session and still open \u2014 do not re-explain it; act on it or carry on)" : "")
27276
+ renderItem("", text2, p.pattern_id, p.file, p.line) + (seenBefore ? "\n (raised earlier this session and still open \u2014 do not re-explain it; act on it or carry on)" : "")
27277
27277
  );
27278
27278
  }
27279
27279
  if (lines.length === 0) return null;
@@ -27281,11 +27281,11 @@ function buildAgentContext(input) {
27281
27281
  ${lines.slice(0, MAX_AGENT_ITEMS).join("\n")}`;
27282
27282
  return truncateToCap(body2);
27283
27283
  }
27284
- function truncateToCap(text) {
27285
- if (text.length <= MAX_AGENT_CONTEXT_CHARS) return text;
27284
+ function truncateToCap(text2) {
27285
+ if (text2.length <= MAX_AGENT_CONTEXT_CHARS) return text2;
27286
27286
  const marker = "\n- (truncated)";
27287
27287
  const budget = MAX_AGENT_CONTEXT_CHARS - marker.length;
27288
- const lines = text.slice(0, budget).split("\n");
27288
+ const lines = text2.slice(0, budget).split("\n");
27289
27289
  lines.pop();
27290
27290
  return `${lines.join("\n")}${marker}`;
27291
27291
  }
@@ -27313,7 +27313,7 @@ function channelSilence(input) {
27313
27313
  // src/lib/cli-version.ts
27314
27314
  function cliVersion() {
27315
27315
  try {
27316
- return true ? "0.34.3" : "dev";
27316
+ return true ? "0.34.4" : "dev";
27317
27317
  } catch {
27318
27318
  return "dev";
27319
27319
  }
@@ -29489,8 +29489,8 @@ function argResource(kind2, arg) {
29489
29489
  const keyed = keyedCall(arg.text);
29490
29490
  return keyed ? { kind: kind2, name: keyed, confidence: "expression" } : { kind: kind2, name: null, confidence: "unknown" };
29491
29491
  }
29492
- function keyedCall(text) {
29493
- const t = text.trim();
29492
+ function keyedCall(text2) {
29493
+ const t = text2.trim();
29494
29494
  const open = t.indexOf("(");
29495
29495
  if (open <= 0 || !t.endsWith(")")) return null;
29496
29496
  const callee = t.slice(0, open);
@@ -29524,16 +29524,16 @@ var SQL_TABLE = [
29524
29524
  [/\bfrom\s+[`"[]?([\w.]+)/i, "read"]
29525
29525
  ];
29526
29526
  function sqlLiteral(c) {
29527
- const text = c.args.find((a) => a.literal !== null && SQL.test(a.literal))?.literal;
29528
- if (!text) return null;
29527
+ const text2 = c.args.find((a) => a.literal !== null && SQL.test(a.literal))?.literal;
29528
+ if (!text2) return null;
29529
29529
  for (const [re, op] of SQL_TABLE) {
29530
- const m = re.exec(text);
29530
+ const m = re.exec(text2);
29531
29531
  if (m) {
29532
29532
  const name2 = m[1].split(".").pop();
29533
- return { op, resource: table(name2), rule: "sql.literal", lock: op === "read" && /\bfor\s+(?:no\s+key\s+)?update\b/i.test(text) };
29533
+ return { op, resource: table(name2), rule: "sql.literal", lock: op === "read" && /\bfor\s+(?:no\s+key\s+)?update\b/i.test(text2) };
29534
29534
  }
29535
29535
  }
29536
- return { op: /^\s*select\b/i.test(text) ? "read" : "write", resource: { kind: "table", name: null, confidence: "unknown" }, rule: "sql.literal" };
29536
+ return { op: /^\s*select\b/i.test(text2) ? "read" : "write", resource: { kind: "table", name: null, confidence: "unknown" }, rule: "sql.literal" };
29537
29537
  }
29538
29538
  var SB_WRITE = /* @__PURE__ */ new Set(["insert", "update", "upsert", "delete"]);
29539
29539
  var SB_FROM = /\bfrom\(\s*(['"`])([\w.-]+)\1\s*\)\s*$/;
@@ -29929,28 +29929,28 @@ var ALIAS_OF = "alias-of:";
29929
29929
  var FIELD_OF = "field-of:";
29930
29930
  var IDENT_CHAR = /[\w$]/;
29931
29931
  var isIdent = (s) => /^[A-Za-z_$][\w$]*$/.test(s);
29932
- function wordAt(text, word) {
29932
+ function wordAt(text2, word) {
29933
29933
  const out2 = [];
29934
- for (let i2 = text.indexOf(word); i2 >= 0; i2 = text.indexOf(word, i2 + 1)) {
29935
- if (IDENT_CHAR.test(text[i2 - 1] ?? "") || IDENT_CHAR.test(text[i2 + word.length] ?? "")) continue;
29934
+ for (let i2 = text2.indexOf(word); i2 >= 0; i2 = text2.indexOf(word, i2 + 1)) {
29935
+ if (IDENT_CHAR.test(text2[i2 - 1] ?? "") || IDENT_CHAR.test(text2[i2 + word.length] ?? "")) continue;
29936
29936
  out2.push(i2);
29937
29937
  }
29938
29938
  return out2;
29939
29939
  }
29940
29940
  var GO_NOT_TYPES = /* @__PURE__ */ new Set(["int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64", "complex64", "complex128", "string", "bool", "byte", "rune", "error", "any", "func", "chan", "map", "struct", "interface", "range", "make", "new", "nil", "true", "false"]);
29941
- function typeAt(text, i2, lang) {
29941
+ function typeAt(text2, i2, lang) {
29942
29942
  let j = i2;
29943
29943
  const skip = () => {
29944
- while (j < text.length && /[\s?*&]/.test(text[j])) j++;
29944
+ while (j < text2.length && /[\s?*&]/.test(text2[j])) j++;
29945
29945
  };
29946
29946
  skip();
29947
- for (const kw of ["new ", "readonly ", "await "]) if (text.startsWith(kw, j)) {
29947
+ for (const kw of ["new ", "readonly ", "await "]) if (text2.startsWith(kw, j)) {
29948
29948
  j += kw.length;
29949
29949
  skip();
29950
29950
  }
29951
29951
  let k = j;
29952
- while (k < text.length && /[\w$.]/.test(text[k])) k++;
29953
- const name2 = text.slice(j, k).split(".").pop() ?? "";
29952
+ while (k < text2.length && /[\w$.]/.test(text2[k])) k++;
29953
+ const name2 = text2.slice(j, k).split(".").pop() ?? "";
29954
29954
  if (lang === "go") return /^[A-Za-z_]\w*$/.test(name2) && !GO_NOT_TYPES.has(name2) ? name2 : null;
29955
29955
  return /^[A-Z][\w$]*$/.test(name2) ? name2 : null;
29956
29956
  }
@@ -30100,10 +30100,10 @@ function boundInGo(fnText, name2) {
30100
30100
  const brace = fnText.indexOf("{");
30101
30101
  return wordAt(brace < 0 ? fnText : fnText.slice(0, brace), name2).length > 0;
30102
30102
  }
30103
- function rebindsInGo(text, name2) {
30104
- for (const i2 of wordAt(text, name2)) {
30105
- const lineEnd = text.indexOf("\n", i2);
30106
- const rest = text.slice(i2 + name2.length, lineEnd < 0 ? void 0 : lineEnd);
30103
+ function rebindsInGo(text2, name2) {
30104
+ for (const i2 of wordAt(text2, name2)) {
30105
+ const lineEnd = text2.indexOf("\n", i2);
30106
+ const rest = text2.slice(i2 + name2.length, lineEnd < 0 ? void 0 : lineEnd);
30107
30107
  const decl = rest.indexOf(":=");
30108
30108
  if (decl >= 0 && /^[\s\w,]*$/.test(rest.slice(0, decl))) return true;
30109
30109
  }
@@ -30329,15 +30329,15 @@ function importOf(content, lang, name2, receiver) {
30329
30329
  return null;
30330
30330
  }
30331
30331
  var TS_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
30332
- function parseJsonc(text) {
30332
+ function parseJsonc(text2) {
30333
30333
  let out2 = "";
30334
30334
  let inString = false;
30335
- for (let i2 = 0; i2 < text.length; i2++) {
30336
- const c = text[i2];
30335
+ for (let i2 = 0; i2 < text2.length; i2++) {
30336
+ const c = text2[i2];
30337
30337
  if (inString) {
30338
30338
  out2 += c;
30339
30339
  if (c === "\\") {
30340
- out2 += text[++i2] ?? "";
30340
+ out2 += text2[++i2] ?? "";
30341
30341
  continue;
30342
30342
  }
30343
30343
  if (c === '"') inString = false;
@@ -30348,14 +30348,14 @@ function parseJsonc(text) {
30348
30348
  out2 += c;
30349
30349
  continue;
30350
30350
  }
30351
- if (c === "/" && text[i2 + 1] === "/") {
30352
- while (i2 < text.length && text[i2] !== "\n") i2++;
30351
+ if (c === "/" && text2[i2 + 1] === "/") {
30352
+ while (i2 < text2.length && text2[i2] !== "\n") i2++;
30353
30353
  out2 += "\n";
30354
30354
  continue;
30355
30355
  }
30356
- if (c === "/" && text[i2 + 1] === "*") {
30356
+ if (c === "/" && text2[i2 + 1] === "*") {
30357
30357
  i2 += 2;
30358
- while (i2 < text.length && !(text[i2] === "*" && text[i2 + 1] === "/")) i2++;
30358
+ while (i2 < text2.length && !(text2[i2] === "*" && text2[i2 + 1] === "/")) i2++;
30359
30359
  i2++;
30360
30360
  continue;
30361
30361
  }
@@ -30564,36 +30564,36 @@ function definitionOf(input) {
30564
30564
  }
30565
30565
  var MAX_CALLER_SCAN_FILES = 60;
30566
30566
  var isWordChar2 = (c) => c !== void 0 && /[\w$]/.test(c);
30567
- function callStarts(text, name2) {
30567
+ function callStarts(text2, name2) {
30568
30568
  const out2 = [];
30569
- for (let i2 = text.indexOf(name2); i2 >= 0; i2 = text.indexOf(name2, i2 + 1)) {
30569
+ for (let i2 = text2.indexOf(name2); i2 >= 0; i2 = text2.indexOf(name2, i2 + 1)) {
30570
30570
  let j = i2 + name2.length;
30571
- if (text[j] === "<") {
30571
+ if (text2[j] === "<") {
30572
30572
  let depth = 0;
30573
- for (; j < text.length; j++) {
30574
- if (text[j] === "<") depth++;
30575
- else if (text[j] === ">" && --depth === 0) break;
30573
+ for (; j < text2.length; j++) {
30574
+ if (text2[j] === "<") depth++;
30575
+ else if (text2[j] === ">" && --depth === 0) break;
30576
30576
  }
30577
30577
  j++;
30578
30578
  }
30579
- if (text[j] === "(") out2.push({ index: i2, open: j });
30579
+ if (text2[j] === "(") out2.push({ index: i2, open: j });
30580
30580
  }
30581
30581
  return out2;
30582
30582
  }
30583
- function callsName(text, name2) {
30584
- const t = text.trim();
30583
+ function callsName(text2, name2) {
30584
+ const t = text2.trim();
30585
30585
  if (t.startsWith("//") || t.startsWith("*") || t.startsWith("/*") || t.startsWith("#")) return false;
30586
- for (const at of callStarts(text, name2)) {
30586
+ for (const at of callStarts(text2, name2)) {
30587
30587
  const i2 = at.index;
30588
- const spread = text.slice(Math.max(0, i2 - 3), i2) === "...";
30589
- if (isWordChar2(text[i2 - 1]) || text[i2 - 1] === "." && !spread) continue;
30588
+ const spread = text2.slice(Math.max(0, i2 - 3), i2) === "...";
30589
+ if (isWordChar2(text2[i2 - 1]) || text2[i2 - 1] === "." && !spread) continue;
30590
30590
  let depth = 0;
30591
30591
  let j = at.open;
30592
- for (; j < text.length; j++) {
30593
- if (text[j] === "(") depth++;
30594
- else if (text[j] === ")" && --depth === 0) break;
30592
+ for (; j < text2.length; j++) {
30593
+ if (text2[j] === "(") depth++;
30594
+ else if (text2[j] === ")" && --depth === 0) break;
30595
30595
  }
30596
- const after = text.slice(j + 1).trimStart();
30596
+ const after = text2.slice(j + 1).trimStart();
30597
30597
  if (after.startsWith(":") || after.startsWith("{")) continue;
30598
30598
  return true;
30599
30599
  }
@@ -30683,11 +30683,11 @@ function callersOf(input) {
30683
30683
  if (seen.has(line2) || declared.has(line2)) continue;
30684
30684
  const inside2 = r.value.some((f) => f.start.line <= line2 && line2 <= f.end.line && f.calls.some((c) => c.line === line2 && c.name === name2));
30685
30685
  if (inside2) continue;
30686
- const text = lines[line2 - 1] ?? "";
30687
- if (!callsName(text, name2) || /^\s*(?:export\s+)?(?:async\s+)?(?:function|def|func)\b/.test(text)) continue;
30686
+ const text2 = lines[line2 - 1] ?? "";
30687
+ if (!callsName(text2, name2) || /^\s*(?:export\s+)?(?:async\s+)?(?:function|def|func)\b/.test(text2)) continue;
30688
30688
  const enclosing = r.value.filter((f) => f.start.line <= line2 && line2 <= f.end.line).sort((a, b) => a.end.line - a.start.line - (b.end.line - b.start.line))[0];
30689
30689
  seen.add(line2);
30690
- callers.push({ path: p, caller: enclosing ? enclosing.qualified : null, line: line2, text: text.trim().slice(0, 200), provenance: fileTie });
30690
+ callers.push({ path: p, caller: enclosing ? enclosing.qualified : null, line: line2, text: text2.trim().slice(0, 200), provenance: fileTie });
30691
30691
  }
30692
30692
  }
30693
30693
  }
@@ -30994,16 +30994,16 @@ ${m.accessors.map((x) => ` ${x.fn.path}:${x.fn.start.line} ${x.fn.qualified} \u
30994
30994
  return { ok: true, value: lines.join("\n") };
30995
30995
  };
30996
30996
  var KINDS = ["table", "cache", "file", "http", "queue", "service", "memory"];
30997
- function parseResource(text) {
30998
- const colon = text.indexOf(":");
30999
- const kind2 = colon > 0 ? text.slice(0, colon) : "";
31000
- if (KINDS.includes(kind2)) return { kinds: [kind2], name: text.slice(colon + 1) };
31001
- return { kinds: KINDS, name: text };
30997
+ function parseResource(text2) {
30998
+ const colon = text2.indexOf(":");
30999
+ const kind2 = colon > 0 ? text2.slice(0, colon) : "";
31000
+ if (KINDS.includes(kind2)) return { kinds: [kind2], name: text2.slice(colon + 1) };
31001
+ return { kinds: KINDS, name: text2 };
31002
31002
  }
31003
31003
  var writersOfTool = async (ctx, args2) => {
31004
- const text = str(args2.resource, 300, "resource");
31005
- if (!text.ok) return text;
31006
- const { kinds, name: name2 } = parseResource(text.value);
31004
+ const text2 = str(args2.resource, 300, "resource");
31005
+ if (!text2.ok) return text2;
31006
+ const { kinds, name: name2 } = parseResource(text2.value);
31007
31007
  if (name2.length < 2) return { ok: false, error: "resource name too short to search for" };
31008
31008
  const deadline = Date.now() + TOOL_MS;
31009
31009
  const src = source(ctx);
@@ -31042,7 +31042,7 @@ var writersOfTool = async (ctx, args2) => {
31042
31042
  if (files.size > 25) truncated = true;
31043
31043
  const list2 = (xs) => xs.length ? xs.slice(0, MAX_LIST).map((x) => ` ${x}`).join("\n") + (xs.length > MAX_LIST ? `
31044
31044
  \u2026 ${xs.length - MAX_LIST} more` : "") : " none found";
31045
- return { ok: true, value: `${text.value} (${where(ctx)}), heuristic${truncated ? ", search cut short" : ""}
31045
+ return { ok: true, value: `${text2.value} (${where(ctx)}), heuristic${truncated ? ", search cut short" : ""}
31046
31046
  writers:
31047
31047
  ${list2(writers)}
31048
31048
  readers:
@@ -31100,9 +31100,9 @@ function validateRepoPath(value, { allowRoot = false } = {}) {
31100
31100
  function lineNumber(value, fallback) {
31101
31101
  return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : fallback;
31102
31102
  }
31103
- function capOutput(text) {
31104
- return text.length > TOOL_LIMITS.outputChars ? `${text.slice(0, TOOL_LIMITS.outputChars)}
31105
- [truncated at ${TOOL_LIMITS.outputChars} characters]` : text;
31103
+ function capOutput(text2) {
31104
+ return text2.length > TOOL_LIMITS.outputChars ? `${text2.slice(0, TOOL_LIMITS.outputChars)}
31105
+ [truncated at ${TOOL_LIMITS.outputChars} characters]` : text2;
31106
31106
  }
31107
31107
  function git(ctx, args2, opts = {}) {
31108
31108
  return runGit(ctx.root, args2, { timeoutMs: opts.timeoutMs ?? TOOL_LIMITS.gitTimeoutMs, maxBuffer: opts.maxBuffer ?? TOOL_LIMITS.fileBytes });
@@ -31236,8 +31236,8 @@ var listFilesTool = async (ctx, args2) => {
31236
31236
  \u2026 ${more} more` : ""}` : "no matching files" };
31237
31237
  };
31238
31238
  var searchTool = async (ctx, args2) => {
31239
- const text = args2.text;
31240
- if (typeof text !== "string" || text.length === 0 || text.length > TOOL_LIMITS.textChars || /[\n\r]/.test(text)) {
31239
+ const text2 = args2.text;
31240
+ if (typeof text2 !== "string" || text2.length === 0 || text2.length > TOOL_LIMITS.textChars || /[\n\r]/.test(text2)) {
31241
31241
  return { ok: false, error: `text must be 1-${TOOL_LIMITS.textChars} characters on one line` };
31242
31242
  }
31243
31243
  const glob = typeof args2.path_glob === "string" && args2.path_glob.length > 0 ? args2.path_glob : null;
@@ -31246,7 +31246,7 @@ var searchTool = async (ctx, args2) => {
31246
31246
  }
31247
31247
  const pathspec = glob ? [`:(glob)${glob}`] : [];
31248
31248
  const revision = ctx.range.kind === "push" ? [ctx.range.head] : ["--cached"];
31249
- const res = await git(ctx, ["grep", "-n", "-I", "-F", "--no-color", ...revision.filter((r) => r === "--cached"), "-e", text, ...revision.filter((r) => r !== "--cached"), "--", ...pathspec]);
31249
+ const res = await git(ctx, ["grep", "-n", "-I", "-F", "--no-color", ...revision.filter((r) => r === "--cached"), "-e", text2, ...revision.filter((r) => r !== "--cached"), "--", ...pathspec]);
31250
31250
  if (!res.ok) {
31251
31251
  if (res.error === "failed" && res.stderr.trim() === "") return { ok: true, value: "no matches" };
31252
31252
  return { ok: false, error: gitFailure(res, "search") };
@@ -31284,7 +31284,7 @@ function makeToolExecutor(ctx) {
31284
31284
  const index = next++;
31285
31285
  const call = calls[index];
31286
31286
  const codeTool = ctx.codeMap && CODE_TOOL_NAMES.includes(call.name) ? CODE_TOOLS[call.name] : null;
31287
- const tool = codeTool ? (c, args2) => codeTool({ root: c.root, revision: c.range.kind === "push" ? c.range.head : null, codeMap: c.codeMap, validatePath: (v) => validateRepoPath(v), denied: isDeniedPath }, args2) : TOOLS[call.name] ?? null;
31287
+ const tool = codeTool ? (c, args2) => codeTool({ root: c.root, revision: c.range.kind === "push" ? c.range.head : null, codeMap: c.codeMap, validatePath: (v) => validateRepoPath(v), denied: isDeniedPath }, args2) : Object.hasOwn(TOOLS, call.name) ? TOOLS[call.name] : null;
31288
31288
  if (!tool) {
31289
31289
  results[index] = { call_id: call.call_id, ok: false, error: `unknown tool ${call.name}` };
31290
31290
  continue;
@@ -31380,10 +31380,10 @@ function writerExcerpts(codeMap, writers, read2, maxLines, maxChars, budget) {
31380
31380
  if (content === null) continue;
31381
31381
  const win = functionWindow(codeMap, w.file, content, w.line, maxLines);
31382
31382
  if (!win) continue;
31383
- const text = content.split("\n").slice(win.start_line - 1, win.end_line).join("\n").slice(0, maxChars);
31384
- if (used2 + text.length > budget) break;
31385
- used2 += text.length;
31386
- out2.push({ file: w.file, start_line: win.start_line, symbol: `${w.symbol} (writes ${w.resource}, like ${w.changed})`, kind: "writer", text });
31383
+ const text2 = content.split("\n").slice(win.start_line - 1, win.end_line).join("\n").slice(0, maxChars);
31384
+ if (used2 + text2.length > budget) break;
31385
+ used2 += text2.length;
31386
+ out2.push({ file: w.file, start_line: win.start_line, symbol: `${w.symbol} (writes ${w.resource}, like ${w.changed})`, kind: "writer", text: text2 });
31387
31387
  }
31388
31388
  return out2;
31389
31389
  }
@@ -34056,14 +34056,14 @@ function trackedSourceFiles(root, opts = {}) {
34056
34056
  return files.slice(0, opts.maxFiles ?? 5e3);
34057
34057
  }
34058
34058
  var EFFECT_LINE = /\bawait\b|\basync\s+with\b|\.then\(|\bsetTimeout\(|\bsetInterval\(|\bwith\s+\w[\w.]*\.(?:lock|acquire)\b|\bLock\(\)/;
34059
- function countEffects(text) {
34059
+ function countEffects(text2) {
34060
34060
  let n2 = 0;
34061
- for (const line2 of text.split("\n")) if (EFFECT_LINE.test(line2)) n2++;
34061
+ for (const line2 of text2.split("\n")) if (EFFECT_LINE.test(line2)) n2++;
34062
34062
  return n2;
34063
34063
  }
34064
- function scanRisk(text) {
34064
+ function scanRisk(text2) {
34065
34065
  const counts = Object.fromEntries(RISK_PATTERNS.map(([c]) => [c, 0]));
34066
- for (const line2 of text.split("\n")) {
34066
+ for (const line2 of text2.split("\n")) {
34067
34067
  for (const [category, re] of RISK_PATTERNS) {
34068
34068
  if (re.test(line2)) counts[category]++;
34069
34069
  }
@@ -34141,7 +34141,7 @@ function collectRiskSignals(root, opts = {}) {
34141
34141
  }
34142
34142
 
34143
34143
  // src/lib/prove/grounding.ts
34144
- var BASIS_RULES = ["conservation", "once", "no-lost-update", "fail-closed", "progress"];
34144
+ var BASIS_RULES = ["conservation", "once", "no-lost-update", "fail-closed", "progress", "isolation", "revocation", "flow-binding", "event-order", "check-use"];
34145
34145
  var RULE = /^rule:([a-z-]+)$/;
34146
34146
  var LINE = /^(?:([^\s:]+):)?L?(\d{1,6})(?::\s*|\s+)(?:comment\s+)?([\s\S]+)$/;
34147
34147
  var norm = (s) => s.replace(/\s+/g, " ").trim().toLowerCase();
@@ -34192,6 +34192,19 @@ var STRUCTURAL_CATALOGUE = [
34192
34192
  "check_then_act_is_atomic",
34193
34193
  "no_double_effect"
34194
34194
  ];
34195
+ var SECURITY_CATALOGUE = [
34196
+ "limit_holds_under_concurrency",
34197
+ "single_use",
34198
+ "no_cross_subject_leak",
34199
+ "revocation_holds",
34200
+ "flow_binding",
34201
+ "event_order_holds",
34202
+ "fails_closed",
34203
+ "checked_value_is_used",
34204
+ "idempotency_scoped"
34205
+ ];
34206
+ var MAX_HYPOTHESES = 3;
34207
+ var CATALOGUE = [...STRUCTURAL_CATALOGUE, ...SECURITY_CATALOGUE];
34195
34208
  function combineScore(c) {
34196
34209
  const f = (x) => Math.max(0.01, Math.min(1, x));
34197
34210
  return Math.round(Math.pow(f(c.hazard), 0.45) * Math.pow(f(c.critical), 0.35) * Math.pow(f(c.error_prone), 0.2) * 1e3) / 1e3;
@@ -34294,9 +34307,11 @@ var LUNA_SCHEMA = {
34294
34307
  error_prone: { type: "number" },
34295
34308
  reasons: { type: "array", items: { type: "string" } },
34296
34309
  symbol: { type: "string" },
34297
- properties: { type: "array", items: { type: "string", enum: [...STRUCTURAL_CATALOGUE] } }
34310
+ properties: { type: "array", items: { type: "string", enum: [...STRUCTURAL_CATALOGUE, ...SECURITY_CATALOGUE] } },
34311
+ lens: { type: "string", enum: ["security", "reliability"] },
34312
+ hypotheses: { type: "array", items: { type: "string" } }
34298
34313
  },
34299
- required: ["file", "critical", "hazard", "error_prone", "reasons", "symbol", "properties"]
34314
+ required: ["file", "critical", "hazard", "error_prone", "reasons", "symbol", "properties", "lens", "hypotheses"]
34300
34315
  }
34301
34316
  }
34302
34317
  },
@@ -34309,7 +34324,9 @@ For each file you get its git-history signals (commits, fix commits), risk keywo
34309
34324
  - error_prone: fix commits in its history (the strongest evidence: this code has already been wrong), high churn, several branches or error paths, a concurrency primitive \u2014 a hazard nobody sees by reading the function once.
34310
34325
  Rate a criterion LOW (below 0.5) when it is missing: rendering, formatting, configuration, command registration, wiring, types, a single read, a single write with nothing before it, logging, or a function whose correctness is plain from reading it. A crash window alone (an external call, then a local write, where a re-run repairs the state and nothing is lost for good) is not enough for a high score. A model of a function longer than about 150 lines abstracts too much to find anything: name the smaller function inside it that holds the hazard, or score lower.
34311
34326
  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 \u2014 its hazard, what a mistake costs, its history \u2014 never about the change. Return every file you were given, exactly once.
34312
- 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, never the edit itself. A one-line change (a log line, a rename) to a function that meets all three still scores high: a latent bug in it is exactly what this is for. A large change to a function that misses one still scores low. An excerpt may open with "Functions this push changed:", from a parser: when it says a changed function "shares state with" another, judge the PAIR \u2014 a function that alone is a single write or an invalidation is error-prone when the other reads and repopulates, or also writes, the same state (a cache invalidated here and refilled there, two writers of one ledger or file); score it as that pair would be scored, but name ONE symbol \u2014 the changed function, never both ("a/b", "a and b" name nothing). "reads then writes" names a check-then-act the parser saw with no transaction or lock around it. "effects" lists the state the function reads and writes (a table, a file, a cache key, a queue, an outside service). "Fix commits that changed these lines" quotes the history of THIS code: each one is evidence it has been wrong before (error_prone), and its subject often says what a mistake cost (critical) \u2014 a subject about duplicates, lost writes, double charges or stuck jobs is a costly mistake that already happened.`;
34327
+ 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, never the edit itself. A one-line change (a log line, a rename) to a function that meets all three still scores high: a latent bug in it is exactly what this is for. A large change to a function that misses one still scores low. An excerpt may open with "Functions this push changed:", from a parser: when it says a changed function "shares state with" another, judge the PAIR \u2014 a function that alone is a single write or an invalidation is error-prone when the other reads and repopulates, or also writes, the same state (a cache invalidated here and refilled there, two writers of one ledger or file); score it as that pair would be scored, but name ONE symbol \u2014 the changed function, never both ("a/b", "a and b" name nothing). "reads then writes" names a check-then-act the parser saw with no transaction or lock around it. "effects" lists the state the function reads and writes (a table, a file, a cache key, a queue, an outside service). "Fix commits that changed these lines" quotes the history of THIS code: each one is evidence it has been wrong before (error_prone), and its subject often says what a mistake cost (critical) \u2014 a subject about duplicates, lost writes, double charges or stuck jobs is a costly mistake that already happened.
34328
+ SECURITY LENS. Also decide who can drive the hazard. Set lens to "security" when an attacker outside the trust boundary can drive it: another user, an anonymous caller, another tenant, a holder of a stolen or revoked credential, the sender of a replayed or delayed event, or the owner of a domain or URL the code fetches. Otherwise set lens to "reliability". For the security lens, critical includes what the attacker gains (money, data, rights, another user's or tenant's data, free service), and hazard includes the moves an attacker controls: many copies of one request at once; the steps of a multi-step flow skipped, repeated or reordered; a right revoked between two steps; an event duplicated, delayed or reordered; a dependency that fails, times out or is circuit-broken; state shared across requests, users or tenants (module variables, caches, fallback answers, idempotency keys); a value that changes between its check and its use (a DNS answer, a redirect, a path).
34329
+ HYPOTHESES. For the symbol you name, give up to 3 concrete hypotheses for the model to try to break. Each is one sentence about THIS code that says who acts, the two or more steps, and the harm (for example "two concurrent redeem requests by one user both pass the count check, so the credit is added twice"). When a file lists \`changed\` lines, at least one hypothesis is about what the change made possible. A hypothesis is a question for the model, not a verdict. Give none when you see no concrete one.`;
34313
34330
  var lunaRanker = {
34314
34331
  name: "luna",
34315
34332
  async rank(candidates2, ctx) {
@@ -34340,7 +34357,7 @@ var lunaRanker = {
34340
34357
  store: false,
34341
34358
  instructions: LUNA_INSTRUCTIONS,
34342
34359
  input: JSON.stringify({
34343
- catalogue: STRUCTURAL_CATALOGUE,
34360
+ catalogue: [...STRUCTURAL_CATALOGUE, ...SECURITY_CATALOGUE],
34344
34361
  ...ctx.hint ? { user_hint: `The user said what they want checked: "${ctx.hint.slice(0, 500)}". Score files and name symbols relevant to it higher; do not invent relevance.` } : {},
34345
34362
  files: payload
34346
34363
  }),
@@ -34357,13 +34374,13 @@ var lunaRanker = {
34357
34374
  signal: controller.signal
34358
34375
  });
34359
34376
  if (!res.ok) {
34360
- const text2 = await res.text().catch(() => "");
34361
- return { ok: false, ranker: "luna", error: `OpenAI ${res.status}: ${text2.slice(0, 200)}`, ranked: fallback };
34377
+ const text3 = await res.text().catch(() => "");
34378
+ return { ok: false, ranker: "luna", error: `OpenAI ${res.status}: ${text3.slice(0, 200)}`, ranked: fallback };
34362
34379
  }
34363
34380
  const data = await res.json();
34364
- const text = (data.output ?? []).flatMap((o) => o.content ?? []).find((c) => c.type === "output_text")?.text;
34365
- if (!text) return { ok: false, ranker: "luna", error: "no output_text in response", ranked: fallback };
34366
- const parsed = JSON.parse(text);
34381
+ const text2 = (data.output ?? []).flatMap((o) => o.content ?? []).find((c) => c.type === "output_text")?.text;
34382
+ if (!text2) return { ok: false, ranker: "luna", error: "no output_text in response", ranked: fallback };
34383
+ const parsed = JSON.parse(text2);
34367
34384
  const items = parsed.items.map((i2) => {
34368
34385
  const legacy = i2.critical === void 0 && i2.hazard === void 0 && i2.error_prone === void 0;
34369
34386
  const criteria = legacy ? { critical: Number(i2.score) || 0, hazard: Number(i2.score) || 0, error_prone: Number(i2.score) || 0 } : { critical: Number(i2.critical) || 0, hazard: Number(i2.hazard) || 0, error_prone: Number(i2.error_prone) || 0 };
@@ -34415,7 +34432,7 @@ function applyAnnotations(candidates2, items) {
34415
34432
  const annotated = candidates2.map((c) => {
34416
34433
  const a = byFile.get(c.file);
34417
34434
  if (!a) return { ...c, rank: 0 };
34418
- const properties = (a.properties ?? []).filter((p) => STRUCTURAL_CATALOGUE.includes(p));
34435
+ const properties = (a.properties ?? []).filter((p) => CATALOGUE.includes(p));
34419
34436
  const score = Math.max(0, Math.min(1, Number(a.score) || 0));
34420
34437
  const ai = { score, reasons: (a.reasons ?? []).slice(0, 3), properties };
34421
34438
  if (a.criteria && typeof a.criteria === "object") {
@@ -34424,6 +34441,9 @@ function applyAnnotations(candidates2, items) {
34424
34441
  }
34425
34442
  if (a.reasked) ai.reasked = true;
34426
34443
  if (a.symbol && a.symbol.trim()) ai.symbol = a.symbol.trim();
34444
+ if (a.lens === "security") ai.lens = "security";
34445
+ const hypotheses = (Array.isArray(a.hypotheses) ? a.hypotheses : []).filter((h) => typeof h === "string" && h.trim().length > 0).slice(0, MAX_HYPOTHESES).map((h) => h.trim().slice(0, 300));
34446
+ if (hypotheses.length) ai.hypotheses = hypotheses;
34427
34447
  return { ...c, rank: 0, ai };
34428
34448
  });
34429
34449
  annotated.sort((x, y) => {
@@ -34753,6 +34773,8 @@ var MAX_CO_ACCESSORS = 2;
34753
34773
  var MAX_HELPERS2 = 3;
34754
34774
  var MAX_CONTEXT_LINES = 120;
34755
34775
  var MAX_CALLERS2 = 3;
34776
+ var MAX_CHANGED_RANGES = 50;
34777
+ var MAX_HYPOTHESES2 = 3;
34756
34778
  var MAX_CALLER_LINES = 9;
34757
34779
  var MAX_HELPER_LINES = 80;
34758
34780
  var MAX_CONTEXT_BYTES = 48 * 1024;
@@ -34905,6 +34927,10 @@ def P1 : Property := { id := "parts_sum_to_total", kind := .structural, desc :=
34905
34927
  end Models.Split`;
34906
34928
  var RULES2 = `Write ONE Lean 4 model of the code below that could expose a real bug in it. Work in three steps.
34907
34929
 
34930
+ A PUSH. When the code below marks lines \`*|\`, this is the review of a push and those are the lines it changed. Look first for a bug the CHANGE introduced: what the changed lines made possible, removed or reordered \u2014 a check dropped, or moved after the write it guarded; a write moved before another; a new path that skips a dedup, a lock or a transaction; a new early return or exception that leaves state half-written; a new caller of shared state. Model the changed lines as actions (STEP 1). Among the properties (STEP 3), list FIRST those a trace through a changed line can break; a property that holds or fails the same without the change comes after them.
34931
+
34932
+ AN ATTACKER. When the input says SECURITY, model the environment as an ATTACKER, not as nature. The attacker controls: how many requests they send, when, and in which interleaving (many copies of the same request at once included); the order of their own steps in a multi-step flow (they may skip, repeat or reorder steps); which of their own accounts they use (they may hold several); the inputs they type, and the DNS of domains they own; and when they act relative to other users, to outages and to slow dependencies. The attacker does not control secrets, other users' credentials or the server code. A property counts only when the attacker can cause its violation and gains data, rights, money or service they must not get. The promises to check: limits, balances, counters, single-use tokens and "already done" checks hold for EVERY interleaving (\`rule:once\`, \`rule:no-lost-update\`); state shared across requests never lets one user or tenant read or act with another's data or rights (\`rule:isolation\`); a removed right is never kept or regained through a refresh, a cache or a copied claim (\`rule:revocation\`); every step of a multi-step flow is bound to the same subject and value an earlier step proved (\`rule:flow-binding\`); duplicated, late or reordered events end in the state of the order they were produced in (\`rule:event-order\`); a failing dependency yields deny, never another subject's answer (\`rule:fail-closed\`); the value checked is the value used (\`rule:check-use\`).
34933
+
34908
34934
  STEP 1 \u2014 Effects. The input lists the target's EFFECT SITES: awaits, calls on its dependencies, loops, try/catch, locks. Those are the only points where the real world can interleave, fail or crash. Each one that reads or writes shared state becomes an action, named after the real call and its line \u2014 "db.setBalance (L14)" \u2014 so a trace reads as a scenario someone can replay.
34909
34935
 
34910
34936
  STEP 2 \u2014 The environment's moves that exist in reality for THIS code, and only those:
@@ -34928,7 +34954,7 @@ STEP 3 \u2014 Properties: 2 to 4, in this order of priority:
34928
34954
  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.
34929
34955
  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.
34930
34956
  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). Fail-open means an ACCESS decision: running WITHOUT an optional check when the check itself is unavailable, and saying so (a warning, a log line), is a stated degradation, not a fail-open \u2014 never claim it must fail closed.
34931
- 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: where the code dedups, or where the SAME input can arrive twice \u2014 an input that carries its own identity, such as an event, message, delivery or request id, redelivered or retried \u2014 even when the code has no dedup at all; a missing dedup is the bug, not a reason to drop the property. Two calls whose input carries no such identity are two operations: reserving a seat twice or creating a record twice from two requests is what the caller asked for, not a double effect), \`rule:no-lost-update\`, \`rule:fail-closed\` (an error in an access decision never grants access; an operation that failed is never reported as done), \`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.
34957
+ 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: where the code dedups, or where the SAME input can arrive twice \u2014 an input that carries its own identity, such as an event, message, delivery or request id, redelivered or retried \u2014 even when the code has no dedup at all; a missing dedup is the bug, not a reason to drop the property. Two calls whose input carries no such identity are two operations: reserving a seat twice or creating a record twice from two requests is what the caller asked for, not a double effect), \`rule:no-lost-update\`, \`rule:fail-closed\` (an error in an access decision never grants access; an operation that failed is never reported as done), \`rule:progress\` (nothing stuck forever); and for an attacker (SECURITY) \`rule:isolation\`, \`rule:revocation\`, \`rule:flow-binding\`, \`rule:event-order\`, \`rule:check-use\` (AN ATTACKER says what each means). 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.
34932
34958
  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).
34933
34959
  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").
34934
34960
  A PURE computation (no effect sites) is checked with \`Verity.checkData\` on small concrete inputs, including the edge cases (exemplar 3).
@@ -34998,19 +35024,19 @@ var PURE_METHODS2 = /* @__PURE__ */ new Set([
34998
35024
  "test",
34999
35025
  "match"
35000
35026
  ]);
35001
- function callsOn(text, name2) {
35027
+ function callsOn(text2, name2) {
35002
35028
  const needle = `${name2}.`;
35003
- for (let i2 = text.indexOf(needle); i2 >= 0; i2 = text.indexOf(needle, i2 + 1)) {
35004
- if (i2 > 0 && IDENT_CHAR2.test(text[i2 - 1])) continue;
35005
- const call = CALL_AFTER_DOT.exec(text.slice(i2 + needle.length));
35029
+ for (let i2 = text2.indexOf(needle); i2 >= 0; i2 = text2.indexOf(needle, i2 + 1)) {
35030
+ if (i2 > 0 && IDENT_CHAR2.test(text2[i2 - 1])) continue;
35031
+ const call = CALL_AFTER_DOT.exec(text2.slice(i2 + needle.length));
35006
35032
  if (call && !PURE_METHODS2.has(call[1])) return true;
35007
35033
  }
35008
35034
  return false;
35009
35035
  }
35010
- function callsDirect(text, name2) {
35036
+ function callsDirect(text2, name2) {
35011
35037
  const needle = `${name2}(`;
35012
- for (let i2 = text.indexOf(needle); i2 >= 0; i2 = text.indexOf(needle, i2 + 1)) {
35013
- if (i2 > 0 && IDENT_CHAR2.test(text[i2 - 1])) continue;
35038
+ for (let i2 = text2.indexOf(needle); i2 >= 0; i2 = text2.indexOf(needle, i2 + 1)) {
35039
+ if (i2 > 0 && IDENT_CHAR2.test(text2[i2 - 1])) continue;
35014
35040
  return true;
35015
35041
  }
35016
35042
  return false;
@@ -35042,17 +35068,44 @@ function effectSites(numbered2) {
35042
35068
  }
35043
35069
  return out2;
35044
35070
  }
35071
+ function starChanged(numbered2, changed) {
35072
+ const isChanged = (n2) => !!changed?.some((r) => n2 >= r.from && n2 <= r.to);
35073
+ const starred = [];
35074
+ let total = 0;
35075
+ const text2 = numbered2.split("\n").map((raw) => {
35076
+ const m = NUMBERED_LINE.exec(raw);
35077
+ if (!m) return raw;
35078
+ total++;
35079
+ const n2 = Number(m[1]);
35080
+ if (!isChanged(n2)) return raw;
35081
+ starred.push(n2);
35082
+ return raw.replace("|", "*|");
35083
+ }).join("\n");
35084
+ const runs = [];
35085
+ for (let i2 = 0; i2 < starred.length; i2++) {
35086
+ let j = i2;
35087
+ while (j + 1 < starred.length && starred[j + 1] === starred[j] + 1) j++;
35088
+ runs.push(i2 === j ? `${starred[i2]}` : `${starred[i2]}\u2013${starred[j]}`);
35089
+ i2 = j;
35090
+ }
35091
+ return { text: text2, ranges: runs.join(", "), count: starred.length, total };
35092
+ }
35045
35093
  function renderGenerateInput(t, previous) {
35046
35094
  const parts2 = [];
35047
35095
  parts2.push(`# Target: ${t.file} \u2014 \`${t.symbol}\`${t.lines ? ` (lines ${t.lines.from}\u2013${t.lines.to})` : ""}`);
35048
35096
  parts2.push(`Header values: file := "${t.file}", symbol := "${t.symbol}", sha256 := "${t.sha256}", lines := "${t.lines ? `${t.lines.from}-${t.lines.to}` : ""}"`);
35049
35097
  if (t.reasons.length) parts2.push(`Why it was selected: ${t.reasons.join(" \xB7 ")}`);
35050
35098
  if (t.properties.length) parts2.push(`Suggested properties: ${t.properties.join(", ")}`);
35099
+ if (t.lens === "security") parts2.push("SECURITY: an attacker outside the trust boundary can drive this code. Follow AN ATTACKER.");
35100
+ if (t.hypotheses?.length) parts2.push(`Hypotheses from the selector (questions to try, not verdicts):
35101
+ ${t.hypotheses.slice(0, MAX_HYPOTHESES2).map((h) => ` - ${h}`).join("\n")}`);
35051
35102
  const numbered2 = t.numbered.slice(0, MAX_NUMBERED_BYTES);
35052
35103
  const sites = effectSites(numbered2);
35104
+ const push = starChanged(numbered2, t.changed);
35105
+ if (push.count > 0) parts2.push(`A push: it changed ${push.count} of these ${push.total} lines (${push.ranges}), marked \`*|\` below. Look first for a bug the change introduced.`);
35053
35106
  parts2.push("", sites.length ? `Effect sites (STEP 1 \u2014 where the real world can interleave, fail or crash):
35054
35107
  ${sites.map((s) => ` ${s}`).join("\n")}` : "Effect sites: none \u2014 a pure computation (STEP 3: Verity.checkData on concrete inputs).");
35055
- parts2.push("", "```", numbered2, "```");
35108
+ parts2.push("", "```", push.count > 0 ? push.text : numbered2, "```");
35056
35109
  if (t.effects?.length) {
35057
35110
  parts2.push("", `State this function touches (from the parser; "via" names the helper that does it):
35058
35111
  ${t.effects.slice(0, MAX_CONTEXT_EFFECTS).map((e) => ` ${e}`).join("\n")}`);
@@ -35166,11 +35219,11 @@ async function generateModel(target, opts) {
35166
35219
  return { ok: false, error: name2 === "TimeoutError" || name2 === "AbortError" ? "timed out" : err2.message };
35167
35220
  }
35168
35221
  const usage = data.usage ? { inputTokens: Number(data.usage.input_tokens ?? 0), outputTokens: Number(data.usage.output_tokens ?? 0) } : null;
35169
- const text = (data.output ?? []).flatMap((o) => o.content ?? []).find((c) => c.type === "output_text")?.text;
35170
- if (!text) return { ok: false, error: "no output_text in response", usage };
35222
+ const text2 = (data.output ?? []).flatMap((o) => o.content ?? []).find((c) => c.type === "output_text")?.text;
35223
+ if (!text2) return { ok: false, error: "no output_text in response", usage };
35171
35224
  let parsed;
35172
35225
  try {
35173
- parsed = JSON.parse(text);
35226
+ parsed = JSON.parse(text2);
35174
35227
  } catch {
35175
35228
  return { ok: false, error: "answer was not JSON", usage };
35176
35229
  }
@@ -35180,8 +35233,8 @@ async function generateModel(target, opts) {
35180
35233
  // src/lib/prove/replay.ts
35181
35234
  var MAX_SOURCE_BYTES = 96 * 1024;
35182
35235
  var MAX_MODEL_BYTES = 32 * 1024;
35183
- function numberLines(text, from = 1) {
35184
- return text.split("\n").map((l, i2) => `${String(i2 + from).padStart(5)}| ${l}`).join("\n");
35236
+ function numberLines(text2, from = 1) {
35237
+ return text2.split("\n").map((l, i2) => `${String(i2 + from).padStart(5)}| ${l}`).join("\n");
35185
35238
  }
35186
35239
  var PY_DECL = /^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(/;
35187
35240
  var TS_DECLS = [
@@ -35255,9 +35308,9 @@ function extractSymbolBody(source2, symbol, file, range) {
35255
35308
  }
35256
35309
  return null;
35257
35310
  }
35258
- function significantLines(text) {
35311
+ function significantLines(text2) {
35259
35312
  const out2 = [];
35260
- for (const raw of text.split("\n")) {
35313
+ for (const raw of text2.split("\n")) {
35261
35314
  const l = raw.trim();
35262
35315
  if (l.length < 12) continue;
35263
35316
  if (/^(\/\/|\/\*|\*|#)/.test(l)) continue;
@@ -35282,14 +35335,14 @@ function isTrackedInRoot(root, file) {
35282
35335
  function buildReplayBrief(root, report, property, modelSource, readSource) {
35283
35336
  const sources = (report.header?.abstracts ?? []).map((a) => {
35284
35337
  const read2 = readSource ? readSource(a.file, MAX_SOURCE_BYTES) : isTrackedInRoot(root, a.file) ? readRegularFileInRoot(root, a.file, MAX_SOURCE_BYTES) : { ok: false, reason: "not tracked by git \u2014 models may only abstract tracked source" };
35285
- const text = read2.ok ? read2.content : "";
35338
+ const text2 = read2.ok ? read2.content : "";
35286
35339
  return {
35287
35340
  file: a.file,
35288
35341
  symbol: a.symbol,
35289
- numbered: text ? numberLines(text) : `(could not read ${a.file}: ${read2.ok ? "" : read2.reason})`,
35290
- symbolBody: text ? extractSymbolBody(text, a.symbol, a.file, a.lines) : null,
35342
+ numbered: text2 ? numberLines(text2) : `(could not read ${a.file}: ${read2.ok ? "" : read2.reason})`,
35343
+ symbolBody: text2 ? extractSymbolBody(text2, a.symbol, a.file, a.lines) : null,
35291
35344
  lines: a.lines,
35292
- content: text
35345
+ content: text2
35293
35346
  };
35294
35347
  });
35295
35348
  return { model: report.model, property, modelSource: modelSource.slice(0, MAX_MODEL_BYTES), sources };
@@ -35371,7 +35424,7 @@ function parseReplayOutput(output) {
35371
35424
  let executedCalls = 0;
35372
35425
  const shellTexts = [];
35373
35426
  const outputTexts = [];
35374
- let text = null;
35427
+ let text2 = null;
35375
35428
  for (const item of output) {
35376
35429
  if (item.type === "shell_call") {
35377
35430
  shellCalls++;
@@ -35387,13 +35440,13 @@ function parseReplayOutput(output) {
35387
35440
  }
35388
35441
  } else if (item.type === "message" && Array.isArray(item.content)) {
35389
35442
  const t = item.content.find((c) => c.type === "output_text")?.text;
35390
- if (typeof t === "string") text = t;
35443
+ if (typeof t === "string") text2 = t;
35391
35444
  }
35392
35445
  }
35393
35446
  let claim = null;
35394
- if (text) {
35447
+ if (text2) {
35395
35448
  try {
35396
- const o = JSON.parse(text);
35449
+ const o = JSON.parse(text2);
35397
35450
  const verdict = o.verdict;
35398
35451
  if (verdict === "reproduced" || verdict === "diverged" || verdict === "unreplayable") {
35399
35452
  claim = {
@@ -36577,8 +36630,8 @@ function listModelFiles(root) {
36577
36630
  if (!(0, import_node_fs61.existsSync)(modelsAbs)) return [];
36578
36631
  return (0, import_node_fs61.readdirSync)(modelsAbs).filter((f) => isModelInRoot(modelsAbs, f)).sort().map((f) => (0, import_node_path48.join)(modelsAbs, f));
36579
36632
  }
36580
- function trimDiagnostics(text) {
36581
- return text.split("\n").filter((l) => !l.startsWith("trace:")).join("\n").trim().slice(0, 4e3);
36633
+ function trimDiagnostics(text2) {
36634
+ return text2.split("\n").filter((l) => !l.startsWith("trace:")).join("\n").trim().slice(0, 4e3);
36582
36635
  }
36583
36636
  function checkModel(tc, root, modelFile, opts = {}) {
36584
36637
  const proofsAbs = (0, import_node_path48.join)(root, PROOFS_DIR);
@@ -36646,33 +36699,70 @@ function writeResult(root, report) {
36646
36699
  function isCrashTrace(trace) {
36647
36700
  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));
36648
36701
  }
36702
+ function formalDisposition(f) {
36703
+ if (f.verdict !== "reproduced" || f.waived) return "none";
36704
+ const c = f.check;
36705
+ if (!c || c.outcome !== "answered") return "block";
36706
+ switch (c.verdict) {
36707
+ case "REAL":
36708
+ return c.introduced === "no" ? "report" : "block";
36709
+ case "REAL-LOW":
36710
+ return "report";
36711
+ case "PREVENTED":
36712
+ case "ARTIFACT":
36713
+ return "checked";
36714
+ default:
36715
+ return "block";
36716
+ }
36717
+ }
36718
+ var CAUTION = { block: 3, report: 2, checked: 1, none: 0 };
36719
+ function cautiousCheck(f, checks) {
36720
+ if (checks.length === 0) return void 0;
36721
+ let chosen = checks[0];
36722
+ for (const c of checks.slice(1)) if (CAUTION[formalDisposition({ ...f, check: c })] > CAUTION[formalDisposition({ ...f, check: chosen })]) chosen = c;
36723
+ return { ...chosen, votes: checks.map((c) => c.outcome === "answered" && c.verdict ? c.verdict : c.outcome), costUsd: checks.reduce((n2, c) => n2 + c.costUsd, 0), unpriced: checks.reduce((n2, c) => n2 + c.unpriced, 0) };
36724
+ }
36725
+ function checkLine(f) {
36726
+ const c = f.check;
36727
+ if (!c || c.outcome !== "answered" || !c.verdict) return "";
36728
+ const why = c.verdict === "PREVENTED" ? c.preventedBy || c.reason : c.reason;
36729
+ const from = c.trigger ? ` Reached from: ${c.trigger}.` : "";
36730
+ const pre = c.introduced === "no" ? " Already possible before this change." : "";
36731
+ const votes = c.votes && c.votes.length > 1 ? ` (${c.votes.length} checks: ${c.votes.join(", ")})` : "";
36732
+ return `Checked: ${c.verdict}${votes}.${from}${pre} ${why ?? ""}`.trim();
36733
+ }
36649
36734
  function mergeFormalFindings(response, outcome, pushedFiles) {
36650
- if (!outcome) return { blocking: 0 };
36735
+ if (!outcome) return { blocking: 0, reported: 0 };
36651
36736
  const norm3 = (p) => p.replace(/\\/g, "/");
36652
36737
  const pushed = new Set(pushedFiles.map(norm3));
36653
- const blocking = outcome.findings.filter((f) => f.verdict === "reproduced" && !f.waived && pushed.has(norm3(f.file)));
36654
- if (blocking.length === 0) return { blocking: 0 };
36738
+ const onPush = outcome.findings.filter((f) => pushed.has(norm3(f.file)));
36739
+ const blocking = onPush.filter((f) => formalDisposition(f) === "block");
36740
+ const reported = onPush.filter((f) => formalDisposition(f) === "report");
36741
+ if (blocking.length === 0 && reported.length === 0) return { blocking: 0, reported: 0 };
36655
36742
  const existing = Array.isArray(response.findings) ? response.findings : [];
36656
- response.findings = [
36657
- ...existing,
36658
- ...blocking.map((f) => ({
36743
+ const asFinding = (f, blocks) => {
36744
+ const checked = checkLine(f);
36745
+ return {
36659
36746
  pattern_id: "formal-counterexample",
36660
- severity: "high",
36661
- scope: "change",
36747
+ severity: blocks ? "high" : "medium",
36748
+ // A bug this change did not introduce is filed the way the gate files everything it will not block on.
36749
+ scope: f.check?.introduced === "no" && !blocks ? "pre-existing" : "change",
36662
36750
  file: f.file,
36663
36751
  line: outcome.target?.lines?.from ?? null,
36664
36752
  // Worded as the VIOLATION: the property text is what should hold, and
36665
36753
  // as a title it read backwards (e2e, 2026-09-26). A trace that needs a
36666
36754
  // crash between two writes is labelled: reproducing it means injecting
36667
36755
  // the crash, so it is a crash-consistency finding, not a race.
36668
- 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`,
36669
- description: terminalText(f.reason ?? "", 2e3),
36756
+ title: `Formal: ${terminalText(f.symbol, 128)} breaks "${terminalText(f.desc, 280)}"${isCrashTrace(f.trace) ? " after a crash (crash consistency)" : ""} \u2014 reproduced on the real code${f.check?.outcome === "answered" ? `, checked ${f.check.verdict}` : ""}`,
36757
+ description: terminalText([f.reason ?? "", checked].filter(Boolean).join(" "), 2e3),
36670
36758
  fix: { description: `Trace: ${terminalText(f.trace.join(" \u2192 ") || "(initial state)", 2e3)}. Replay: ${terminalText(f.reason ?? "reproduced in the container", 2e3)}` },
36671
36759
  source: "formal"
36672
- }))
36673
- ];
36674
- if (response.gate_decision !== "FAIL") response.gate_decision = "FAIL";
36675
- return { blocking: blocking.length };
36760
+ };
36761
+ };
36762
+ response.findings = [...existing, ...blocking.map((f) => asFinding(f, true)), ...reported.map((f) => asFinding(f, false))];
36763
+ if (blocking.length > 0) response.gate_decision = "FAIL";
36764
+ else if (response.gate_decision === "PASS") response.gate_decision = "WARN";
36765
+ return { blocking: blocking.length, reported: reported.length };
36676
36766
  }
36677
36767
 
36678
36768
  // src/lib/prove/scalpel.ts
@@ -36836,6 +36926,11 @@ function isEligible(s, largePush) {
36836
36926
  if (critical) return { ok: true, why: "domain-critical" };
36837
36927
  return { ok: false, why: "no effects, no risky shape, not domain-critical" };
36838
36928
  }
36929
+ function changedWithin(changed, lines) {
36930
+ if (!changed?.length) return [];
36931
+ const inside2 = lines ? changed.filter((r) => r.from <= lines.to && lines.from <= r.to).map((r) => ({ from: Math.max(r.from, lines.from), to: Math.min(r.to, lines.to) })) : changed;
36932
+ return inside2.slice(0, MAX_CHANGED_RANGES);
36933
+ }
36839
36934
  function intersects(a, ranges) {
36840
36935
  return ranges.some((b) => a.from <= b.to && b.from <= a.to);
36841
36936
  }
@@ -36909,6 +37004,8 @@ function bumpLedger(root) {
36909
37004
  }
36910
37005
  var REPLAY_RESERVE_MS = 45e3;
36911
37006
  var MAX_REPLAYS = 3;
37007
+ var MAX_CHECKS = 2;
37008
+ var CHECK_VOTES = 2;
36912
37009
  function isNetworkError(message) {
36913
37010
  return /Network error|fetch failed|ETIMEDOUT|ECONNRESET|ECONNREFUSED|EAI_AGAIN|socket hang up/i.test(message);
36914
37011
  }
@@ -36991,7 +37088,9 @@ async function runFormalPhase(input) {
36991
37088
  symbol: f.symbol,
36992
37089
  verdict: "reproduced",
36993
37090
  reason: f.waived ? `waived ${f.waived.at.slice(0, 10)}: ${f.waived.reason} (standing since ${f.at.slice(0, 10)}; the file has not changed)` : `${f.reason ?? "reproduced on the real code"} (standing since ${f.at.slice(0, 10)}; the file has not changed)`,
36994
- ...f.waived ? { waived: true } : {}
37091
+ ...f.waived ? { waived: true } : {},
37092
+ // Same bytes, same answer: the check is not paid for twice.
37093
+ ...f.check ? { check: { outcome: "answered", rounds: 0, costUsd: 0, unpriced: 0, durationMs: 0, ...f.check } } : {}
36995
37094
  });
36996
37095
  }
36997
37096
  }
@@ -37013,13 +37112,13 @@ async function runFormalPhase(input) {
37013
37112
  if (signals.length === 0) return done({ status: "skipped", skip: "no-source-files" });
37014
37113
  const largePush = signals.length >= LARGE_PUSH_FILES || files.length >= LARGE_PUSH_FILES * 2;
37015
37114
  const mapEligible = (file) => {
37016
- const changed = input.changedLines?.get(file);
37017
- if (!deps.codeMap || largePush || !changed?.length) return false;
37115
+ const changed2 = input.changedLines?.get(file);
37116
+ if (!deps.codeMap || largePush || !changed2?.length) return false;
37018
37117
  const read2 = readSrc(file, 96 * 1024);
37019
37118
  if (!read2.ok) return false;
37020
37119
  const outline = deps.codeMap.outline(file, read2.content);
37021
37120
  if (outline.source !== "tree-sitter") return false;
37022
- return outline.value.some((f) => changed.some((r) => f.start.line <= r.to && r.from <= f.end.line) && (() => {
37121
+ return outline.value.some((f) => changed2.some((r) => f.start.line <= r.to && r.from <= f.end.line) && (() => {
37023
37122
  const a = deps.codeMap.access(file, read2.content, f);
37024
37123
  return a.source === "tree-sitter" && a.value.effects.length >= 2;
37025
37124
  })());
@@ -37079,8 +37178,8 @@ async function runFormalPhase(input) {
37079
37178
  }
37080
37179
  if (d.resolved && d.resolved !== c.ai.symbol) c.ai.symbol = d.resolved;
37081
37180
  if (input.changedLines) {
37082
- const changed = input.changedLines.get(c.file) ?? [];
37083
- if (changed.length === 0) {
37181
+ const changed2 = input.changedLines.get(c.file) ?? [];
37182
+ if (changed2.length === 0) {
37084
37183
  note("symbol-not-in-push", `${c.file}: no changed lines in this push`);
37085
37184
  continue;
37086
37185
  }
@@ -37088,7 +37187,7 @@ async function runFormalPhase(input) {
37088
37187
  note("symbol-not-in-push", `${c.ai.symbol}: body not found, cannot tell whether the push touched it`);
37089
37188
  continue;
37090
37189
  }
37091
- if (!intersects(d.lines, changed)) {
37190
+ if (!intersects(d.lines, changed2)) {
37092
37191
  note("symbol-not-in-push", `${c.ai.symbol} (lines ${d.lines.from}\u2013${d.lines.to}) unchanged by this push`);
37093
37192
  continue;
37094
37193
  }
@@ -37146,7 +37245,8 @@ async function runFormalPhase(input) {
37146
37245
  if (!REPLAYABLE.test(top.file)) return done({ status: "skipped", skip: "replay-unsupported", detail: `${top.file}: replay supports TS/JS and Python`, target });
37147
37246
  if (timeLeft() < GENERATE_RESERVE_MS) return done({ status: "skipped", skip: "not-enough-time", detail: `${Math.round(timeLeft() / 1e3)} s left before generation`, target });
37148
37247
  bumpLedger(root);
37149
- const genTarget = { file: top.file, symbol: top.ai.symbol, lines, sha256: sha2562, numbered: numbered2, reasons: target.reasons, properties: top.ai.properties, ...chosen.context?.fields ?? {}, extentSource: chosen.extent };
37248
+ const changed = changedWithin(input.changedLines?.get(top.file), lines);
37249
+ const genTarget = { file: top.file, symbol: top.ai.symbol, lines, sha256: sha2562, numbered: numbered2, reasons: target.reasons, properties: top.ai.properties, ...chosen.context?.fields ?? {}, extentSource: chosen.extent, ...changed.length ? { changed } : {}, ...top.ai.lens === "security" ? { lens: "security" } : {}, ...top.ai.hypotheses?.length ? { hypotheses: top.ai.hypotheses } : {} };
37150
37250
  const headerFiles = /* @__PURE__ */ new Set([top.file, ...chosen.context?.files ?? []]);
37151
37251
  let previous;
37152
37252
  let report = null;
@@ -37267,13 +37367,40 @@ async function runFormalPhase(input) {
37267
37367
  const verdict = r.verdict === "reproduced" ? "reproduced" : r.verdict === "diverged" ? "diverged" : r.verdict === "unreplayable" ? "model-only" : "unverified";
37268
37368
  findings.push({ ...baseOf(p), verdict, reason: r.reason || r.downgrade || void 0 });
37269
37369
  }
37370
+ const toCheck = deps.verify && !cancelled() ? findings.filter((f) => f.verdict === "reproduced").slice(0, MAX_CHECKS) : [];
37371
+ if (toCheck.length > 0) {
37372
+ progress.step = "verify";
37373
+ const share = Math.max(0, costCap - usage.costUsd) / (toCheck.length * CHECK_VOTES);
37374
+ const failed = (error) => ({ outcome: "failed", verdict: null, introduced: "unknown", rounds: 0, costUsd: 0, unpriced: 0, durationMs: 0, error });
37375
+ await Promise.all(toCheck.map(async (f) => {
37376
+ const checks = await Promise.all(Array.from({ length: CHECK_VOTES }, () => deps.verify(f, { ...lines ? { lines } : {}, changedLines: input.changedLines?.get(f.file) ?? [], deadlineAt: input.deadlineAt, costLeftUsd: share }).catch((err2) => failed(String(err2?.message ?? err2).slice(0, 120)))));
37377
+ for (const c of checks) {
37378
+ if (c.costUsd > 0) spend({ costUsd: c.costUsd });
37379
+ for (let i2 = 0; i2 < c.unpriced; i2++) usage.unpriced++;
37380
+ }
37381
+ if (!cancelled()) f.check = cautiousCheck(f, checks);
37382
+ }));
37383
+ }
37270
37384
  const reproduced = findings.filter((f) => f.verdict === "reproduced");
37271
37385
  if (reproduced.length > 0 && !cancelled()) {
37272
37386
  const now = (/* @__PURE__ */ new Date()).toISOString();
37273
37387
  const others = readPersistedFindings(root).filter((f) => f.file !== top.file);
37274
37388
  writePersistedFindings(root, [
37275
37389
  ...others,
37276
- ...reproduced.map((f) => ({ file: f.file, symbol: f.symbol, sha256: sha2562, property: f.property, desc: f.desc, kind: f.kind, trace: f.trace, model: modelName, reason: f.reason, ...lines ? { lines } : {}, at: now }))
37390
+ ...reproduced.map((f) => ({
37391
+ file: f.file,
37392
+ symbol: f.symbol,
37393
+ sha256: sha2562,
37394
+ property: f.property,
37395
+ desc: f.desc,
37396
+ kind: f.kind,
37397
+ trace: f.trace,
37398
+ model: modelName,
37399
+ reason: f.reason,
37400
+ ...lines ? { lines } : {},
37401
+ at: now,
37402
+ ...f.check?.outcome === "answered" ? { check: { verdict: f.check.verdict, introduced: f.check.introduced, trigger: f.check.trigger, preventedBy: f.check.preventedBy, effect: f.check.effect, reason: f.check.reason, ...f.check.votes ? { votes: f.check.votes } : {} } } : {}
37403
+ }))
37277
37404
  ]);
37278
37405
  }
37279
37406
  const clean2 = findings.every((f) => f.verdict === "diverged");
@@ -37493,7 +37620,9 @@ function describeFormalOutcome(o) {
37493
37620
  if (o.status === "skipped") return `formal: skipped (${o.skip}${o.detail ? `: ${o.detail}` : ""})`;
37494
37621
  const parts2 = o.properties?.map((p) => {
37495
37622
  const f = o.findings.find((x) => x.property === p.id);
37496
- const tag = p.result === "counterexample" ? f ? f.verdict.toUpperCase() + (f.waived ? " (waived)" : "") : "counterexample" : p.result;
37623
+ const votes = f?.check?.votes && f.check.votes.length > 1 ? ` [${f.check.votes.join(", ")}]` : "";
37624
+ const checked = f?.check ? f.check.outcome === "answered" ? ` \u2192 checked ${f.check.verdict}${f.check.introduced === "no" ? " (pre-existing)" : ""}${votes}` : ` \u2192 not checked (${f.check.outcome})${votes}` : "";
37625
+ const tag = p.result === "counterexample" ? f ? f.verdict.toUpperCase() + (f.waived ? " (waived)" : "") + checked : "counterexample" : p.result;
37497
37626
  return `${p.id} ${tag}`;
37498
37627
  }) ?? [];
37499
37628
  const cost = o.usage.costUsd > 0 ? ` \xB7 $${o.usage.costUsd.toFixed(2)}` : "";
@@ -37501,6 +37630,105 @@ function describeFormalOutcome(o) {
37501
37630
  return `formal: ${o.model?.name ?? "?"} on ${o.target?.file ?? "?"}#${o.target?.symbol ?? "?"} (${o.model?.rounds ?? 1} round${o.model?.rounds === 1 ? "" : "s"}) \xB7 ${parts2.join(" \xB7 ")} \xB7 ${Math.round(o.durationMs / 1e3)} s${cost}${cut}`;
37502
37631
  }
37503
37632
 
37633
+ // src/lib/prove/verify.ts
37634
+ var MIN_VERIFY_TURN_MS = 15e3;
37635
+ var MIN_VERIFY_START_MS = 45e3;
37636
+ var VERIFY_TURN_HOLD_USD = 0.1;
37637
+ var MAX_TURNS2 = 14;
37638
+ var VERDICTS = ["REAL", "REAL-LOW", "PREVENTED", "ARTIFACT", "UNSURE"];
37639
+ var verdictOf = (v) => typeof v === "string" && VERDICTS.includes(v) ? v : null;
37640
+ var introducedOf = (v) => v === "yes" || v === "no" ? v : "unknown";
37641
+ var text = (v, n2) => typeof v === "string" ? v.slice(0, n2) : "";
37642
+ function verifyPost(auth3) {
37643
+ return async (body2, timeoutMs) => {
37644
+ const res = await apiRequest({
37645
+ method: "POST",
37646
+ path: "/prove/verify",
37647
+ serviceUrl: auth3.serviceUrl,
37648
+ token: auth3.token,
37649
+ // Streamed with a heartbeat, like generate and replay (decision 53).
37650
+ extraHeaders: { "X-Verity-Keepalive": "1" },
37651
+ body: body2,
37652
+ timeout: timeoutMs,
37653
+ cmd: "prove-verify"
37654
+ });
37655
+ return res.ok ? { ok: true, data: res.data ?? {} } : { ok: false, error: res.error };
37656
+ };
37657
+ }
37658
+ function wellFormedCalls(raw, offered) {
37659
+ const calls = [];
37660
+ for (const c of raw) {
37661
+ if (!c || typeof c !== "object" || Array.isArray(c)) return null;
37662
+ const o = c;
37663
+ if (typeof o.call_id !== "string" || o.call_id.length === 0 || o.call_id.length > 200) return null;
37664
+ if (typeof o.name !== "string" || !offered.includes(o.name)) return null;
37665
+ const args2 = o.arguments ?? {};
37666
+ if (!args2 || typeof args2 !== "object" || Array.isArray(args2)) return null;
37667
+ calls.push({ call_id: o.call_id, name: o.name, arguments: args2 });
37668
+ }
37669
+ return calls;
37670
+ }
37671
+ async function verifyFinding(input, opts) {
37672
+ const now = opts.now ?? Date.now;
37673
+ const startedAt = now();
37674
+ const remaining = () => opts.deadlineAt - now();
37675
+ let rounds = 0;
37676
+ let costUsd = 0;
37677
+ let unpriced = 0;
37678
+ const end = (outcome, extra = {}) => ({ outcome, verdict: null, introduced: "unknown", rounds, costUsd, unpriced, durationMs: now() - startedAt, ...extra });
37679
+ if (remaining() < MIN_VERIFY_START_MS) return end("deadline", { error: "not enough time left" });
37680
+ let body2 = {
37681
+ target: {
37682
+ file: input.file,
37683
+ symbol: input.symbol,
37684
+ ...input.lines ? { lines: input.lines } : {},
37685
+ property: input.property,
37686
+ replay_reason: input.replayReason,
37687
+ changed_lines: input.changedLines.slice(0, 200)
37688
+ },
37689
+ local_tools: opts.localTools
37690
+ };
37691
+ for (let turn = 0; turn < MAX_TURNS2; turn++) {
37692
+ if (remaining() < MIN_VERIFY_TURN_MS) return end("deadline");
37693
+ if (costUsd + VERIFY_TURN_HOLD_USD > opts.costLeftUsd) return end("cost-cap");
37694
+ const res = await opts.post({ ...body2, remaining_ms: remaining() }, Math.max(1, remaining()));
37695
+ if (!res.ok) return end("failed", { error: res.error.slice(0, 200) });
37696
+ const d = res.data;
37697
+ if (d.enabled === false) return end("disabled", { error: text(d.reason, 60) });
37698
+ if (d.error && typeof d.error === "object") return end("failed", { error: text(d.error.code, 60) || "SERVICE_ERROR" });
37699
+ rounds = typeof d.round === "number" && Number.isInteger(d.round) && d.round >= 0 ? d.round : rounds + 1;
37700
+ if (typeof d.cost_usd === "number") {
37701
+ if (!Number.isFinite(d.cost_usd) || d.cost_usd < 0) return end("failed", { error: "invalid cost" });
37702
+ costUsd += d.cost_usd;
37703
+ } else unpriced++;
37704
+ if (d.done) {
37705
+ const a = d.answer;
37706
+ if (!a) return end("failed", { error: text(d.error, 60) || "no verdict" });
37707
+ const verdict = verdictOf(a.verdict);
37708
+ if (!verdict) return end("failed", { error: "unknown verdict" });
37709
+ return end("answered", {
37710
+ verdict,
37711
+ claimed: verdictOf(a.claimed),
37712
+ introduced: introducedOf(a.introduced),
37713
+ trigger: text(a.trigger, 400),
37714
+ preventedBy: typeof a.prevented_by === "string" ? a.prevented_by.slice(0, 600) : null,
37715
+ effect: text(a.effect, 600),
37716
+ reason: text(a.reason, 2e3),
37717
+ quotes: (Array.isArray(a.quotes) ? a.quotes : []).slice(0, 12).flatMap((q) => {
37718
+ const o = q ?? {};
37719
+ return typeof o.path === "string" && typeof o.text === "string" ? [{ path: o.path.slice(0, 400), text: o.text.slice(0, 600), witnessed: o.witnessed === true }] : [];
37720
+ })
37721
+ });
37722
+ }
37723
+ if (typeof d.state !== "string" || !Array.isArray(d.calls)) return end("failed", { error: "malformed turn" });
37724
+ const calls = wellFormedCalls(d.calls, opts.localTools);
37725
+ if (!calls) return end("failed", { error: "malformed tool calls" });
37726
+ const results = await opts.executeTools(calls);
37727
+ body2 = { state: d.state, results };
37728
+ }
37729
+ return end("failed", { error: "too many turns" });
37730
+ }
37731
+
37504
37732
  // src/lib/prove/record.ts
37505
37733
  var RECORD_TIMEOUT_MS = 8e3;
37506
37734
  var HOOK_EXIT_MARGIN_MS = 5e3;
@@ -37541,7 +37769,7 @@ var kind = (k) => k === "intent" ? "intent" : "structural";
37541
37769
  var signalsOf = (c) => c && typeof c.commits === "number" && typeof c.fixCommits === "number" ? { commits: c.commits, fix_commits: c.fixCommits, risk: Object.fromEntries(Object.entries(c.risk ?? {}).filter(([, n2]) => typeof n2 === "number" && n2 > 0).slice(0, 16)) } : null;
37542
37770
  function buildPhaseRecord(outcome, ctx) {
37543
37771
  const pushed = new Set(ctx.pushedFiles.map(norm2));
37544
- const blocks = (f) => ctx.moment === "push" && f.verdict === "reproduced" && !f.waived && pushed.has(norm2(f.file));
37772
+ const blocks = (f) => ctx.moment === "push" && formalDisposition(f) === "block" && pushed.has(norm2(f.file));
37545
37773
  const symbol = (s) => ident2(s, SYMBOL, 200);
37546
37774
  return {
37547
37775
  moment: ctx.moment,
@@ -37581,7 +37809,9 @@ function buildPhaseRecord(outcome, ctx) {
37581
37809
  symbol: f.symbol,
37582
37810
  verdict: f.verdict,
37583
37811
  waived: f.waived === true,
37584
- blocked: blocks(f)
37812
+ blocked: blocks(f),
37813
+ // The check's closed codes only (decision 91): never its reason, trigger or quotes.
37814
+ check: f.check ? { outcome: f.check.outcome, verdict: f.check.verdict, introduced: f.check.introduced, rounds: f.check.rounds } : null
37585
37815
  })),
37586
37816
  grounding: outcome.grounding ? {
37587
37817
  sent_back: outcome.grounding.sentBack,
@@ -37763,17 +37993,17 @@ function parsePrIntent(command) {
37763
37993
  }
37764
37994
  var TRIVIAL_INTENT = /^(wip|tmp|temp|test|fix|chore|amend|\.+|fixup!.*|squash!.*|merge\b.*)$/i;
37765
37995
  var SHELL_PLUMBING = /\$\(|`|<<-?\s*['"]?[A-Za-z_]/;
37766
- function isSubstantiveIntent(text) {
37767
- if (!text) return false;
37768
- const t = text.trim();
37996
+ function isSubstantiveIntent(text2) {
37997
+ if (!text2) return false;
37998
+ const t = text2.trim();
37769
37999
  if (t.length < 12) return false;
37770
38000
  if (TRIVIAL_INTENT.test(t)) return false;
37771
38001
  if (SHELL_PLUMBING.test(t)) return false;
37772
38002
  return true;
37773
38003
  }
37774
38004
  function extractStatedIntent(moment, command, pushedMessages = null) {
37775
- const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? pushedMessages;
37776
- return isSubstantiveIntent(text) ? text : null;
38005
+ const text2 = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? pushedMessages;
38006
+ return isSubstantiveIntent(text2) ? text2 : null;
37777
38007
  }
37778
38008
  function hasBlockingFinding(response, sentFiles) {
37779
38009
  const findings = response.findings ?? [];
@@ -37976,6 +38206,14 @@ function startFormalPhase(input) {
37976
38206
  const run4 = async () => {
37977
38207
  const prep = await prepareFormalPhase(input.root, process.env, input.auth, readSource, withoutPaths(tip ? revisionSource(input.root, tip) : worktreeSource(input.root), isDeniedPath));
37978
38208
  if (!prep.ok) return skippedOutcome(prep.skip, prep.detail);
38209
+ if (process.env.VERITY_FORMAL_VERIFY !== "off") {
38210
+ const ctx = { root: input.root, range: input.range, codeMap: prep.deps.codeMap ?? null };
38211
+ const post = verifyPost(input.auth);
38212
+ prep.deps.verify = (f, v) => verifyFinding(
38213
+ { file: f.file, symbol: f.symbol, ...v.lines ? { lines: v.lines } : {}, property: { desc: f.desc, kind: f.kind, trace: f.trace }, replayReason: f.reason ?? "", changedLines: v.changedLines },
38214
+ { executeTools: makeToolExecutor(ctx), localTools: offeredLocalTools(ctx), deadlineAt: v.deadlineAt, costLeftUsd: v.costLeftUsd, post }
38215
+ );
38216
+ }
37979
38217
  return runFormalPhaseWithin({ root: input.root, files, changedLines, deadlineAt, deps: prep.deps });
37980
38218
  };
37981
38219
  return run4().catch((err2) => skippedOutcome("error", String(err2?.message ?? err2).slice(0, 200)));
@@ -38508,12 +38746,12 @@ function printBanner(opts) {
38508
38746
  const color = colorEnabled();
38509
38747
  const version = `v${cliVersion()}`;
38510
38748
  const art = [WORDMARK, WORDMARK_NARROW].find((a) => artWidth(a) <= columns) ?? null;
38511
- const dim = (text) => color ? `${DIM3}${text}${RESET2}` : text;
38749
+ const dim = (text2) => color ? `${DIM3}${text2}${RESET2}` : text2;
38512
38750
  process.stderr.write("\n");
38513
38751
  if (!art) {
38514
38752
  const candidates2 = [`Verity ${version}`, "Verity"];
38515
- const text = candidates2.find((c) => c.length + INDENT.length <= columns);
38516
- if (text) process.stderr.write(`${INDENT}${dim(text)}
38753
+ const text2 = candidates2.find((c) => c.length + INDENT.length <= columns);
38754
+ if (text2) process.stderr.write(`${INDENT}${dim(text2)}
38517
38755
  `);
38518
38756
  process.stderr.write("\n");
38519
38757
  return;
@@ -39297,7 +39535,7 @@ var DIM4 = "\x1B[2m";
39297
39535
  var BOLD2 = "\x1B[1m";
39298
39536
  var RESET3 = "\x1B[0m";
39299
39537
  function renderSelect(question, choices, state, mode2, color = colorEnabled()) {
39300
- const paint = (text, code) => color ? `${code}${text}${RESET3}` : text;
39538
+ const paint = (text2, code) => color ? `${code}${text2}${RESET3}` : text2;
39301
39539
  const lines = ["", ` ${question}`];
39302
39540
  const labelOf = (c) => `${c.label}${c.recommended ? " (recommended)" : ""}`;
39303
39541
  const gutter = Math.max(...choices.map((c) => labelOf(c).length));
@@ -40636,7 +40874,7 @@ function registerInitCommand(program2) {
40636
40874
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
40637
40875
  init: {
40638
40876
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
40639
- cli_version: true ? "0.34.3" : "dev"
40877
+ cli_version: true ? "0.34.4" : "dev"
40640
40878
  }
40641
40879
  });
40642
40880
  } catch (err2) {
@@ -41077,11 +41315,11 @@ function resolve7(token, treePaths) {
41077
41315
  const matches = treePaths.filter((p) => p.endsWith(suffix));
41078
41316
  return matches.length === 1 ? matches[0] : null;
41079
41317
  }
41080
- function deriveFileGlobs(text, treePaths) {
41081
- if (!text || treePaths.length === 0) return [];
41318
+ function deriveFileGlobs(text2, treePaths) {
41319
+ if (!text2 || treePaths.length === 0) return [];
41082
41320
  const out2 = [];
41083
41321
  const seen = /* @__PURE__ */ new Set();
41084
- for (const raw of text.match(PATH_TOKEN) ?? []) {
41322
+ for (const raw of text2.match(PATH_TOKEN) ?? []) {
41085
41323
  const token = clean(raw);
41086
41324
  if (!token) continue;
41087
41325
  const path = resolve7(token, treePaths);
@@ -42319,8 +42557,8 @@ function registerLeanCommand(program2) {
42319
42557
  }
42320
42558
 
42321
42559
  // src/cli.ts
42322
- program.name("verity").description("CLI for Verity quality gate service").version("0.34.3").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) => {
42323
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.3");
42560
+ program.name("verity").description("CLI for Verity quality gate service").version("0.34.4").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) => {
42561
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.4");
42324
42562
  setUserNamedServiceUrl(program.opts().serviceUrl);
42325
42563
  try {
42326
42564
  await foldLegacyLocalCredential();