@codacy/verity-cli 0.28.1-experimental.055cd97 → 0.28.1-experimental.39013ce
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 +385 -63
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -13591,6 +13591,70 @@ var import_node_fs10 = require("node:fs");
|
|
|
13591
13591
|
var import_node_crypto5 = require("node:crypto");
|
|
13592
13592
|
var import_node_path11 = require("node:path");
|
|
13593
13593
|
|
|
13594
|
+
// src/lib/skip-detection.ts
|
|
13595
|
+
function isBareAckPrompt(prompt) {
|
|
13596
|
+
if (typeof prompt !== "string") return false;
|
|
13597
|
+
const trimmed = prompt.trim();
|
|
13598
|
+
if (trimmed.length === 0) return false;
|
|
13599
|
+
if (trimmed.length > 20) return false;
|
|
13600
|
+
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;
|
|
13601
|
+
return bareAckPattern.test(trimmed);
|
|
13602
|
+
}
|
|
13603
|
+
function isContinuationPrompt(prompt) {
|
|
13604
|
+
if (typeof prompt !== "string") return false;
|
|
13605
|
+
const trimmed = prompt.trim();
|
|
13606
|
+
if (trimmed.length === 0) return false;
|
|
13607
|
+
if (trimmed.length > 24) return false;
|
|
13608
|
+
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;
|
|
13609
|
+
return continuation.test(trimmed) || isBareAckPrompt(trimmed);
|
|
13610
|
+
}
|
|
13611
|
+
function resolveGoalPrompt(prompts) {
|
|
13612
|
+
if (prompts.length === 0) return null;
|
|
13613
|
+
const latest = prompts[prompts.length - 1];
|
|
13614
|
+
if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
|
|
13615
|
+
for (let i = prompts.length - 2; i >= 0; i--) {
|
|
13616
|
+
if (!isContinuationPrompt(prompts[i].prompt)) {
|
|
13617
|
+
return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
|
|
13618
|
+
}
|
|
13619
|
+
}
|
|
13620
|
+
return { entry: latest, turnsBack: 0 };
|
|
13621
|
+
}
|
|
13622
|
+
function isReflectionQuestion(response) {
|
|
13623
|
+
if (!response || typeof response !== "string") return false;
|
|
13624
|
+
const markers = [
|
|
13625
|
+
/reflection\s+for\s+future\s+agents/i,
|
|
13626
|
+
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
13627
|
+
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
13628
|
+
/quick\s+reflection\s+question/i,
|
|
13629
|
+
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
13630
|
+
// interactive, asks the user to confirm/correct before recording. That
|
|
13631
|
+
// turn authors no code either, so it's still a reflection turn.
|
|
13632
|
+
/reflection\s+draft/i,
|
|
13633
|
+
/confirm,?\s+correct,?\s+or\s+add/i
|
|
13634
|
+
];
|
|
13635
|
+
return markers.some((m) => m.test(response));
|
|
13636
|
+
}
|
|
13637
|
+
function isMetaTaskLabel(label2) {
|
|
13638
|
+
if (label2 === null || label2 === void 0) return false;
|
|
13639
|
+
if (typeof label2 !== "string") return false;
|
|
13640
|
+
const trimmed = label2.trim();
|
|
13641
|
+
if (trimmed.length === 0) return true;
|
|
13642
|
+
const metaPatterns = [
|
|
13643
|
+
/^verity\s+[\w-]+\s+response$/i,
|
|
13644
|
+
// "Verity reflect response"
|
|
13645
|
+
/^simple user response$/i,
|
|
13646
|
+
/^verity\s+command$/i,
|
|
13647
|
+
// "Verity command"
|
|
13648
|
+
/^user\s+(question|reply|response|ack)$/i
|
|
13649
|
+
];
|
|
13650
|
+
return metaPatterns.some((p) => p.test(trimmed));
|
|
13651
|
+
}
|
|
13652
|
+
function shouldSkipForBareAck(input) {
|
|
13653
|
+
if (!isBareAckPrompt(input.prompt)) return false;
|
|
13654
|
+
if (input.turnAuthoredCode) return false;
|
|
13655
|
+
return input.canSeeTurnAuthorship;
|
|
13656
|
+
}
|
|
13657
|
+
|
|
13594
13658
|
// src/lib/dossier.ts
|
|
13595
13659
|
var import_node_fs9 = require("node:fs");
|
|
13596
13660
|
var import_node_crypto4 = require("node:crypto");
|
|
@@ -14695,7 +14759,19 @@ function sessionDossier(token, sessionId) {
|
|
|
14695
14759
|
const d = openDossier(identity);
|
|
14696
14760
|
return d ? { d, identity } : null;
|
|
14697
14761
|
}
|
|
14762
|
+
function hasActiveGoal(d) {
|
|
14763
|
+
try {
|
|
14764
|
+
if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return false;
|
|
14765
|
+
return (0, import_node_fs10.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
|
|
14766
|
+
} catch {
|
|
14767
|
+
return false;
|
|
14768
|
+
}
|
|
14769
|
+
}
|
|
14698
14770
|
function recordGoal(d, prompt, source = "prompt") {
|
|
14771
|
+
if (source === "prompt" && isContinuationPrompt(prompt) && hasActiveGoal(d)) {
|
|
14772
|
+
appendEvent(d, { k: "goal_continue", text: prompt.slice(0, 64) });
|
|
14773
|
+
return;
|
|
14774
|
+
}
|
|
14699
14775
|
const text = prompt.slice(0, MAX_GOAL_CHARS);
|
|
14700
14776
|
appendEvent(d, {
|
|
14701
14777
|
k: "goal",
|
|
@@ -15118,6 +15194,8 @@ function collectCodeDelta(files, opts) {
|
|
|
15118
15194
|
let totalSize = 0;
|
|
15119
15195
|
let truncationReason = null;
|
|
15120
15196
|
const droppedPaths = [];
|
|
15197
|
+
const excluded = [];
|
|
15198
|
+
const exclude = (path, reason) => excluded.push({ path, reason, stage: "collectCodeDelta", kind: "capacity" });
|
|
15121
15199
|
for (const filepath of sorted) {
|
|
15122
15200
|
if (result.length >= maxFiles) {
|
|
15123
15201
|
truncationReason ??= "max_files";
|
|
@@ -15125,14 +15203,21 @@ function collectCodeDelta(files, opts) {
|
|
|
15125
15203
|
continue;
|
|
15126
15204
|
}
|
|
15127
15205
|
const resolved = resolveFile(filepath);
|
|
15128
|
-
if (!resolved)
|
|
15206
|
+
if (!resolved) {
|
|
15207
|
+
exclude(filepath, "path-not-resolvable");
|
|
15208
|
+
continue;
|
|
15209
|
+
}
|
|
15129
15210
|
let size;
|
|
15130
15211
|
try {
|
|
15131
15212
|
size = (0, import_node_fs11.statSync)(resolved).size;
|
|
15132
15213
|
} catch {
|
|
15214
|
+
exclude(filepath, "not-stattable");
|
|
15215
|
+
continue;
|
|
15216
|
+
}
|
|
15217
|
+
if (size > maxFileBytes) {
|
|
15218
|
+
exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
|
|
15133
15219
|
continue;
|
|
15134
15220
|
}
|
|
15135
|
-
if (size > maxFileBytes) continue;
|
|
15136
15221
|
if (totalSize + size > maxTotalBytes) {
|
|
15137
15222
|
truncationReason ??= "max_total_bytes";
|
|
15138
15223
|
const idx = sorted.indexOf(filepath);
|
|
@@ -15143,6 +15228,7 @@ function collectCodeDelta(files, opts) {
|
|
|
15143
15228
|
try {
|
|
15144
15229
|
content = (0, import_node_fs11.readFileSync)(resolved, "utf-8");
|
|
15145
15230
|
} catch {
|
|
15231
|
+
exclude(filepath, "not-readable");
|
|
15146
15232
|
continue;
|
|
15147
15233
|
}
|
|
15148
15234
|
totalSize += size;
|
|
@@ -15156,10 +15242,14 @@ function collectCodeDelta(files, opts) {
|
|
|
15156
15242
|
(sum, f) => sum + f.content.split("\n").length,
|
|
15157
15243
|
0
|
|
15158
15244
|
);
|
|
15245
|
+
for (const path of droppedPaths) {
|
|
15246
|
+
exclude(path, truncationReason === "max_files" ? "max-files-cap" : "max-total-bytes-cap");
|
|
15247
|
+
}
|
|
15159
15248
|
return {
|
|
15160
15249
|
files: result,
|
|
15161
15250
|
total_lines: totalLines,
|
|
15162
15251
|
total_files: result.length,
|
|
15252
|
+
excluded,
|
|
15163
15253
|
...truncationReason && {
|
|
15164
15254
|
truncated: {
|
|
15165
15255
|
reason: truncationReason,
|
|
@@ -16611,7 +16701,7 @@ function resolveTaskContext(opts) {
|
|
|
16611
16701
|
// src/lib/cli-version.ts
|
|
16612
16702
|
function cliVersion() {
|
|
16613
16703
|
try {
|
|
16614
|
-
return true ? "0.28.1-experimental.
|
|
16704
|
+
return true ? "0.28.1-experimental.39013ce" : "dev";
|
|
16615
16705
|
} catch {
|
|
16616
16706
|
return "dev";
|
|
16617
16707
|
}
|
|
@@ -17156,6 +17246,86 @@ function checkConservation(changedFiles, result, repoRoot2) {
|
|
|
17156
17246
|
};
|
|
17157
17247
|
}
|
|
17158
17248
|
|
|
17249
|
+
// src/lib/verdict.ts
|
|
17250
|
+
function reconcileCoverage(changed, coverage) {
|
|
17251
|
+
const changedSet = new Set(changed);
|
|
17252
|
+
const reviewed = coverage.reviewed.filter((p) => changedSet.has(p));
|
|
17253
|
+
const claimed = /* @__PURE__ */ new Set([...reviewed, ...coverage.notReviewed.map((n) => n.path)]);
|
|
17254
|
+
const unaccounted = [...changedSet].filter((p) => !claimed.has(p)).sort();
|
|
17255
|
+
const notReviewed = [
|
|
17256
|
+
...coverage.notReviewed.filter((n) => changedSet.has(n.path)),
|
|
17257
|
+
...unaccounted.map((path) => ({
|
|
17258
|
+
path,
|
|
17259
|
+
reason: "unaccounted",
|
|
17260
|
+
// Named so the eventual bug report writes itself: some stage removed this
|
|
17261
|
+
// path and did not say so.
|
|
17262
|
+
stage: "unknown-stage",
|
|
17263
|
+
// An undeclared drop is CAPACITY by default. A stage that cannot be
|
|
17264
|
+
// bothered to say why it dropped a file does not get the benefit of the
|
|
17265
|
+
// doubt — that default is what makes forgetting expensive.
|
|
17266
|
+
kind: "capacity"
|
|
17267
|
+
}))
|
|
17268
|
+
];
|
|
17269
|
+
return {
|
|
17270
|
+
coverage: { reviewed: [...new Set(reviewed)].sort(), notReviewed },
|
|
17271
|
+
unaccounted,
|
|
17272
|
+
balances: unaccounted.length === 0
|
|
17273
|
+
};
|
|
17274
|
+
}
|
|
17275
|
+
function resolveVerdict(proposed, coverage) {
|
|
17276
|
+
if (proposed === "FAIL") return "FAIL";
|
|
17277
|
+
const blocking = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17278
|
+
if (blocking.length === 0) return proposed;
|
|
17279
|
+
return "WARN";
|
|
17280
|
+
}
|
|
17281
|
+
function describeCoverage(coverage, maxPaths = 5) {
|
|
17282
|
+
const relevant = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17283
|
+
if (relevant.length === 0) return null;
|
|
17284
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
17285
|
+
for (const n of relevant) {
|
|
17286
|
+
const key = `${n.reason}`;
|
|
17287
|
+
const list = byReason.get(key) ?? [];
|
|
17288
|
+
list.push(n.path);
|
|
17289
|
+
byReason.set(key, list);
|
|
17290
|
+
}
|
|
17291
|
+
const lines = [];
|
|
17292
|
+
for (const [reason, paths] of [...byReason.entries()].sort()) {
|
|
17293
|
+
const shown = paths.slice(0, maxPaths).join(", ");
|
|
17294
|
+
const more = paths.length > maxPaths ? ` (+${paths.length - maxPaths} more)` : "";
|
|
17295
|
+
lines.push(` ${paths.length} not reviewed \u2014 ${reason}: ${shown}${more}`);
|
|
17296
|
+
}
|
|
17297
|
+
return `NOT A CLEAN REVIEW. ${relevant.length} changed file(s) never reached the reviewer, so this verdict does not cover them:
|
|
17298
|
+
${lines.join("\n")}
|
|
17299
|
+
Treat those files as UNCHECKED, not as approved.`;
|
|
17300
|
+
}
|
|
17301
|
+
function openBlockingElsewhere(statements, reviewedNow, lineShaAt) {
|
|
17302
|
+
const reviewed = new Set(reviewedNow);
|
|
17303
|
+
const out = [];
|
|
17304
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17305
|
+
for (const s of statements) {
|
|
17306
|
+
if (s.outcome !== "open") continue;
|
|
17307
|
+
if (s.register !== "BLOCK") continue;
|
|
17308
|
+
if (s.carried) continue;
|
|
17309
|
+
if (reviewed.has(s.file)) continue;
|
|
17310
|
+
if (!s.line_sha) continue;
|
|
17311
|
+
if (lineShaAt(s.file, s.line) !== s.line_sha) continue;
|
|
17312
|
+
const key = `${s.file}::${s.pattern_id}`;
|
|
17313
|
+
if (seen.has(key)) continue;
|
|
17314
|
+
seen.add(key);
|
|
17315
|
+
out.push({ file: s.file, line: s.line, pattern_id: s.pattern_id });
|
|
17316
|
+
}
|
|
17317
|
+
return out;
|
|
17318
|
+
}
|
|
17319
|
+
function describeOpenElsewhere(open) {
|
|
17320
|
+
if (open.length === 0) return null;
|
|
17321
|
+
const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
|
|
17322
|
+
const more = open.length > 5 ? `
|
|
17323
|
+
(+${open.length - 5} more)` : "";
|
|
17324
|
+
return `STILL OPEN ELSEWHERE. ${open.length} blocking finding(s) Verity raised earlier are still present in files this run did not review:
|
|
17325
|
+
${lines.join("\n")}${more}
|
|
17326
|
+
This verdict covers the current change only. The tree is not clean.`;
|
|
17327
|
+
}
|
|
17328
|
+
|
|
17159
17329
|
// src/lib/channel.ts
|
|
17160
17330
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
17161
17331
|
var MAX_AGENT_ITEMS = 5;
|
|
@@ -17243,6 +17413,39 @@ function channelSilence(input) {
|
|
|
17243
17413
|
return null;
|
|
17244
17414
|
}
|
|
17245
17415
|
|
|
17416
|
+
// src/lib/emit.ts
|
|
17417
|
+
var YELLOW2 = "\x1B[33m";
|
|
17418
|
+
var NC2 = "\x1B[0m";
|
|
17419
|
+
function emitVerdict(input) {
|
|
17420
|
+
const exit = input.exit ?? ((code) => process.exit(code));
|
|
17421
|
+
const { coverage, unaccounted } = reconcileCoverage(input.changed, input.coverage);
|
|
17422
|
+
let verdict = resolveVerdict(input.proposed, coverage);
|
|
17423
|
+
const openElsewhere = input.openElsewhere ?? [];
|
|
17424
|
+
if (verdict === "PASS" && openElsewhere.length > 0) verdict = "WARN";
|
|
17425
|
+
const note = [describeCoverage(coverage), describeOpenElsewhere(openElsewhere)].filter(Boolean).join("\n\n") || null;
|
|
17426
|
+
if (unaccounted.length > 0) {
|
|
17427
|
+
process.stderr.write(
|
|
17428
|
+
`${YELLOW2}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
|
|
17429
|
+
`
|
|
17430
|
+
);
|
|
17431
|
+
}
|
|
17432
|
+
if (verdict === "FAIL") {
|
|
17433
|
+
input.renderBlocking?.();
|
|
17434
|
+
if (input.agentContext) {
|
|
17435
|
+
process.stderr.write(`
|
|
17436
|
+
${input.agentContext}
|
|
17437
|
+
`);
|
|
17438
|
+
}
|
|
17439
|
+
if (note && !input.silenced) process.stderr.write(`
|
|
17440
|
+
${YELLOW2}${note}${NC2}
|
|
17441
|
+
`);
|
|
17442
|
+
return exit(2);
|
|
17443
|
+
}
|
|
17444
|
+
const agentBlock = input.silenced ? null : [input.agentContext, note].filter(Boolean).join("\n\n") || null;
|
|
17445
|
+
printJsonCompact(buildHookOutput(verdict, input.userSummary, agentBlock));
|
|
17446
|
+
return exit(0);
|
|
17447
|
+
}
|
|
17448
|
+
|
|
17246
17449
|
// src/lib/cache-cleanup.ts
|
|
17247
17450
|
var import_node_fs21 = require("node:fs");
|
|
17248
17451
|
var import_node_path18 = require("node:path");
|
|
@@ -17422,46 +17625,6 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
17422
17625
|
return false;
|
|
17423
17626
|
}
|
|
17424
17627
|
|
|
17425
|
-
// src/lib/skip-detection.ts
|
|
17426
|
-
function isBareAckPrompt(prompt) {
|
|
17427
|
-
if (typeof prompt !== "string") return false;
|
|
17428
|
-
const trimmed = prompt.trim();
|
|
17429
|
-
if (trimmed.length === 0) return false;
|
|
17430
|
-
if (trimmed.length > 20) return false;
|
|
17431
|
-
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;
|
|
17432
|
-
return bareAckPattern.test(trimmed);
|
|
17433
|
-
}
|
|
17434
|
-
function isReflectionQuestion(response) {
|
|
17435
|
-
if (!response || typeof response !== "string") return false;
|
|
17436
|
-
const markers = [
|
|
17437
|
-
/reflection\s+for\s+future\s+agents/i,
|
|
17438
|
-
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
17439
|
-
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
17440
|
-
/quick\s+reflection\s+question/i,
|
|
17441
|
-
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
17442
|
-
// interactive, asks the user to confirm/correct before recording. That
|
|
17443
|
-
// turn authors no code either, so it's still a reflection turn.
|
|
17444
|
-
/reflection\s+draft/i,
|
|
17445
|
-
/confirm,?\s+correct,?\s+or\s+add/i
|
|
17446
|
-
];
|
|
17447
|
-
return markers.some((m) => m.test(response));
|
|
17448
|
-
}
|
|
17449
|
-
function isMetaTaskLabel(label2) {
|
|
17450
|
-
if (label2 === null || label2 === void 0) return false;
|
|
17451
|
-
if (typeof label2 !== "string") return false;
|
|
17452
|
-
const trimmed = label2.trim();
|
|
17453
|
-
if (trimmed.length === 0) return true;
|
|
17454
|
-
const metaPatterns = [
|
|
17455
|
-
/^verity\s+[\w-]+\s+response$/i,
|
|
17456
|
-
// "Verity reflect response"
|
|
17457
|
-
/^simple user response$/i,
|
|
17458
|
-
/^verity\s+command$/i,
|
|
17459
|
-
// "Verity command"
|
|
17460
|
-
/^user\s+(question|reply|response|ack)$/i
|
|
17461
|
-
];
|
|
17462
|
-
return metaPatterns.some((p) => p.test(trimmed));
|
|
17463
|
-
}
|
|
17464
|
-
|
|
17465
17628
|
// src/lib/transcript.ts
|
|
17466
17629
|
var import_node_fs22 = require("node:fs");
|
|
17467
17630
|
var MAX_READ_BYTES = 256 * 1024;
|
|
@@ -17625,6 +17788,13 @@ function buildSummary(lines) {
|
|
|
17625
17788
|
files_read: capArray(filesRead, MAX_FILES_LIST),
|
|
17626
17789
|
files_edited: capArray(filesEdited, MAX_FILES_LIST),
|
|
17627
17790
|
files_created: capArray(filesCreated, MAX_CREATED_LIST),
|
|
17791
|
+
// The complement of the two caps that affect SCOPE. `files_read` is excluded
|
|
17792
|
+
// deliberately: reading a file is not authoring it, so a capped read list
|
|
17793
|
+
// narrows nothing.
|
|
17794
|
+
capped_out: [
|
|
17795
|
+
...cappedOut(filesEdited, MAX_FILES_LIST),
|
|
17796
|
+
...cappedOut(filesCreated, MAX_CREATED_LIST)
|
|
17797
|
+
],
|
|
17628
17798
|
searches,
|
|
17629
17799
|
commands,
|
|
17630
17800
|
subagents,
|
|
@@ -17675,6 +17845,9 @@ function sanitizeCommand(rawCmd) {
|
|
|
17675
17845
|
function capArray(set, max) {
|
|
17676
17846
|
return Array.from(set).slice(0, max);
|
|
17677
17847
|
}
|
|
17848
|
+
function cappedOut(set, max) {
|
|
17849
|
+
return Array.from(set).slice(max);
|
|
17850
|
+
}
|
|
17678
17851
|
|
|
17679
17852
|
// src/lib/run-mode.ts
|
|
17680
17853
|
function parseAutonomousEnv(raw) {
|
|
@@ -18084,12 +18257,45 @@ function agentContextFor(response, intentRepeat = 0) {
|
|
|
18084
18257
|
});
|
|
18085
18258
|
}
|
|
18086
18259
|
var beaconCtx = null;
|
|
18087
|
-
async function passAndExit(reason, skip) {
|
|
18260
|
+
async function passAndExit(reason, skip, kindOverride) {
|
|
18088
18261
|
const sent = await sendSkipBeacon(beaconCtx, skip);
|
|
18089
18262
|
logEvent("skip", { reason: skip, beacon: sent });
|
|
18090
|
-
|
|
18263
|
+
const POLICY_SKIPS = /* @__PURE__ */ new Set([
|
|
18264
|
+
"no-analyzable-files",
|
|
18265
|
+
"verity-command",
|
|
18266
|
+
"bare-acknowledgment",
|
|
18267
|
+
"reflection-prompt",
|
|
18268
|
+
"skip-mode",
|
|
18269
|
+
"zero-increment",
|
|
18270
|
+
"debounce",
|
|
18271
|
+
"no-delta-since-last-review"
|
|
18272
|
+
]);
|
|
18273
|
+
const skipKind = kindOverride ?? (POLICY_SKIPS.has(skip) ? "policy" : "capacity");
|
|
18274
|
+
const changed = skipCoverageChanged;
|
|
18275
|
+
const { coverage, unaccounted } = reconcileCoverage(changed, {
|
|
18276
|
+
reviewed: [],
|
|
18277
|
+
notReviewed: changed.map((path) => ({ path, reason: skip, stage: "pre-flight", kind: skipKind }))
|
|
18278
|
+
});
|
|
18279
|
+
const verdict = resolveVerdict("PASS", coverage);
|
|
18280
|
+
const note = describeCoverage(coverage);
|
|
18281
|
+
if (unaccounted.length > 0) {
|
|
18282
|
+
logEvent("coverage_unaccounted", { where: "passAndExit", skip, count: unaccounted.length });
|
|
18283
|
+
}
|
|
18284
|
+
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set(["iteration-cap"]);
|
|
18285
|
+
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18286
|
+
printJsonCompact(
|
|
18287
|
+
buildHookOutput(
|
|
18288
|
+
verdict,
|
|
18289
|
+
`Verity: ${reason}`,
|
|
18290
|
+
// The agent's ONLY input is additionalContext. Sixteen of the nineteen
|
|
18291
|
+
// terminating paths wrote `systemMessage` — the human's field — and told
|
|
18292
|
+
// the agent nothing at all.
|
|
18293
|
+
agentNote
|
|
18294
|
+
)
|
|
18295
|
+
);
|
|
18091
18296
|
process.exit(0);
|
|
18092
18297
|
}
|
|
18298
|
+
var skipCoverageChanged = [];
|
|
18093
18299
|
var EMPTY_STATIC = {
|
|
18094
18300
|
tool: "@codacy/analysis-cli",
|
|
18095
18301
|
findings: [],
|
|
@@ -18104,7 +18310,7 @@ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
|
18104
18310
|
}
|
|
18105
18311
|
function localOnlyAndExit(staticResults) {
|
|
18106
18312
|
printJsonCompact({
|
|
18107
|
-
gate_decision: "
|
|
18313
|
+
gate_decision: "WARN",
|
|
18108
18314
|
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.",
|
|
18109
18315
|
unauthenticated: true,
|
|
18110
18316
|
static_results: staticResults
|
|
@@ -18164,6 +18370,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18164
18370
|
});
|
|
18165
18371
|
}
|
|
18166
18372
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18373
|
+
skipCoverageChanged = allChanged;
|
|
18167
18374
|
const analyzable = filterAnalyzable(allChanged);
|
|
18168
18375
|
const reviewable = filterReviewable(allChanged);
|
|
18169
18376
|
const securityFiles = filterSecurity(allChanged);
|
|
@@ -18179,10 +18386,12 @@ async function runAnalyze(opts, globals) {
|
|
|
18179
18386
|
if (/^\s*\/verity-/i.test(latestPrompt)) {
|
|
18180
18387
|
await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
|
|
18181
18388
|
}
|
|
18182
|
-
|
|
18389
|
+
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18390
|
+
const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18391
|
+
const canSeeTurnAuthorship = !!actionSummary || !!baseline;
|
|
18392
|
+
if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
|
|
18183
18393
|
await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
|
|
18184
18394
|
}
|
|
18185
|
-
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18186
18395
|
if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
|
|
18187
18396
|
await passAndExit("Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
|
|
18188
18397
|
}
|
|
@@ -18239,7 +18448,8 @@ async function runAnalyze(opts, globals) {
|
|
|
18239
18448
|
let codeDelta = {
|
|
18240
18449
|
files: [],
|
|
18241
18450
|
total_lines: 0,
|
|
18242
|
-
total_files: 0
|
|
18451
|
+
total_files: 0,
|
|
18452
|
+
excluded: []
|
|
18243
18453
|
};
|
|
18244
18454
|
let snapshotResult = { has_snapshots: false, diffs: [] };
|
|
18245
18455
|
let contentHash = null;
|
|
@@ -18304,7 +18514,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18304
18514
|
if (assistantResponse) {
|
|
18305
18515
|
analysisMode = "plan";
|
|
18306
18516
|
} else {
|
|
18307
|
-
await passAndExit(
|
|
18517
|
+
await passAndExit(
|
|
18518
|
+
"No files within size limits to analyze",
|
|
18519
|
+
"size-limit",
|
|
18520
|
+
codeDelta.excluded.length > 0 ? "capacity" : "policy"
|
|
18521
|
+
);
|
|
18308
18522
|
}
|
|
18309
18523
|
}
|
|
18310
18524
|
}
|
|
@@ -18620,7 +18834,12 @@ async function runAnalyze(opts, globals) {
|
|
|
18620
18834
|
const intentContext = {};
|
|
18621
18835
|
if (conversation && conversation.prompts.length > 0) {
|
|
18622
18836
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
18623
|
-
|
|
18837
|
+
const goalPrompt = resolveGoalPrompt(conversation.prompts) ?? { entry: latest, turnsBack: 0 };
|
|
18838
|
+
intentContext.user_prompt = goalPrompt.entry.prompt;
|
|
18839
|
+
if (goalPrompt.turnsBack > 0) {
|
|
18840
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
18841
|
+
logEvent("goal_walked_back", { turns_back: goalPrompt.turnsBack });
|
|
18842
|
+
}
|
|
18624
18843
|
intentContext.session_id = latest.session_id || void 0;
|
|
18625
18844
|
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
18626
18845
|
if (conversation.prompts.length > 1) {
|
|
@@ -18712,6 +18931,85 @@ async function runAnalyze(opts, globals) {
|
|
|
18712
18931
|
const response = result.data;
|
|
18713
18932
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
18714
18933
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
18934
|
+
let openElsewhere = [];
|
|
18935
|
+
if (memorySession) {
|
|
18936
|
+
try {
|
|
18937
|
+
const st = foldDossier(memorySession.d);
|
|
18938
|
+
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
18939
|
+
try {
|
|
18940
|
+
const src = (0, import_node_fs24.readFileSync)((0, import_node_path20.join)(repoRoot(), file), "utf8").split("\n");
|
|
18941
|
+
const at = src[line - 1];
|
|
18942
|
+
return at === void 0 ? null : lineSha(at);
|
|
18943
|
+
} catch {
|
|
18944
|
+
return null;
|
|
18945
|
+
}
|
|
18946
|
+
});
|
|
18947
|
+
} catch {
|
|
18948
|
+
}
|
|
18949
|
+
}
|
|
18950
|
+
const reviewCoverage = {
|
|
18951
|
+
reviewed: sentPaths,
|
|
18952
|
+
// Declared drops from the stages that DO report themselves today. The other
|
|
18953
|
+
// stages surface via `unaccounted`, which is the tripwire, not the design.
|
|
18954
|
+
notReviewed: [
|
|
18955
|
+
// Every exit from the collection loop, each named. Six reasons where there
|
|
18956
|
+
// used to be two recorded and four silent — the silent ones including the
|
|
18957
|
+
// per-file size cap, which could drop a whole source file without leaving a
|
|
18958
|
+
// trace anywhere in the payload or the run row.
|
|
18959
|
+
...codeDelta.excluded,
|
|
18960
|
+
// The server-side 300-line middle-out truncation. It only bites on the
|
|
18961
|
+
// full-file branch (a first analysis, before snapshots exist) because
|
|
18962
|
+
// analyze normally sends diffs — but on that branch the reviewer sees the
|
|
18963
|
+
// first and last 100 lines and nothing between, and until now said so to
|
|
18964
|
+
// nobody. CAPACITY: a partial look is not a look.
|
|
18965
|
+
...(response.metadata?.truncated_files ?? []).map((path) => ({
|
|
18966
|
+
path,
|
|
18967
|
+
reason: "file-middle-truncated-300-lines",
|
|
18968
|
+
stage: "prompt-builder",
|
|
18969
|
+
kind: "capacity"
|
|
18970
|
+
})),
|
|
18971
|
+
// The 20-entry edit cap. CAPACITY, and the sharpest of the lot: it narrows
|
|
18972
|
+
// what is REVIEWED, not merely what is summarised — a session editing 25
|
|
18973
|
+
// files had five silently excluded from the reviewed set.
|
|
18974
|
+
...(actionSummary?.capped_out ?? []).map((path) => ({
|
|
18975
|
+
path,
|
|
18976
|
+
reason: "edit-list-cap-20",
|
|
18977
|
+
stage: "extractActionSummary",
|
|
18978
|
+
kind: "capacity"
|
|
18979
|
+
})),
|
|
18980
|
+
// ⚠ BASELINE SCOPING — the biggest source of false NOT A CLEAN REVIEW.
|
|
18981
|
+
//
|
|
18982
|
+
// The universe is `allChanged`, git's whole dirty tree. The reviewed set is
|
|
18983
|
+
// scoped to what THIS SESSION authored (the VRT-26 contamination cure), so
|
|
18984
|
+
// every pre-existing dirty file is in the universe, absent from `reviewed`,
|
|
18985
|
+
// and — until now — declared by nobody. It fell through to `unaccounted`,
|
|
18986
|
+
// became capacity, and produced "NOT A CLEAN REVIEW: admin.js" over a file
|
|
18987
|
+
// that was never this session's to review.
|
|
18988
|
+
//
|
|
18989
|
+
// Measured 2026-08-04: three consecutive runs over an untouched tree gave
|
|
18990
|
+
// three different answers — .claude/settings.json, then admin.js, then six
|
|
18991
|
+
// files — because each run took a different path and each path had a
|
|
18992
|
+
// different idea of the universe. POLICY: not this session's work is not a
|
|
18993
|
+
// coverage gap, it is the cure working.
|
|
18994
|
+
...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) => ({
|
|
18995
|
+
path,
|
|
18996
|
+
reason: "not-authored-this-session",
|
|
18997
|
+
stage: "baseline-scoping",
|
|
18998
|
+
kind: "policy"
|
|
18999
|
+
})),
|
|
19000
|
+
// The extension allowlist, and it is POLICY rather than capacity: a changed
|
|
19001
|
+
// README was never going to be reviewed, and treating that as a coverage
|
|
19002
|
+
// gap would downgrade nearly every PASS to WARN until WARN meant nothing.
|
|
19003
|
+
// Recorded so the ledger balances and so "what did Verity ignore entirely"
|
|
19004
|
+
// is answerable — but it never touches the verdict.
|
|
19005
|
+
...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
|
|
19006
|
+
path,
|
|
19007
|
+
reason: "not-a-reviewed-file-type",
|
|
19008
|
+
stage: "extension-allowlist",
|
|
19009
|
+
kind: "policy"
|
|
19010
|
+
}))
|
|
19011
|
+
]
|
|
19012
|
+
};
|
|
18715
19013
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18716
19014
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
18717
19015
|
let silenced = null;
|
|
@@ -18943,7 +19241,19 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18943
19241
|
if (grantNudge) process.stderr.write(`
|
|
18944
19242
|
${YELLOW}${grantNudge.trim()}${NC}
|
|
18945
19243
|
`);
|
|
18946
|
-
|
|
19244
|
+
emitVerdict({
|
|
19245
|
+
proposed: "FAIL",
|
|
19246
|
+
changed: skipCoverageChanged,
|
|
19247
|
+
coverage: reviewCoverage,
|
|
19248
|
+
userSummary: "",
|
|
19249
|
+
// Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
|
|
19250
|
+
// the findings themselves are rendered above by the blocking renderer,
|
|
19251
|
+
// so what the cut removes is the repeated commentary, never the defect.
|
|
19252
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19253
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19254
|
+
silenced: !!silenced,
|
|
19255
|
+
openElsewhere
|
|
19256
|
+
});
|
|
18947
19257
|
break;
|
|
18948
19258
|
}
|
|
18949
19259
|
case "PASS": {
|
|
@@ -18955,10 +19265,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18955
19265
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18956
19266
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18957
19267
|
userSummary += loginNudge + grantNudge;
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
19268
|
+
emitVerdict({
|
|
19269
|
+
proposed: "PASS",
|
|
19270
|
+
changed: skipCoverageChanged,
|
|
19271
|
+
coverage: reviewCoverage,
|
|
19272
|
+
userSummary,
|
|
19273
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19274
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19275
|
+
silenced: !!silenced,
|
|
19276
|
+
openElsewhere
|
|
19277
|
+
});
|
|
18962
19278
|
break;
|
|
18963
19279
|
}
|
|
18964
19280
|
case "WARN": {
|
|
@@ -18969,10 +19285,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18969
19285
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18970
19286
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18971
19287
|
userSummary += loginNudge + grantNudge;
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
19288
|
+
emitVerdict({
|
|
19289
|
+
proposed: "WARN",
|
|
19290
|
+
changed: skipCoverageChanged,
|
|
19291
|
+
coverage: reviewCoverage,
|
|
19292
|
+
userSummary,
|
|
19293
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19294
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19295
|
+
silenced: !!silenced,
|
|
19296
|
+
openElsewhere
|
|
19297
|
+
});
|
|
18976
19298
|
break;
|
|
18977
19299
|
}
|
|
18978
19300
|
default: {
|
|
@@ -19085,8 +19407,8 @@ async function runReview(opts, globals) {
|
|
|
19085
19407
|
for (const p of specPaths) {
|
|
19086
19408
|
if (!(0, import_node_fs26.existsSync)(p)) continue;
|
|
19087
19409
|
try {
|
|
19088
|
-
const { readFileSync:
|
|
19089
|
-
const content =
|
|
19410
|
+
const { readFileSync: readFileSync16 } = await import("node:fs");
|
|
19411
|
+
const content = readFileSync16(p, "utf-8");
|
|
19090
19412
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
19091
19413
|
} catch {
|
|
19092
19414
|
}
|
|
@@ -20772,7 +21094,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20772
21094
|
}
|
|
20773
21095
|
|
|
20774
21096
|
// src/cli.ts
|
|
20775
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
21097
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.39013ce").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
20776
21098
|
try {
|
|
20777
21099
|
await foldLegacyLocalCredential();
|
|
20778
21100
|
} catch {
|