@codacy/verity-cli 0.28.1-experimental.dfb3ce6 → 0.28.1-experimental.f9181dc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/verity.js +471 -95
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10389,7 +10389,6 @@ var MAX_DELTA_BYTES = 194560;
|
|
|
10389
10389
|
var MAX_FILES = 20;
|
|
10390
10390
|
var MAX_FILE_BYTES = 51200;
|
|
10391
10391
|
var DEBOUNCE_SECONDS = 30;
|
|
10392
|
-
var MAX_ITERATIONS = 2;
|
|
10393
10392
|
var MAX_SPEC_FILES = 10;
|
|
10394
10393
|
var MAX_SPEC_FILE_BYTES = 10240;
|
|
10395
10394
|
var MAX_TOTAL_SPEC_BYTES = 30720;
|
|
@@ -13591,6 +13590,70 @@ var import_node_fs10 = require("node:fs");
|
|
|
13591
13590
|
var import_node_crypto5 = require("node:crypto");
|
|
13592
13591
|
var import_node_path11 = require("node:path");
|
|
13593
13592
|
|
|
13593
|
+
// src/lib/skip-detection.ts
|
|
13594
|
+
function isBareAckPrompt(prompt) {
|
|
13595
|
+
if (typeof prompt !== "string") return false;
|
|
13596
|
+
const trimmed = prompt.trim();
|
|
13597
|
+
if (trimmed.length === 0) return false;
|
|
13598
|
+
if (trimmed.length > 20) return false;
|
|
13599
|
+
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
13600
|
+
return bareAckPattern.test(trimmed);
|
|
13601
|
+
}
|
|
13602
|
+
function isContinuationPrompt(prompt) {
|
|
13603
|
+
if (typeof prompt !== "string") return false;
|
|
13604
|
+
const trimmed = prompt.trim();
|
|
13605
|
+
if (trimmed.length === 0) return false;
|
|
13606
|
+
if (trimmed.length > 24) return false;
|
|
13607
|
+
const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
|
|
13608
|
+
return continuation.test(trimmed) || isBareAckPrompt(trimmed);
|
|
13609
|
+
}
|
|
13610
|
+
function resolveGoalPrompt(prompts) {
|
|
13611
|
+
if (prompts.length === 0) return null;
|
|
13612
|
+
const latest = prompts[prompts.length - 1];
|
|
13613
|
+
if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
|
|
13614
|
+
for (let i = prompts.length - 2; i >= 0; i--) {
|
|
13615
|
+
if (!isContinuationPrompt(prompts[i].prompt)) {
|
|
13616
|
+
return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
|
|
13617
|
+
}
|
|
13618
|
+
}
|
|
13619
|
+
return { entry: latest, turnsBack: 0 };
|
|
13620
|
+
}
|
|
13621
|
+
function isReflectionQuestion(response) {
|
|
13622
|
+
if (!response || typeof response !== "string") return false;
|
|
13623
|
+
const markers = [
|
|
13624
|
+
/reflection\s+for\s+future\s+agents/i,
|
|
13625
|
+
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
13626
|
+
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
13627
|
+
/quick\s+reflection\s+question/i,
|
|
13628
|
+
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
13629
|
+
// interactive, asks the user to confirm/correct before recording. That
|
|
13630
|
+
// turn authors no code either, so it's still a reflection turn.
|
|
13631
|
+
/reflection\s+draft/i,
|
|
13632
|
+
/confirm,?\s+correct,?\s+or\s+add/i
|
|
13633
|
+
];
|
|
13634
|
+
return markers.some((m) => m.test(response));
|
|
13635
|
+
}
|
|
13636
|
+
function isMetaTaskLabel(label2) {
|
|
13637
|
+
if (label2 === null || label2 === void 0) return false;
|
|
13638
|
+
if (typeof label2 !== "string") return false;
|
|
13639
|
+
const trimmed = label2.trim();
|
|
13640
|
+
if (trimmed.length === 0) return true;
|
|
13641
|
+
const metaPatterns = [
|
|
13642
|
+
/^verity\s+[\w-]+\s+response$/i,
|
|
13643
|
+
// "Verity reflect response"
|
|
13644
|
+
/^simple user response$/i,
|
|
13645
|
+
/^verity\s+command$/i,
|
|
13646
|
+
// "Verity command"
|
|
13647
|
+
/^user\s+(question|reply|response|ack)$/i
|
|
13648
|
+
];
|
|
13649
|
+
return metaPatterns.some((p) => p.test(trimmed));
|
|
13650
|
+
}
|
|
13651
|
+
function shouldSkipForBareAck(input) {
|
|
13652
|
+
if (!isBareAckPrompt(input.prompt)) return false;
|
|
13653
|
+
if (input.turnAuthoredCode) return false;
|
|
13654
|
+
return input.canSeeTurnAuthorship;
|
|
13655
|
+
}
|
|
13656
|
+
|
|
13594
13657
|
// src/lib/dossier.ts
|
|
13595
13658
|
var import_node_fs9 = require("node:fs");
|
|
13596
13659
|
var import_node_crypto4 = require("node:crypto");
|
|
@@ -14333,6 +14396,10 @@ function projectMemory(state, opts) {
|
|
|
14333
14396
|
...active.delivered && { delivered: active.delivered }
|
|
14334
14397
|
};
|
|
14335
14398
|
}
|
|
14399
|
+
if (opts.capture) {
|
|
14400
|
+
const missed = Math.max(0, opts.capture.seen - opts.capture.captured);
|
|
14401
|
+
p.capture = { seen: opts.capture.seen, captured: opts.capture.captured, missed };
|
|
14402
|
+
}
|
|
14336
14403
|
if (state.meta.last_adjudication) {
|
|
14337
14404
|
const a = state.meta.last_adjudication;
|
|
14338
14405
|
p.last_adjudication = { verdict: a.verdict, score: a.score, at: a.at };
|
|
@@ -14579,7 +14646,8 @@ function recall(d, input) {
|
|
|
14579
14646
|
continuity,
|
|
14580
14647
|
spoken: reanchored.spoken,
|
|
14581
14648
|
refused: reanchored.dropped.length,
|
|
14582
|
-
lastVerdictSeq
|
|
14649
|
+
lastVerdictSeq,
|
|
14650
|
+
...input.capture && { capture: input.capture }
|
|
14583
14651
|
});
|
|
14584
14652
|
return {
|
|
14585
14653
|
state: effective,
|
|
@@ -14690,7 +14758,19 @@ function sessionDossier(token, sessionId) {
|
|
|
14690
14758
|
const d = openDossier(identity);
|
|
14691
14759
|
return d ? { d, identity } : null;
|
|
14692
14760
|
}
|
|
14761
|
+
function hasActiveGoal(d) {
|
|
14762
|
+
try {
|
|
14763
|
+
if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return false;
|
|
14764
|
+
return (0, import_node_fs10.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
|
|
14765
|
+
} catch {
|
|
14766
|
+
return false;
|
|
14767
|
+
}
|
|
14768
|
+
}
|
|
14693
14769
|
function recordGoal(d, prompt, source = "prompt") {
|
|
14770
|
+
if (source === "prompt" && isContinuationPrompt(prompt) && hasActiveGoal(d)) {
|
|
14771
|
+
appendEvent(d, { k: "goal_continue", text: prompt.slice(0, 64) });
|
|
14772
|
+
return;
|
|
14773
|
+
}
|
|
14694
14774
|
const text = prompt.slice(0, MAX_GOAL_CHARS);
|
|
14695
14775
|
appendEvent(d, {
|
|
14696
14776
|
k: "goal",
|
|
@@ -14854,7 +14934,9 @@ function recallMemory(d, identity, opts) {
|
|
|
14854
14934
|
const state = foldDossier(d);
|
|
14855
14935
|
const watermark = state.meta.watermark?.sha ?? null;
|
|
14856
14936
|
const watermarkPaths = (state.authored ?? []).map((a) => a.path);
|
|
14937
|
+
const captureCmp = typeof opts.userMessagesSeen === "number" ? { seen: opts.userMessagesSeen, captured: state.meta.goal_chain } : void 0;
|
|
14857
14938
|
const r = recall(d, {
|
|
14939
|
+
...captureCmp && { capture: captureCmp },
|
|
14858
14940
|
identity,
|
|
14859
14941
|
currentSessionKey: opts.currentSessionKey,
|
|
14860
14942
|
branchNow: getCurrentBranch(),
|
|
@@ -15111,6 +15193,8 @@ function collectCodeDelta(files, opts) {
|
|
|
15111
15193
|
let totalSize = 0;
|
|
15112
15194
|
let truncationReason = null;
|
|
15113
15195
|
const droppedPaths = [];
|
|
15196
|
+
const excluded = [];
|
|
15197
|
+
const exclude = (path, reason) => excluded.push({ path, reason, stage: "collectCodeDelta", kind: "capacity" });
|
|
15114
15198
|
for (const filepath of sorted) {
|
|
15115
15199
|
if (result.length >= maxFiles) {
|
|
15116
15200
|
truncationReason ??= "max_files";
|
|
@@ -15118,14 +15202,21 @@ function collectCodeDelta(files, opts) {
|
|
|
15118
15202
|
continue;
|
|
15119
15203
|
}
|
|
15120
15204
|
const resolved = resolveFile(filepath);
|
|
15121
|
-
if (!resolved)
|
|
15205
|
+
if (!resolved) {
|
|
15206
|
+
exclude(filepath, "path-not-resolvable");
|
|
15207
|
+
continue;
|
|
15208
|
+
}
|
|
15122
15209
|
let size;
|
|
15123
15210
|
try {
|
|
15124
15211
|
size = (0, import_node_fs11.statSync)(resolved).size;
|
|
15125
15212
|
} catch {
|
|
15213
|
+
exclude(filepath, "not-stattable");
|
|
15214
|
+
continue;
|
|
15215
|
+
}
|
|
15216
|
+
if (size > maxFileBytes) {
|
|
15217
|
+
exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
|
|
15126
15218
|
continue;
|
|
15127
15219
|
}
|
|
15128
|
-
if (size > maxFileBytes) continue;
|
|
15129
15220
|
if (totalSize + size > maxTotalBytes) {
|
|
15130
15221
|
truncationReason ??= "max_total_bytes";
|
|
15131
15222
|
const idx = sorted.indexOf(filepath);
|
|
@@ -15136,6 +15227,7 @@ function collectCodeDelta(files, opts) {
|
|
|
15136
15227
|
try {
|
|
15137
15228
|
content = (0, import_node_fs11.readFileSync)(resolved, "utf-8");
|
|
15138
15229
|
} catch {
|
|
15230
|
+
exclude(filepath, "not-readable");
|
|
15139
15231
|
continue;
|
|
15140
15232
|
}
|
|
15141
15233
|
totalSize += size;
|
|
@@ -15149,10 +15241,14 @@ function collectCodeDelta(files, opts) {
|
|
|
15149
15241
|
(sum, f) => sum + f.content.split("\n").length,
|
|
15150
15242
|
0
|
|
15151
15243
|
);
|
|
15244
|
+
for (const path of droppedPaths) {
|
|
15245
|
+
exclude(path, truncationReason === "max_files" ? "max-files-cap" : "max-total-bytes-cap");
|
|
15246
|
+
}
|
|
15152
15247
|
return {
|
|
15153
15248
|
files: result,
|
|
15154
15249
|
total_lines: totalLines,
|
|
15155
15250
|
total_files: result.length,
|
|
15251
|
+
excluded,
|
|
15156
15252
|
...truncationReason && {
|
|
15157
15253
|
truncated: {
|
|
15158
15254
|
reason: truncationReason,
|
|
@@ -16322,40 +16418,40 @@ function narrowToRecent(files, sessionId) {
|
|
|
16322
16418
|
});
|
|
16323
16419
|
return recent.length > 0 ? recent : files;
|
|
16324
16420
|
}
|
|
16325
|
-
function
|
|
16326
|
-
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return 1;
|
|
16421
|
+
function readIterationState(currentCommit) {
|
|
16422
|
+
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
|
|
16327
16423
|
try {
|
|
16328
16424
|
const stored = (0, import_node_fs15.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16329
16425
|
const parts = stored.split(":");
|
|
16330
16426
|
const iter = parseInt(parts[0], 10);
|
|
16331
16427
|
const storedCommit = parts[1] ?? "";
|
|
16332
16428
|
const storedTimestamp = parseInt(parts[2] ?? "0", 10);
|
|
16333
|
-
|
|
16334
|
-
if (
|
|
16429
|
+
const fingerprint = parts.slice(3).join(":") || null;
|
|
16430
|
+
if (isNaN(iter)) return { iteration: 1, fingerprint: null };
|
|
16431
|
+
if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
|
|
16335
16432
|
if (storedTimestamp > 0) {
|
|
16336
16433
|
const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
|
|
16337
|
-
if (elapsed > 600) return 1;
|
|
16434
|
+
if (elapsed > 600) return { iteration: 1, fingerprint: null };
|
|
16338
16435
|
}
|
|
16339
|
-
return iter;
|
|
16436
|
+
return { iteration: iter, fingerprint };
|
|
16340
16437
|
} catch {
|
|
16341
|
-
return 1;
|
|
16438
|
+
return { iteration: 1, fingerprint: null };
|
|
16342
16439
|
}
|
|
16343
16440
|
}
|
|
16344
|
-
function
|
|
16345
|
-
const
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
}
|
|
16353
|
-
return { skip: null, iteration };
|
|
16441
|
+
function findingsFingerprint(findings) {
|
|
16442
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16443
|
+
return [...new Set(keys)].sort().join(",");
|
|
16444
|
+
}
|
|
16445
|
+
function isSameProblem(previous, current) {
|
|
16446
|
+
if (!previous || !current) return false;
|
|
16447
|
+
const prev = new Set(previous.split(","));
|
|
16448
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16354
16449
|
}
|
|
16355
|
-
function writeIteration(iteration, commit, _contentHash) {
|
|
16450
|
+
function writeIteration(iteration, commit, _contentHash, fingerprint) {
|
|
16356
16451
|
(0, import_node_fs15.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16357
16452
|
const ts = Math.floor(Date.now() / 1e3);
|
|
16358
|
-
|
|
16453
|
+
const fp = fingerprint ? `:${fingerprint}` : "";
|
|
16454
|
+
(0, import_node_fs15.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
|
|
16359
16455
|
}
|
|
16360
16456
|
|
|
16361
16457
|
// src/lib/static-analysis.ts
|
|
@@ -16604,7 +16700,7 @@ function resolveTaskContext(opts) {
|
|
|
16604
16700
|
// src/lib/cli-version.ts
|
|
16605
16701
|
function cliVersion() {
|
|
16606
16702
|
try {
|
|
16607
|
-
return true ? "0.28.1-experimental.
|
|
16703
|
+
return true ? "0.28.1-experimental.f9181dc" : "dev";
|
|
16608
16704
|
} catch {
|
|
16609
16705
|
return "dev";
|
|
16610
16706
|
}
|
|
@@ -16830,6 +16926,15 @@ var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
|
16830
16926
|
]);
|
|
16831
16927
|
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit", "MultiEdit"]);
|
|
16832
16928
|
var DISPATCH_TOOLS = /* @__PURE__ */ new Set(["Task", "Workflow"]);
|
|
16929
|
+
function hasUserText(record) {
|
|
16930
|
+
const message = record.message;
|
|
16931
|
+
const content = message?.content ?? record.content;
|
|
16932
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
16933
|
+
if (!Array.isArray(content)) return false;
|
|
16934
|
+
return content.some(
|
|
16935
|
+
(b) => b?.type === "text" && typeof b.text === "string" && b.text.trim().length > 0
|
|
16936
|
+
);
|
|
16937
|
+
}
|
|
16833
16938
|
var COMMAND_CLASSES = [
|
|
16834
16939
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?test\b|\bvitest\b|\bjest\b|\bpytest\b|\bgo test\b/, "test"],
|
|
16835
16940
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?build\b|\btsc\b|\bwebpack\b|\bcargo build\b/, "build"],
|
|
@@ -16957,6 +17062,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16957
17062
|
malformed: 0,
|
|
16958
17063
|
subagentFiles: 0,
|
|
16959
17064
|
dispatched: 0,
|
|
17065
|
+
userMessages: 0,
|
|
16960
17066
|
subagentSkipped: 0,
|
|
16961
17067
|
compactions: 0,
|
|
16962
17068
|
complete: false
|
|
@@ -16983,6 +17089,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16983
17089
|
if (type === "system" && record.subtype === "compact_boundary") {
|
|
16984
17090
|
result.coverage.compactions++;
|
|
16985
17091
|
}
|
|
17092
|
+
if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
|
|
16986
17093
|
collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot, result.coverage);
|
|
16987
17094
|
}
|
|
16988
17095
|
};
|
|
@@ -17138,6 +17245,86 @@ function checkConservation(changedFiles, result, repoRoot2) {
|
|
|
17138
17245
|
};
|
|
17139
17246
|
}
|
|
17140
17247
|
|
|
17248
|
+
// src/lib/verdict.ts
|
|
17249
|
+
function reconcileCoverage(changed, coverage) {
|
|
17250
|
+
const changedSet = new Set(changed);
|
|
17251
|
+
const reviewed = coverage.reviewed.filter((p) => changedSet.has(p));
|
|
17252
|
+
const claimed = /* @__PURE__ */ new Set([...reviewed, ...coverage.notReviewed.map((n) => n.path)]);
|
|
17253
|
+
const unaccounted = [...changedSet].filter((p) => !claimed.has(p)).sort();
|
|
17254
|
+
const notReviewed = [
|
|
17255
|
+
...coverage.notReviewed.filter((n) => changedSet.has(n.path)),
|
|
17256
|
+
...unaccounted.map((path) => ({
|
|
17257
|
+
path,
|
|
17258
|
+
reason: "unaccounted",
|
|
17259
|
+
// Named so the eventual bug report writes itself: some stage removed this
|
|
17260
|
+
// path and did not say so.
|
|
17261
|
+
stage: "unknown-stage",
|
|
17262
|
+
// An undeclared drop is CAPACITY by default. A stage that cannot be
|
|
17263
|
+
// bothered to say why it dropped a file does not get the benefit of the
|
|
17264
|
+
// doubt — that default is what makes forgetting expensive.
|
|
17265
|
+
kind: "capacity"
|
|
17266
|
+
}))
|
|
17267
|
+
];
|
|
17268
|
+
return {
|
|
17269
|
+
coverage: { reviewed: [...new Set(reviewed)].sort(), notReviewed },
|
|
17270
|
+
unaccounted,
|
|
17271
|
+
balances: unaccounted.length === 0
|
|
17272
|
+
};
|
|
17273
|
+
}
|
|
17274
|
+
function resolveVerdict(proposed, coverage) {
|
|
17275
|
+
if (proposed === "FAIL") return "FAIL";
|
|
17276
|
+
const blocking = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17277
|
+
if (blocking.length === 0) return proposed;
|
|
17278
|
+
return "WARN";
|
|
17279
|
+
}
|
|
17280
|
+
function describeCoverage(coverage, maxPaths = 5) {
|
|
17281
|
+
const relevant = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17282
|
+
if (relevant.length === 0) return null;
|
|
17283
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
17284
|
+
for (const n of relevant) {
|
|
17285
|
+
const key = `${n.reason}`;
|
|
17286
|
+
const list = byReason.get(key) ?? [];
|
|
17287
|
+
list.push(n.path);
|
|
17288
|
+
byReason.set(key, list);
|
|
17289
|
+
}
|
|
17290
|
+
const lines = [];
|
|
17291
|
+
for (const [reason, paths] of [...byReason.entries()].sort()) {
|
|
17292
|
+
const shown = paths.slice(0, maxPaths).join(", ");
|
|
17293
|
+
const more = paths.length > maxPaths ? ` (+${paths.length - maxPaths} more)` : "";
|
|
17294
|
+
lines.push(` ${paths.length} not reviewed \u2014 ${reason}: ${shown}${more}`);
|
|
17295
|
+
}
|
|
17296
|
+
return `NOT A CLEAN REVIEW. ${relevant.length} changed file(s) never reached the reviewer, so this verdict does not cover them:
|
|
17297
|
+
${lines.join("\n")}
|
|
17298
|
+
Treat those files as UNCHECKED, not as approved.`;
|
|
17299
|
+
}
|
|
17300
|
+
function openBlockingElsewhere(statements, reviewedNow, lineShaAt) {
|
|
17301
|
+
const reviewed = new Set(reviewedNow);
|
|
17302
|
+
const out = [];
|
|
17303
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17304
|
+
for (const s of statements) {
|
|
17305
|
+
if (s.outcome !== "open") continue;
|
|
17306
|
+
if (s.register !== "BLOCK") continue;
|
|
17307
|
+
if (s.carried) continue;
|
|
17308
|
+
if (reviewed.has(s.file)) continue;
|
|
17309
|
+
if (!s.line_sha) continue;
|
|
17310
|
+
if (lineShaAt(s.file, s.line) !== s.line_sha) continue;
|
|
17311
|
+
const key = `${s.file}::${s.pattern_id}`;
|
|
17312
|
+
if (seen.has(key)) continue;
|
|
17313
|
+
seen.add(key);
|
|
17314
|
+
out.push({ file: s.file, line: s.line, pattern_id: s.pattern_id });
|
|
17315
|
+
}
|
|
17316
|
+
return out;
|
|
17317
|
+
}
|
|
17318
|
+
function describeOpenElsewhere(open) {
|
|
17319
|
+
if (open.length === 0) return null;
|
|
17320
|
+
const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
|
|
17321
|
+
const more = open.length > 5 ? `
|
|
17322
|
+
(+${open.length - 5} more)` : "";
|
|
17323
|
+
return `STILL OPEN ELSEWHERE. ${open.length} blocking finding(s) Verity raised earlier are still present in files this run did not review:
|
|
17324
|
+
${lines.join("\n")}${more}
|
|
17325
|
+
This verdict covers the current change only. The tree is not clean.`;
|
|
17326
|
+
}
|
|
17327
|
+
|
|
17141
17328
|
// src/lib/channel.ts
|
|
17142
17329
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
17143
17330
|
var MAX_AGENT_ITEMS = 5;
|
|
@@ -17225,6 +17412,39 @@ function channelSilence(input) {
|
|
|
17225
17412
|
return null;
|
|
17226
17413
|
}
|
|
17227
17414
|
|
|
17415
|
+
// src/lib/emit.ts
|
|
17416
|
+
var YELLOW2 = "\x1B[33m";
|
|
17417
|
+
var NC2 = "\x1B[0m";
|
|
17418
|
+
function emitVerdict(input) {
|
|
17419
|
+
const exit = input.exit ?? ((code) => process.exit(code));
|
|
17420
|
+
const { coverage, unaccounted } = reconcileCoverage(input.changed, input.coverage);
|
|
17421
|
+
let verdict = resolveVerdict(input.proposed, coverage);
|
|
17422
|
+
const openElsewhere = input.openElsewhere ?? [];
|
|
17423
|
+
if (verdict === "PASS" && openElsewhere.length > 0) verdict = "WARN";
|
|
17424
|
+
const note = [describeCoverage(coverage), describeOpenElsewhere(openElsewhere)].filter(Boolean).join("\n\n") || null;
|
|
17425
|
+
if (unaccounted.length > 0) {
|
|
17426
|
+
process.stderr.write(
|
|
17427
|
+
`${YELLOW2}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
|
|
17428
|
+
`
|
|
17429
|
+
);
|
|
17430
|
+
}
|
|
17431
|
+
if (verdict === "FAIL") {
|
|
17432
|
+
input.renderBlocking?.();
|
|
17433
|
+
if (input.agentContext) {
|
|
17434
|
+
process.stderr.write(`
|
|
17435
|
+
${input.agentContext}
|
|
17436
|
+
`);
|
|
17437
|
+
}
|
|
17438
|
+
if (note && !input.silenced) process.stderr.write(`
|
|
17439
|
+
${YELLOW2}${note}${NC2}
|
|
17440
|
+
`);
|
|
17441
|
+
return exit(2);
|
|
17442
|
+
}
|
|
17443
|
+
const agentBlock = input.silenced ? null : [input.agentContext, note].filter(Boolean).join("\n\n") || null;
|
|
17444
|
+
printJsonCompact(buildHookOutput(verdict, input.userSummary, agentBlock));
|
|
17445
|
+
return exit(0);
|
|
17446
|
+
}
|
|
17447
|
+
|
|
17228
17448
|
// src/lib/cache-cleanup.ts
|
|
17229
17449
|
var import_node_fs21 = require("node:fs");
|
|
17230
17450
|
var import_node_path18 = require("node:path");
|
|
@@ -17404,46 +17624,6 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
17404
17624
|
return false;
|
|
17405
17625
|
}
|
|
17406
17626
|
|
|
17407
|
-
// src/lib/skip-detection.ts
|
|
17408
|
-
function isBareAckPrompt(prompt) {
|
|
17409
|
-
if (typeof prompt !== "string") return false;
|
|
17410
|
-
const trimmed = prompt.trim();
|
|
17411
|
-
if (trimmed.length === 0) return false;
|
|
17412
|
-
if (trimmed.length > 20) return false;
|
|
17413
|
-
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
17414
|
-
return bareAckPattern.test(trimmed);
|
|
17415
|
-
}
|
|
17416
|
-
function isReflectionQuestion(response) {
|
|
17417
|
-
if (!response || typeof response !== "string") return false;
|
|
17418
|
-
const markers = [
|
|
17419
|
-
/reflection\s+for\s+future\s+agents/i,
|
|
17420
|
-
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
17421
|
-
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
17422
|
-
/quick\s+reflection\s+question/i,
|
|
17423
|
-
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
17424
|
-
// interactive, asks the user to confirm/correct before recording. That
|
|
17425
|
-
// turn authors no code either, so it's still a reflection turn.
|
|
17426
|
-
/reflection\s+draft/i,
|
|
17427
|
-
/confirm,?\s+correct,?\s+or\s+add/i
|
|
17428
|
-
];
|
|
17429
|
-
return markers.some((m) => m.test(response));
|
|
17430
|
-
}
|
|
17431
|
-
function isMetaTaskLabel(label2) {
|
|
17432
|
-
if (label2 === null || label2 === void 0) return false;
|
|
17433
|
-
if (typeof label2 !== "string") return false;
|
|
17434
|
-
const trimmed = label2.trim();
|
|
17435
|
-
if (trimmed.length === 0) return true;
|
|
17436
|
-
const metaPatterns = [
|
|
17437
|
-
/^verity\s+[\w-]+\s+response$/i,
|
|
17438
|
-
// "Verity reflect response"
|
|
17439
|
-
/^simple user response$/i,
|
|
17440
|
-
/^verity\s+command$/i,
|
|
17441
|
-
// "Verity command"
|
|
17442
|
-
/^user\s+(question|reply|response|ack)$/i
|
|
17443
|
-
];
|
|
17444
|
-
return metaPatterns.some((p) => p.test(trimmed));
|
|
17445
|
-
}
|
|
17446
|
-
|
|
17447
17627
|
// src/lib/transcript.ts
|
|
17448
17628
|
var import_node_fs22 = require("node:fs");
|
|
17449
17629
|
var MAX_READ_BYTES = 256 * 1024;
|
|
@@ -17607,6 +17787,13 @@ function buildSummary(lines) {
|
|
|
17607
17787
|
files_read: capArray(filesRead, MAX_FILES_LIST),
|
|
17608
17788
|
files_edited: capArray(filesEdited, MAX_FILES_LIST),
|
|
17609
17789
|
files_created: capArray(filesCreated, MAX_CREATED_LIST),
|
|
17790
|
+
// The complement of the two caps that affect SCOPE. `files_read` is excluded
|
|
17791
|
+
// deliberately: reading a file is not authoring it, so a capped read list
|
|
17792
|
+
// narrows nothing.
|
|
17793
|
+
capped_out: [
|
|
17794
|
+
...cappedOut(filesEdited, MAX_FILES_LIST),
|
|
17795
|
+
...cappedOut(filesCreated, MAX_CREATED_LIST)
|
|
17796
|
+
],
|
|
17610
17797
|
searches,
|
|
17611
17798
|
commands,
|
|
17612
17799
|
subagents,
|
|
@@ -17657,6 +17844,9 @@ function sanitizeCommand(rawCmd) {
|
|
|
17657
17844
|
function capArray(set, max) {
|
|
17658
17845
|
return Array.from(set).slice(0, max);
|
|
17659
17846
|
}
|
|
17847
|
+
function cappedOut(set, max) {
|
|
17848
|
+
return Array.from(set).slice(max);
|
|
17849
|
+
}
|
|
17660
17850
|
|
|
17661
17851
|
// src/lib/run-mode.ts
|
|
17662
17852
|
function parseAutonomousEnv(raw) {
|
|
@@ -18066,12 +18256,45 @@ function agentContextFor(response, intentRepeat = 0) {
|
|
|
18066
18256
|
});
|
|
18067
18257
|
}
|
|
18068
18258
|
var beaconCtx = null;
|
|
18069
|
-
async function passAndExit(reason, skip) {
|
|
18259
|
+
async function passAndExit(reason, skip, kindOverride) {
|
|
18070
18260
|
const sent = await sendSkipBeacon(beaconCtx, skip);
|
|
18071
18261
|
logEvent("skip", { reason: skip, beacon: sent });
|
|
18072
|
-
|
|
18262
|
+
const POLICY_SKIPS = /* @__PURE__ */ new Set([
|
|
18263
|
+
"no-analyzable-files",
|
|
18264
|
+
"verity-command",
|
|
18265
|
+
"bare-acknowledgment",
|
|
18266
|
+
"reflection-prompt",
|
|
18267
|
+
"skip-mode",
|
|
18268
|
+
"zero-increment",
|
|
18269
|
+
"debounce",
|
|
18270
|
+
"no-delta-since-last-review"
|
|
18271
|
+
]);
|
|
18272
|
+
const skipKind = kindOverride ?? (POLICY_SKIPS.has(skip) ? "policy" : "capacity");
|
|
18273
|
+
const changed = skipCoverageChanged;
|
|
18274
|
+
const { coverage, unaccounted } = reconcileCoverage(changed, {
|
|
18275
|
+
reviewed: [],
|
|
18276
|
+
notReviewed: changed.map((path) => ({ path, reason: skip, stage: "pre-flight", kind: skipKind }))
|
|
18277
|
+
});
|
|
18278
|
+
const verdict = resolveVerdict("PASS", coverage);
|
|
18279
|
+
const note = describeCoverage(coverage);
|
|
18280
|
+
if (unaccounted.length > 0) {
|
|
18281
|
+
logEvent("coverage_unaccounted", { where: "passAndExit", skip, count: unaccounted.length });
|
|
18282
|
+
}
|
|
18283
|
+
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([]);
|
|
18284
|
+
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18285
|
+
printJsonCompact(
|
|
18286
|
+
buildHookOutput(
|
|
18287
|
+
verdict,
|
|
18288
|
+
`Verity: ${reason}`,
|
|
18289
|
+
// The agent's ONLY input is additionalContext. Sixteen of the nineteen
|
|
18290
|
+
// terminating paths wrote `systemMessage` — the human's field — and told
|
|
18291
|
+
// the agent nothing at all.
|
|
18292
|
+
agentNote
|
|
18293
|
+
)
|
|
18294
|
+
);
|
|
18073
18295
|
process.exit(0);
|
|
18074
18296
|
}
|
|
18297
|
+
var skipCoverageChanged = [];
|
|
18075
18298
|
var EMPTY_STATIC = {
|
|
18076
18299
|
tool: "@codacy/analysis-cli",
|
|
18077
18300
|
findings: [],
|
|
@@ -18086,7 +18309,7 @@ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
|
18086
18309
|
}
|
|
18087
18310
|
function localOnlyAndExit(staticResults) {
|
|
18088
18311
|
printJsonCompact({
|
|
18089
|
-
gate_decision: "
|
|
18312
|
+
gate_decision: "WARN",
|
|
18090
18313
|
systemMessage: "Verity: not authenticated \u2014 ran a local static-only check (no deep review, no upload). Run `verity init` to authenticate and enable the full quality gate.",
|
|
18091
18314
|
unauthenticated: true,
|
|
18092
18315
|
static_results: staticResults
|
|
@@ -18146,6 +18369,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18146
18369
|
});
|
|
18147
18370
|
}
|
|
18148
18371
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18372
|
+
skipCoverageChanged = allChanged;
|
|
18149
18373
|
const analyzable = filterAnalyzable(allChanged);
|
|
18150
18374
|
const reviewable = filterReviewable(allChanged);
|
|
18151
18375
|
const securityFiles = filterSecurity(allChanged);
|
|
@@ -18161,10 +18385,12 @@ async function runAnalyze(opts, globals) {
|
|
|
18161
18385
|
if (/^\s*\/verity-/i.test(latestPrompt)) {
|
|
18162
18386
|
await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
|
|
18163
18387
|
}
|
|
18164
|
-
|
|
18388
|
+
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18389
|
+
const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18390
|
+
const canSeeTurnAuthorship = !!actionSummary || !!baseline;
|
|
18391
|
+
if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
|
|
18165
18392
|
await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
|
|
18166
18393
|
}
|
|
18167
|
-
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18168
18394
|
if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
|
|
18169
18395
|
await passAndExit("Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
|
|
18170
18396
|
}
|
|
@@ -18221,7 +18447,8 @@ async function runAnalyze(opts, globals) {
|
|
|
18221
18447
|
let codeDelta = {
|
|
18222
18448
|
files: [],
|
|
18223
18449
|
total_lines: 0,
|
|
18224
|
-
total_files: 0
|
|
18450
|
+
total_files: 0,
|
|
18451
|
+
excluded: []
|
|
18225
18452
|
};
|
|
18226
18453
|
let snapshotResult = { has_snapshots: false, diffs: [] };
|
|
18227
18454
|
let contentHash = null;
|
|
@@ -18286,7 +18513,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18286
18513
|
if (assistantResponse) {
|
|
18287
18514
|
analysisMode = "plan";
|
|
18288
18515
|
} else {
|
|
18289
|
-
await passAndExit(
|
|
18516
|
+
await passAndExit(
|
|
18517
|
+
"No files within size limits to analyze",
|
|
18518
|
+
"size-limit",
|
|
18519
|
+
codeDelta.excluded.length > 0 ? "capacity" : "policy"
|
|
18520
|
+
);
|
|
18290
18521
|
}
|
|
18291
18522
|
}
|
|
18292
18523
|
}
|
|
@@ -18299,19 +18530,13 @@ async function runAnalyze(opts, globals) {
|
|
|
18299
18530
|
snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
18300
18531
|
}
|
|
18301
18532
|
currentCommit = getCurrentCommit();
|
|
18302
|
-
|
|
18303
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
18304
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18305
|
-
iteration = iterResult.iteration;
|
|
18533
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18306
18534
|
}
|
|
18307
18535
|
}
|
|
18308
18536
|
if (analysisMode === "plan") {
|
|
18309
18537
|
recordAnalysisStart();
|
|
18310
18538
|
currentCommit = getCurrentCommit();
|
|
18311
|
-
|
|
18312
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations);
|
|
18313
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18314
|
-
iteration = iterResult.iteration;
|
|
18539
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18315
18540
|
}
|
|
18316
18541
|
const contextFiles = gatherContextFiles(contextFilePaths, codeDelta.files);
|
|
18317
18542
|
for (const f of codeDelta.files) {
|
|
@@ -18460,7 +18685,10 @@ async function runAnalyze(opts, globals) {
|
|
|
18460
18685
|
priorState.capabilities
|
|
18461
18686
|
);
|
|
18462
18687
|
memory = recallMemory(memorySession.d, memorySession.identity, {
|
|
18463
|
-
currentSessionKey: memorySession.identity.sessionKey
|
|
18688
|
+
currentSessionKey: memorySession.identity.sessionKey,
|
|
18689
|
+
// The independent witness. Only meaningful when a transcript was folded —
|
|
18690
|
+
// otherwise it stays undefined and capture coverage reads as UNKNOWN.
|
|
18691
|
+
...foldResult && { userMessagesSeen: foldResult.coverage.userMessages }
|
|
18464
18692
|
});
|
|
18465
18693
|
if (memory && !memory.provenanceHolds) {
|
|
18466
18694
|
process.stderr.write("Verity: working-memory provenance check failed \u2014 recall suppressed.\n");
|
|
@@ -18599,7 +18827,20 @@ async function runAnalyze(opts, globals) {
|
|
|
18599
18827
|
const intentContext = {};
|
|
18600
18828
|
if (conversation && conversation.prompts.length > 0) {
|
|
18601
18829
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
18602
|
-
|
|
18830
|
+
const goalPrompt = resolveGoalPrompt(conversation.prompts) ?? { entry: latest, turnsBack: 0 };
|
|
18831
|
+
intentContext.user_prompt = goalPrompt.entry.prompt;
|
|
18832
|
+
if (isContinuationPrompt(intentContext.user_prompt)) {
|
|
18833
|
+
const carried = memory?.projection.goal?.text;
|
|
18834
|
+
if (carried && !isContinuationPrompt(carried)) {
|
|
18835
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
18836
|
+
intentContext.user_prompt = carried;
|
|
18837
|
+
logEvent("goal_from_dossier", { chars: carried.length });
|
|
18838
|
+
}
|
|
18839
|
+
}
|
|
18840
|
+
if (goalPrompt.turnsBack > 0) {
|
|
18841
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
18842
|
+
logEvent("goal_walked_back", { turns_back: goalPrompt.turnsBack });
|
|
18843
|
+
}
|
|
18603
18844
|
intentContext.session_id = latest.session_id || void 0;
|
|
18604
18845
|
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
18605
18846
|
if (conversation.prompts.length > 1) {
|
|
@@ -18691,6 +18932,85 @@ async function runAnalyze(opts, globals) {
|
|
|
18691
18932
|
const response = result.data;
|
|
18692
18933
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
18693
18934
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
18935
|
+
let openElsewhere = [];
|
|
18936
|
+
if (memorySession) {
|
|
18937
|
+
try {
|
|
18938
|
+
const st = foldDossier(memorySession.d);
|
|
18939
|
+
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
18940
|
+
try {
|
|
18941
|
+
const src = (0, import_node_fs24.readFileSync)((0, import_node_path20.join)(repoRoot(), file), "utf8").split("\n");
|
|
18942
|
+
const at = src[line - 1];
|
|
18943
|
+
return at === void 0 ? null : lineSha(at);
|
|
18944
|
+
} catch {
|
|
18945
|
+
return null;
|
|
18946
|
+
}
|
|
18947
|
+
});
|
|
18948
|
+
} catch {
|
|
18949
|
+
}
|
|
18950
|
+
}
|
|
18951
|
+
const reviewCoverage = {
|
|
18952
|
+
reviewed: sentPaths,
|
|
18953
|
+
// Declared drops from the stages that DO report themselves today. The other
|
|
18954
|
+
// stages surface via `unaccounted`, which is the tripwire, not the design.
|
|
18955
|
+
notReviewed: [
|
|
18956
|
+
// Every exit from the collection loop, each named. Six reasons where there
|
|
18957
|
+
// used to be two recorded and four silent — the silent ones including the
|
|
18958
|
+
// per-file size cap, which could drop a whole source file without leaving a
|
|
18959
|
+
// trace anywhere in the payload or the run row.
|
|
18960
|
+
...codeDelta.excluded,
|
|
18961
|
+
// The server-side 300-line middle-out truncation. It only bites on the
|
|
18962
|
+
// full-file branch (a first analysis, before snapshots exist) because
|
|
18963
|
+
// analyze normally sends diffs — but on that branch the reviewer sees the
|
|
18964
|
+
// first and last 100 lines and nothing between, and until now said so to
|
|
18965
|
+
// nobody. CAPACITY: a partial look is not a look.
|
|
18966
|
+
...(response.metadata?.truncated_files ?? []).map((path) => ({
|
|
18967
|
+
path,
|
|
18968
|
+
reason: "file-middle-truncated-300-lines",
|
|
18969
|
+
stage: "prompt-builder",
|
|
18970
|
+
kind: "capacity"
|
|
18971
|
+
})),
|
|
18972
|
+
// The 20-entry edit cap. CAPACITY, and the sharpest of the lot: it narrows
|
|
18973
|
+
// what is REVIEWED, not merely what is summarised — a session editing 25
|
|
18974
|
+
// files had five silently excluded from the reviewed set.
|
|
18975
|
+
...(actionSummary?.capped_out ?? []).map((path) => ({
|
|
18976
|
+
path,
|
|
18977
|
+
reason: "edit-list-cap-20",
|
|
18978
|
+
stage: "extractActionSummary",
|
|
18979
|
+
kind: "capacity"
|
|
18980
|
+
})),
|
|
18981
|
+
// ⚠ BASELINE SCOPING — the biggest source of false NOT A CLEAN REVIEW.
|
|
18982
|
+
//
|
|
18983
|
+
// The universe is `allChanged`, git's whole dirty tree. The reviewed set is
|
|
18984
|
+
// scoped to what THIS SESSION authored (the VRT-26 contamination cure), so
|
|
18985
|
+
// every pre-existing dirty file is in the universe, absent from `reviewed`,
|
|
18986
|
+
// and — until now — declared by nobody. It fell through to `unaccounted`,
|
|
18987
|
+
// became capacity, and produced "NOT A CLEAN REVIEW: admin.js" over a file
|
|
18988
|
+
// that was never this session's to review.
|
|
18989
|
+
//
|
|
18990
|
+
// Measured 2026-08-04: three consecutive runs over an untouched tree gave
|
|
18991
|
+
// three different answers — .claude/settings.json, then admin.js, then six
|
|
18992
|
+
// files — because each run took a different path and each path had a
|
|
18993
|
+
// different idea of the universe. POLICY: not this session's work is not a
|
|
18994
|
+
// coverage gap, it is the cure working.
|
|
18995
|
+
...allChanged.filter((p) => !sentPaths.includes(p) && !codeDelta.excluded.some((e) => e.path === p)).filter((p) => analyzable.includes(p) || reviewable.includes(p) || securityFiles.includes(p)).map((path) => ({
|
|
18996
|
+
path,
|
|
18997
|
+
reason: "not-authored-this-session",
|
|
18998
|
+
stage: "baseline-scoping",
|
|
18999
|
+
kind: "policy"
|
|
19000
|
+
})),
|
|
19001
|
+
// The extension allowlist, and it is POLICY rather than capacity: a changed
|
|
19002
|
+
// README was never going to be reviewed, and treating that as a coverage
|
|
19003
|
+
// gap would downgrade nearly every PASS to WARN until WARN meant nothing.
|
|
19004
|
+
// Recorded so the ledger balances and so "what did Verity ignore entirely"
|
|
19005
|
+
// is answerable — but it never touches the verdict.
|
|
19006
|
+
...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
|
|
19007
|
+
path,
|
|
19008
|
+
reason: "not-a-reviewed-file-type",
|
|
19009
|
+
stage: "extension-allowlist",
|
|
19010
|
+
kind: "policy"
|
|
19011
|
+
}))
|
|
19012
|
+
]
|
|
19013
|
+
};
|
|
18694
19014
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18695
19015
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
18696
19016
|
let silenced = null;
|
|
@@ -18843,9 +19163,41 @@ async function runAnalyze(opts, globals) {
|
|
|
18843
19163
|
reverify_by: response.reverify_by
|
|
18844
19164
|
});
|
|
18845
19165
|
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
18846
|
-
|
|
19166
|
+
let capReleased = false;
|
|
19167
|
+
let effectiveDecision = decision;
|
|
19168
|
+
if (decision === "FAIL") {
|
|
19169
|
+
const blocking = (response.findings ?? []).filter((f) => {
|
|
19170
|
+
const sev = String(f.severity ?? "").toLowerCase();
|
|
19171
|
+
return sev === "critical" || sev === "high";
|
|
19172
|
+
});
|
|
19173
|
+
const fingerprint = findingsFingerprint(blocking);
|
|
19174
|
+
const prior = readIterationState(currentCommit);
|
|
19175
|
+
const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
|
|
19176
|
+
const nextIteration = sameProblem ? prior.iteration + 1 : 1;
|
|
19177
|
+
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
19178
|
+
writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
|
|
19179
|
+
iteration = nextIteration;
|
|
19180
|
+
if (nextIteration > maxIterations) {
|
|
19181
|
+
capReleased = true;
|
|
19182
|
+
effectiveDecision = "WARN";
|
|
19183
|
+
logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
|
|
19184
|
+
}
|
|
19185
|
+
}
|
|
19186
|
+
if (capReleased) {
|
|
19187
|
+
const findings = response.findings ?? [];
|
|
19188
|
+
const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
|
|
19189
|
+
emitVerdict({
|
|
19190
|
+
proposed: "WARN",
|
|
19191
|
+
changed: skipCoverageChanged,
|
|
19192
|
+
coverage: reviewCoverage,
|
|
19193
|
+
userSummary: `Verity: WARN \u2014 self-healing limit (${opts.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${findings.length} finding(s) remain OPEN and were NOT fixed. Human review required before deploying.
|
|
19194
|
+
${lines.join("\n")}`,
|
|
19195
|
+
agentContext: null,
|
|
19196
|
+
silenced: true
|
|
19197
|
+
});
|
|
19198
|
+
}
|
|
19199
|
+
switch (effectiveDecision) {
|
|
18847
19200
|
case "FAIL": {
|
|
18848
|
-
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
18849
19201
|
const assessment = response.assessment;
|
|
18850
19202
|
const narrative = assessment?.narrative ?? "";
|
|
18851
19203
|
const findings = response.findings ?? [];
|
|
@@ -18922,7 +19274,19 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18922
19274
|
if (grantNudge) process.stderr.write(`
|
|
18923
19275
|
${YELLOW}${grantNudge.trim()}${NC}
|
|
18924
19276
|
`);
|
|
18925
|
-
|
|
19277
|
+
emitVerdict({
|
|
19278
|
+
proposed: "FAIL",
|
|
19279
|
+
changed: skipCoverageChanged,
|
|
19280
|
+
coverage: reviewCoverage,
|
|
19281
|
+
userSummary: "",
|
|
19282
|
+
// Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
|
|
19283
|
+
// the findings themselves are rendered above by the blocking renderer,
|
|
19284
|
+
// so what the cut removes is the repeated commentary, never the defect.
|
|
19285
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19286
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19287
|
+
silenced: !!silenced,
|
|
19288
|
+
openElsewhere
|
|
19289
|
+
});
|
|
18926
19290
|
break;
|
|
18927
19291
|
}
|
|
18928
19292
|
case "PASS": {
|
|
@@ -18934,10 +19298,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18934
19298
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18935
19299
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18936
19300
|
userSummary += loginNudge + grantNudge;
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
|
|
19301
|
+
emitVerdict({
|
|
19302
|
+
proposed: "PASS",
|
|
19303
|
+
changed: skipCoverageChanged,
|
|
19304
|
+
coverage: reviewCoverage,
|
|
19305
|
+
userSummary,
|
|
19306
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19307
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19308
|
+
silenced: !!silenced,
|
|
19309
|
+
openElsewhere
|
|
19310
|
+
});
|
|
18941
19311
|
break;
|
|
18942
19312
|
}
|
|
18943
19313
|
case "WARN": {
|
|
@@ -18948,10 +19318,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18948
19318
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18949
19319
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18950
19320
|
userSummary += loginNudge + grantNudge;
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
19321
|
+
emitVerdict({
|
|
19322
|
+
proposed: "WARN",
|
|
19323
|
+
changed: skipCoverageChanged,
|
|
19324
|
+
coverage: reviewCoverage,
|
|
19325
|
+
userSummary,
|
|
19326
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19327
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19328
|
+
silenced: !!silenced,
|
|
19329
|
+
openElsewhere
|
|
19330
|
+
});
|
|
18955
19331
|
break;
|
|
18956
19332
|
}
|
|
18957
19333
|
default: {
|
|
@@ -19064,8 +19440,8 @@ async function runReview(opts, globals) {
|
|
|
19064
19440
|
for (const p of specPaths) {
|
|
19065
19441
|
if (!(0, import_node_fs26.existsSync)(p)) continue;
|
|
19066
19442
|
try {
|
|
19067
|
-
const { readFileSync:
|
|
19068
|
-
const content =
|
|
19443
|
+
const { readFileSync: readFileSync16 } = await import("node:fs");
|
|
19444
|
+
const content = readFileSync16(p, "utf-8");
|
|
19069
19445
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
19070
19446
|
} catch {
|
|
19071
19447
|
}
|
|
@@ -20751,7 +21127,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20751
21127
|
}
|
|
20752
21128
|
|
|
20753
21129
|
// src/cli.ts
|
|
20754
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
21130
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.f9181dc").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
20755
21131
|
try {
|
|
20756
21132
|
await foldLegacyLocalCredential();
|
|
20757
21133
|
} catch {
|