@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.
- package/CHANGELOG.md +24 -0
- package/bin/verity.js +505 -267
- 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,
|
|
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
|
|
2941
|
-
helpStr =
|
|
2940
|
+
if (typeof text2 === "function") {
|
|
2941
|
+
helpStr = text2({ error: context.error, command: context.command });
|
|
2942
2942
|
} else {
|
|
2943
|
-
helpStr =
|
|
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(
|
|
3985
|
+
function foldFlowLines(text2, indent, mode2 = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
|
|
3986
3986
|
if (!lineWidth || lineWidth < 0)
|
|
3987
|
-
return
|
|
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 (
|
|
3992
|
-
return
|
|
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(
|
|
4009
|
+
i2 = consumeMoreIndentedLines(text2, i2, indent.length);
|
|
4010
4010
|
if (i2 !== -1)
|
|
4011
4011
|
end = i2 + endStep;
|
|
4012
4012
|
}
|
|
4013
|
-
for (let ch; ch =
|
|
4013
|
+
for (let ch; ch = text2[i2 += 1]; ) {
|
|
4014
4014
|
if (mode2 === FOLD_QUOTED && ch === "\\") {
|
|
4015
4015
|
escStart = i2;
|
|
4016
|
-
switch (
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
4070
|
+
return text2;
|
|
4071
4071
|
if (onFold)
|
|
4072
4072
|
onFold();
|
|
4073
|
-
let res =
|
|
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] ||
|
|
4076
|
+
const end2 = folds[i3 + 1] || text2.length;
|
|
4077
4077
|
if (fold2 === 0)
|
|
4078
4078
|
res = `
|
|
4079
|
-
${indent}${
|
|
4079
|
+
${indent}${text2.slice(0, end2)}`;
|
|
4080
4080
|
else {
|
|
4081
4081
|
if (mode2 === FOLD_QUOTED && escapedFolds[fold2])
|
|
4082
|
-
res += `${
|
|
4082
|
+
res += `${text2[fold2]}\\`;
|
|
4083
4083
|
res += `
|
|
4084
|
-
${indent}${
|
|
4084
|
+
${indent}${text2.slice(fold2 + 1, end2)}`;
|
|
4085
4085
|
}
|
|
4086
4086
|
}
|
|
4087
4087
|
return res;
|
|
4088
4088
|
}
|
|
4089
|
-
function consumeMoreIndentedLines(
|
|
4089
|
+
function consumeMoreIndentedLines(text2, i2, indent) {
|
|
4090
4090
|
let end = i2;
|
|
4091
4091
|
let start2 = i2 + 1;
|
|
4092
|
-
let ch =
|
|
4092
|
+
let ch = text2[start2];
|
|
4093
4093
|
while (ch === " " || ch === " ") {
|
|
4094
4094
|
if (i2 < start2 + indent) {
|
|
4095
|
-
ch =
|
|
4095
|
+
ch = text2[++i2];
|
|
4096
4096
|
} else {
|
|
4097
4097
|
do {
|
|
4098
|
-
ch =
|
|
4098
|
+
ch = text2[++i2];
|
|
4099
4099
|
} while (ch && ch !== "\n");
|
|
4100
4100
|
end = i2;
|
|
4101
4101
|
start2 = i2 + 1;
|
|
4102
|
-
ch =
|
|
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((
|
|
13886
|
-
return positive ? regex.test(
|
|
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((
|
|
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((
|
|
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
|
|
15715
|
+
const text2 = message ?? current;
|
|
15716
15716
|
const clock = showElapsed ? ` (${secondsSince(start2)})` : "";
|
|
15717
15717
|
stream.write(
|
|
15718
|
-
color ? ` ${GREEN2}\u2713${RESET} ${
|
|
15719
|
-
` : ` \u2713 ${
|
|
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(
|
|
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,
|
|
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
|
|
17475
|
+
const text2 = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : String(chunk);
|
|
17476
17476
|
ensureBanner();
|
|
17477
|
-
append(scrub(
|
|
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(
|
|
17484
|
+
function logToFileOnly(text2) {
|
|
17485
17485
|
if (!installed) return;
|
|
17486
17486
|
ensureBanner();
|
|
17487
|
-
append(
|
|
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(
|
|
18059
|
-
return
|
|
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(
|
|
18925
|
+
function tokenize(text2) {
|
|
18926
18926
|
return new Set(
|
|
18927
|
-
|
|
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(
|
|
19004
|
+
function orgTerms(text2) {
|
|
19005
19005
|
return new Set(
|
|
19006
|
-
|
|
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,
|
|
19289
|
+
function appendInRoot(root, name2, text2, hooks = {}) {
|
|
19290
19290
|
return writeVerified(root, name2, hooks, true, (fd) => {
|
|
19291
|
-
(0, import_node_fs17.writeSync)(fd,
|
|
19291
|
+
(0, import_node_fs17.writeSync)(fd, text2);
|
|
19292
19292
|
});
|
|
19293
19293
|
}
|
|
19294
|
-
function replaceInRoot(root, name2,
|
|
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,
|
|
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
|
|
19487
|
+
let text2 = next.join("\n");
|
|
19488
19488
|
if (!hasMarker) {
|
|
19489
|
-
const sep7 =
|
|
19490
|
-
|
|
19489
|
+
const sep7 = text2 === "" ? "" : text2.endsWith("\n") ? "\n" : "\n\n";
|
|
19490
|
+
text2 = text2 + sep7 + VERITY_GITIGNORE_BLOCK;
|
|
19491
19491
|
}
|
|
19492
|
-
if (
|
|
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
|
|
19518
|
+
let text2 = lines.join("\n");
|
|
19519
19519
|
if (!hasMarker) {
|
|
19520
|
-
const sep7 =
|
|
19521
|
-
|
|
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",
|
|
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(
|
|
20503
|
+
function* scanLines(text2) {
|
|
20504
20504
|
let offset = 0;
|
|
20505
20505
|
let inFence = false;
|
|
20506
|
-
for (const line2 of
|
|
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(
|
|
20514
|
-
for (const { line: line2, start: start2, inFence } of scanLines(
|
|
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(
|
|
20522
|
-
return findMarker(
|
|
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(
|
|
20723
|
-
if (!
|
|
20722
|
+
function pendingTokens(text2) {
|
|
20723
|
+
if (!text2 || typeof text2 !== "string") return [];
|
|
20724
20724
|
const out2 = /* @__PURE__ */ new Set();
|
|
20725
|
-
for (const raw of
|
|
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(
|
|
20741
|
-
const tokens = pendingTokens(
|
|
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(
|
|
21432
|
-
return (0, import_node_crypto8.createHash)("sha256").update(
|
|
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
|
|
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
|
|
23009
|
-
if (
|
|
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
|
|
23328
|
-
for (const m of
|
|
23329
|
-
for (const line2 of
|
|
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
|
|
23344
|
-
for (const m of
|
|
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
|
|
23355
|
-
for (const m of
|
|
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
|
|
25064
|
-
if (!
|
|
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 (
|
|
25084
|
-
await sendMessageArg(
|
|
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(
|
|
25689
|
+
function wordHit(text2, symbol) {
|
|
25690
25690
|
let from = 0;
|
|
25691
25691
|
for (; ; ) {
|
|
25692
|
-
const at =
|
|
25692
|
+
const at = text2.indexOf(symbol, from);
|
|
25693
25693
|
if (at < 0) return false;
|
|
25694
|
-
const before = at === 0 ? void 0 :
|
|
25695
|
-
const after =
|
|
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(
|
|
25700
|
+
function isContractLine(text2, symbol) {
|
|
25701
25701
|
for (const kw of ["implements", "extends"]) {
|
|
25702
25702
|
let from = 0;
|
|
25703
25703
|
for (; ; ) {
|
|
25704
|
-
const at =
|
|
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 :
|
|
25708
|
-
const after =
|
|
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 =
|
|
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
|
|
25743
|
-
if (
|
|
25744
|
-
if (COMMENT_LINE.test(
|
|
25745
|
-
if (BARE_MEMBER_LINE.test(
|
|
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:
|
|
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(
|
|
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(
|
|
25776
|
-
const m = /(?:\bfrom|\brequire\s*\(|\bimport\s*\(|^\s*import)\s*['"]([^'"\n]+)['"]/.exec(
|
|
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
|
|
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
|
|
27145
|
-
if (!node_id || !
|
|
27146
|
-
if (
|
|
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,
|
|
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}${
|
|
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
|
|
27273
|
-
if (!
|
|
27274
|
-
const seenBefore = isRepeatOfAny(
|
|
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("",
|
|
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(
|
|
27285
|
-
if (
|
|
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 =
|
|
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.
|
|
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(
|
|
29493
|
-
const t =
|
|
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
|
|
29528
|
-
if (!
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
29932
|
+
function wordAt(text2, word) {
|
|
29933
29933
|
const out2 = [];
|
|
29934
|
-
for (let i2 =
|
|
29935
|
-
if (IDENT_CHAR.test(
|
|
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(
|
|
29941
|
+
function typeAt(text2, i2, lang) {
|
|
29942
29942
|
let j = i2;
|
|
29943
29943
|
const skip = () => {
|
|
29944
|
-
while (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 (
|
|
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 <
|
|
29953
|
-
const name2 =
|
|
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(
|
|
30104
|
-
for (const i2 of wordAt(
|
|
30105
|
-
const lineEnd =
|
|
30106
|
-
const rest =
|
|
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(
|
|
30332
|
+
function parseJsonc(text2) {
|
|
30333
30333
|
let out2 = "";
|
|
30334
30334
|
let inString = false;
|
|
30335
|
-
for (let i2 = 0; i2 <
|
|
30336
|
-
const c =
|
|
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 +=
|
|
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 === "/" &&
|
|
30352
|
-
while (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 === "/" &&
|
|
30356
|
+
if (c === "/" && text2[i2 + 1] === "*") {
|
|
30357
30357
|
i2 += 2;
|
|
30358
|
-
while (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(
|
|
30567
|
+
function callStarts(text2, name2) {
|
|
30568
30568
|
const out2 = [];
|
|
30569
|
-
for (let i2 =
|
|
30569
|
+
for (let i2 = text2.indexOf(name2); i2 >= 0; i2 = text2.indexOf(name2, i2 + 1)) {
|
|
30570
30570
|
let j = i2 + name2.length;
|
|
30571
|
-
if (
|
|
30571
|
+
if (text2[j] === "<") {
|
|
30572
30572
|
let depth = 0;
|
|
30573
|
-
for (; j <
|
|
30574
|
-
if (
|
|
30575
|
-
else if (
|
|
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 (
|
|
30579
|
+
if (text2[j] === "(") out2.push({ index: i2, open: j });
|
|
30580
30580
|
}
|
|
30581
30581
|
return out2;
|
|
30582
30582
|
}
|
|
30583
|
-
function callsName(
|
|
30584
|
-
const t =
|
|
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(
|
|
30586
|
+
for (const at of callStarts(text2, name2)) {
|
|
30587
30587
|
const i2 = at.index;
|
|
30588
|
-
const spread =
|
|
30589
|
-
if (isWordChar2(
|
|
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 <
|
|
30593
|
-
if (
|
|
30594
|
-
else if (
|
|
30592
|
+
for (; j < text2.length; j++) {
|
|
30593
|
+
if (text2[j] === "(") depth++;
|
|
30594
|
+
else if (text2[j] === ")" && --depth === 0) break;
|
|
30595
30595
|
}
|
|
30596
|
-
const after =
|
|
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
|
|
30687
|
-
if (!callsName(
|
|
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:
|
|
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(
|
|
30998
|
-
const colon =
|
|
30999
|
-
const kind2 = colon > 0 ?
|
|
31000
|
-
if (KINDS.includes(kind2)) return { kinds: [kind2], name:
|
|
31001
|
-
return { kinds: KINDS, name:
|
|
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
|
|
31005
|
-
if (!
|
|
31006
|
-
const { kinds, name: name2 } = parseResource(
|
|
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: `${
|
|
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(
|
|
31104
|
-
return
|
|
31105
|
-
[truncated at ${TOOL_LIMITS.outputChars} characters]` :
|
|
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
|
|
31240
|
-
if (typeof
|
|
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",
|
|
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]
|
|
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
|
|
31384
|
-
if (used2 +
|
|
31385
|
-
used2 +=
|
|
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(
|
|
34059
|
+
function countEffects(text2) {
|
|
34060
34060
|
let n2 = 0;
|
|
34061
|
-
for (const line2 of
|
|
34061
|
+
for (const line2 of text2.split("\n")) if (EFFECT_LINE.test(line2)) n2++;
|
|
34062
34062
|
return n2;
|
|
34063
34063
|
}
|
|
34064
|
-
function scanRisk(
|
|
34064
|
+
function scanRisk(text2) {
|
|
34065
34065
|
const counts = Object.fromEntries(RISK_PATTERNS.map(([c]) => [c, 0]));
|
|
34066
|
-
for (const line2 of
|
|
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
|
|
34361
|
-
return { ok: false, ranker: "luna", error: `OpenAI ${res.status}: ${
|
|
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
|
|
34365
|
-
if (!
|
|
34366
|
-
const parsed = JSON.parse(
|
|
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) =>
|
|
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(
|
|
35027
|
+
function callsOn(text2, name2) {
|
|
35002
35028
|
const needle = `${name2}.`;
|
|
35003
|
-
for (let i2 =
|
|
35004
|
-
if (i2 > 0 && IDENT_CHAR2.test(
|
|
35005
|
-
const call = CALL_AFTER_DOT.exec(
|
|
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(
|
|
35036
|
+
function callsDirect(text2, name2) {
|
|
35011
35037
|
const needle = `${name2}(`;
|
|
35012
|
-
for (let i2 =
|
|
35013
|
-
if (i2 > 0 && IDENT_CHAR2.test(
|
|
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
|
|
35170
|
-
if (!
|
|
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(
|
|
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(
|
|
35184
|
-
return
|
|
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(
|
|
35311
|
+
function significantLines(text2) {
|
|
35259
35312
|
const out2 = [];
|
|
35260
|
-
for (const raw of
|
|
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
|
|
35338
|
+
const text2 = read2.ok ? read2.content : "";
|
|
35286
35339
|
return {
|
|
35287
35340
|
file: a.file,
|
|
35288
35341
|
symbol: a.symbol,
|
|
35289
|
-
numbered:
|
|
35290
|
-
symbolBody:
|
|
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:
|
|
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
|
|
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")
|
|
35443
|
+
if (typeof t === "string") text2 = t;
|
|
35391
35444
|
}
|
|
35392
35445
|
}
|
|
35393
35446
|
let claim = null;
|
|
35394
|
-
if (
|
|
35447
|
+
if (text2) {
|
|
35395
35448
|
try {
|
|
35396
|
-
const o = JSON.parse(
|
|
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(
|
|
36581
|
-
return
|
|
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
|
|
36654
|
-
|
|
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
|
-
|
|
36657
|
-
|
|
36658
|
-
|
|
36743
|
+
const asFinding = (f, blocks) => {
|
|
36744
|
+
const checked = checkLine(f);
|
|
36745
|
+
return {
|
|
36659
36746
|
pattern_id: "formal-counterexample",
|
|
36660
|
-
severity: "high",
|
|
36661
|
-
|
|
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
|
-
|
|
36675
|
-
|
|
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
|
|
37017
|
-
if (!deps.codeMap || largePush || !
|
|
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) =>
|
|
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
|
|
37083
|
-
if (
|
|
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,
|
|
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
|
|
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) => ({
|
|
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
|
|
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
|
|
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(
|
|
37767
|
-
if (!
|
|
37768
|
-
const t =
|
|
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
|
|
37776
|
-
return isSubstantiveIntent(
|
|
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 = (
|
|
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
|
|
38516
|
-
if (
|
|
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 = (
|
|
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.
|
|
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(
|
|
41081
|
-
if (!
|
|
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
|
|
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.
|
|
42323
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.34.
|
|
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();
|