@codacy/verity-cli 0.28.1-experimental.055cd97 → 0.28.1-experimental.4410c26
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 +575 -99
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10386,10 +10386,9 @@ function projectPath(relativePath) {
|
|
|
10386
10386
|
return (0, import_node_path.join)(repoRoot(), relativePath);
|
|
10387
10387
|
}
|
|
10388
10388
|
var MAX_DELTA_BYTES = 194560;
|
|
10389
|
-
var MAX_FILES =
|
|
10389
|
+
var MAX_FILES = 40;
|
|
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");
|
|
@@ -14695,7 +14758,19 @@ function sessionDossier(token, sessionId) {
|
|
|
14695
14758
|
const d = openDossier(identity);
|
|
14696
14759
|
return d ? { d, identity } : null;
|
|
14697
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
|
+
}
|
|
14698
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
|
+
}
|
|
14699
14774
|
const text = prompt.slice(0, MAX_GOAL_CHARS);
|
|
14700
14775
|
appendEvent(d, {
|
|
14701
14776
|
k: "goal",
|
|
@@ -15118,6 +15193,8 @@ function collectCodeDelta(files, opts) {
|
|
|
15118
15193
|
let totalSize = 0;
|
|
15119
15194
|
let truncationReason = null;
|
|
15120
15195
|
const droppedPaths = [];
|
|
15196
|
+
const excluded = [];
|
|
15197
|
+
const exclude = (path, reason) => excluded.push({ path, reason, stage: "collectCodeDelta", kind: "capacity" });
|
|
15121
15198
|
for (const filepath of sorted) {
|
|
15122
15199
|
if (result.length >= maxFiles) {
|
|
15123
15200
|
truncationReason ??= "max_files";
|
|
@@ -15125,14 +15202,21 @@ function collectCodeDelta(files, opts) {
|
|
|
15125
15202
|
continue;
|
|
15126
15203
|
}
|
|
15127
15204
|
const resolved = resolveFile(filepath);
|
|
15128
|
-
if (!resolved)
|
|
15205
|
+
if (!resolved) {
|
|
15206
|
+
exclude(filepath, "path-not-resolvable");
|
|
15207
|
+
continue;
|
|
15208
|
+
}
|
|
15129
15209
|
let size;
|
|
15130
15210
|
try {
|
|
15131
15211
|
size = (0, import_node_fs11.statSync)(resolved).size;
|
|
15132
15212
|
} catch {
|
|
15213
|
+
exclude(filepath, "not-stattable");
|
|
15214
|
+
continue;
|
|
15215
|
+
}
|
|
15216
|
+
if (size > maxFileBytes) {
|
|
15217
|
+
exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
|
|
15133
15218
|
continue;
|
|
15134
15219
|
}
|
|
15135
|
-
if (size > maxFileBytes) continue;
|
|
15136
15220
|
if (totalSize + size > maxTotalBytes) {
|
|
15137
15221
|
truncationReason ??= "max_total_bytes";
|
|
15138
15222
|
const idx = sorted.indexOf(filepath);
|
|
@@ -15143,6 +15227,7 @@ function collectCodeDelta(files, opts) {
|
|
|
15143
15227
|
try {
|
|
15144
15228
|
content = (0, import_node_fs11.readFileSync)(resolved, "utf-8");
|
|
15145
15229
|
} catch {
|
|
15230
|
+
exclude(filepath, "not-readable");
|
|
15146
15231
|
continue;
|
|
15147
15232
|
}
|
|
15148
15233
|
totalSize += size;
|
|
@@ -15156,10 +15241,14 @@ function collectCodeDelta(files, opts) {
|
|
|
15156
15241
|
(sum, f) => sum + f.content.split("\n").length,
|
|
15157
15242
|
0
|
|
15158
15243
|
);
|
|
15244
|
+
for (const path of droppedPaths) {
|
|
15245
|
+
exclude(path, truncationReason === "max_files" ? "max-files-cap" : "max-total-bytes-cap");
|
|
15246
|
+
}
|
|
15159
15247
|
return {
|
|
15160
15248
|
files: result,
|
|
15161
15249
|
total_lines: totalLines,
|
|
15162
15250
|
total_files: result.length,
|
|
15251
|
+
excluded,
|
|
15163
15252
|
...truncationReason && {
|
|
15164
15253
|
truncated: {
|
|
15165
15254
|
reason: truncationReason,
|
|
@@ -15453,6 +15542,34 @@ ${addedLines}`,
|
|
|
15453
15542
|
}
|
|
15454
15543
|
return { diffs, has_baseline: true };
|
|
15455
15544
|
}
|
|
15545
|
+
function absorbIntoBaseline(paths, sessionId) {
|
|
15546
|
+
const baseline = readBaseline(sessionId);
|
|
15547
|
+
if (!baseline || paths.length === 0) return 0;
|
|
15548
|
+
const dir = sessionDir(sessionKey(baseline.session_id));
|
|
15549
|
+
let adopted = 0;
|
|
15550
|
+
const dirty = new Set(baseline.dirty_paths);
|
|
15551
|
+
for (const p of paths) {
|
|
15552
|
+
try {
|
|
15553
|
+
const content = safeReadForMirror(projectPath(p));
|
|
15554
|
+
if (content === null) continue;
|
|
15555
|
+
const dest = mirrorPath(dir, p);
|
|
15556
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
|
|
15557
|
+
(0, import_node_fs13.writeFileSync)(dest, content);
|
|
15558
|
+
dirty.add(p);
|
|
15559
|
+
adopted++;
|
|
15560
|
+
} catch {
|
|
15561
|
+
}
|
|
15562
|
+
}
|
|
15563
|
+
if (adopted === 0) return 0;
|
|
15564
|
+
try {
|
|
15565
|
+
const updated = { ...baseline, dirty_paths: [...dirty] };
|
|
15566
|
+
(0, import_node_fs13.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
|
|
15567
|
+
preImageCache.delete(baseline);
|
|
15568
|
+
} catch {
|
|
15569
|
+
return 0;
|
|
15570
|
+
}
|
|
15571
|
+
return adopted;
|
|
15572
|
+
}
|
|
15456
15573
|
function changedSinceBaseline(repoRelPath, baseline) {
|
|
15457
15574
|
const pre = preImage(repoRelPath, baseline);
|
|
15458
15575
|
let current;
|
|
@@ -16329,40 +16446,40 @@ function narrowToRecent(files, sessionId) {
|
|
|
16329
16446
|
});
|
|
16330
16447
|
return recent.length > 0 ? recent : files;
|
|
16331
16448
|
}
|
|
16332
|
-
function
|
|
16333
|
-
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return 1;
|
|
16449
|
+
function readIterationState(currentCommit) {
|
|
16450
|
+
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
|
|
16334
16451
|
try {
|
|
16335
16452
|
const stored = (0, import_node_fs15.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16336
16453
|
const parts = stored.split(":");
|
|
16337
16454
|
const iter = parseInt(parts[0], 10);
|
|
16338
16455
|
const storedCommit = parts[1] ?? "";
|
|
16339
16456
|
const storedTimestamp = parseInt(parts[2] ?? "0", 10);
|
|
16340
|
-
|
|
16341
|
-
if (
|
|
16457
|
+
const fingerprint = parts.slice(3).join(":") || null;
|
|
16458
|
+
if (isNaN(iter)) return { iteration: 1, fingerprint: null };
|
|
16459
|
+
if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
|
|
16342
16460
|
if (storedTimestamp > 0) {
|
|
16343
16461
|
const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
|
|
16344
|
-
if (elapsed > 600) return 1;
|
|
16462
|
+
if (elapsed > 600) return { iteration: 1, fingerprint: null };
|
|
16345
16463
|
}
|
|
16346
|
-
return iter;
|
|
16464
|
+
return { iteration: iter, fingerprint };
|
|
16347
16465
|
} catch {
|
|
16348
|
-
return 1;
|
|
16466
|
+
return { iteration: 1, fingerprint: null };
|
|
16349
16467
|
}
|
|
16350
16468
|
}
|
|
16351
|
-
function
|
|
16352
|
-
const
|
|
16353
|
-
|
|
16354
|
-
writeIteration(1, currentCommit, contentHash);
|
|
16355
|
-
return {
|
|
16356
|
-
skip: `Max Verity iterations (${maxIterations}) reached \u2014 accepting to prevent infinite loop. Human review required before deploying.`,
|
|
16357
|
-
iteration
|
|
16358
|
-
};
|
|
16359
|
-
}
|
|
16360
|
-
return { skip: null, iteration };
|
|
16469
|
+
function findingsFingerprint(findings) {
|
|
16470
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16471
|
+
return [...new Set(keys)].sort().join(",");
|
|
16361
16472
|
}
|
|
16362
|
-
function
|
|
16473
|
+
function isSameProblem(previous, current) {
|
|
16474
|
+
if (!previous || !current) return false;
|
|
16475
|
+
const prev = new Set(previous.split(","));
|
|
16476
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16477
|
+
}
|
|
16478
|
+
function writeIteration(iteration, commit, _contentHash, fingerprint) {
|
|
16363
16479
|
(0, import_node_fs15.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16364
16480
|
const ts = Math.floor(Date.now() / 1e3);
|
|
16365
|
-
|
|
16481
|
+
const fp = fingerprint ? `:${fingerprint}` : "";
|
|
16482
|
+
(0, import_node_fs15.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
|
|
16366
16483
|
}
|
|
16367
16484
|
|
|
16368
16485
|
// src/lib/static-analysis.ts
|
|
@@ -16611,7 +16728,7 @@ function resolveTaskContext(opts) {
|
|
|
16611
16728
|
// src/lib/cli-version.ts
|
|
16612
16729
|
function cliVersion() {
|
|
16613
16730
|
try {
|
|
16614
|
-
return true ? "0.28.1-experimental.
|
|
16731
|
+
return true ? "0.28.1-experimental.4410c26" : "dev";
|
|
16615
16732
|
} catch {
|
|
16616
16733
|
return "dev";
|
|
16617
16734
|
}
|
|
@@ -17156,6 +17273,86 @@ function checkConservation(changedFiles, result, repoRoot2) {
|
|
|
17156
17273
|
};
|
|
17157
17274
|
}
|
|
17158
17275
|
|
|
17276
|
+
// src/lib/verdict.ts
|
|
17277
|
+
function reconcileCoverage(changed, coverage) {
|
|
17278
|
+
const changedSet = new Set(changed);
|
|
17279
|
+
const reviewed = coverage.reviewed.filter((p) => changedSet.has(p));
|
|
17280
|
+
const claimed = /* @__PURE__ */ new Set([...reviewed, ...coverage.notReviewed.map((n) => n.path)]);
|
|
17281
|
+
const unaccounted = [...changedSet].filter((p) => !claimed.has(p)).sort();
|
|
17282
|
+
const notReviewed = [
|
|
17283
|
+
...coverage.notReviewed.filter((n) => changedSet.has(n.path)),
|
|
17284
|
+
...unaccounted.map((path) => ({
|
|
17285
|
+
path,
|
|
17286
|
+
reason: "unaccounted",
|
|
17287
|
+
// Named so the eventual bug report writes itself: some stage removed this
|
|
17288
|
+
// path and did not say so.
|
|
17289
|
+
stage: "unknown-stage",
|
|
17290
|
+
// An undeclared drop is CAPACITY by default. A stage that cannot be
|
|
17291
|
+
// bothered to say why it dropped a file does not get the benefit of the
|
|
17292
|
+
// doubt — that default is what makes forgetting expensive.
|
|
17293
|
+
kind: "capacity"
|
|
17294
|
+
}))
|
|
17295
|
+
];
|
|
17296
|
+
return {
|
|
17297
|
+
coverage: { reviewed: [...new Set(reviewed)].sort(), notReviewed },
|
|
17298
|
+
unaccounted,
|
|
17299
|
+
balances: unaccounted.length === 0
|
|
17300
|
+
};
|
|
17301
|
+
}
|
|
17302
|
+
function resolveVerdict(proposed, coverage) {
|
|
17303
|
+
if (proposed === "FAIL") return "FAIL";
|
|
17304
|
+
const blocking = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17305
|
+
if (blocking.length === 0) return proposed;
|
|
17306
|
+
return "WARN";
|
|
17307
|
+
}
|
|
17308
|
+
function describeCoverage(coverage, maxPaths = 5) {
|
|
17309
|
+
const relevant = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17310
|
+
if (relevant.length === 0) return null;
|
|
17311
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
17312
|
+
for (const n of relevant) {
|
|
17313
|
+
const key = `${n.reason}`;
|
|
17314
|
+
const list = byReason.get(key) ?? [];
|
|
17315
|
+
list.push(n.path);
|
|
17316
|
+
byReason.set(key, list);
|
|
17317
|
+
}
|
|
17318
|
+
const lines = [];
|
|
17319
|
+
for (const [reason, paths] of [...byReason.entries()].sort()) {
|
|
17320
|
+
const shown = paths.slice(0, maxPaths).join(", ");
|
|
17321
|
+
const more = paths.length > maxPaths ? ` (+${paths.length - maxPaths} more)` : "";
|
|
17322
|
+
lines.push(` ${paths.length} not reviewed \u2014 ${reason}: ${shown}${more}`);
|
|
17323
|
+
}
|
|
17324
|
+
return `NOT A CLEAN REVIEW. ${relevant.length} changed file(s) never reached the reviewer, so this verdict does not cover them:
|
|
17325
|
+
${lines.join("\n")}
|
|
17326
|
+
Treat those files as UNCHECKED, not as approved.`;
|
|
17327
|
+
}
|
|
17328
|
+
function openBlockingElsewhere(statements, reviewedNow, lineShaAt) {
|
|
17329
|
+
const reviewed = new Set(reviewedNow);
|
|
17330
|
+
const out = [];
|
|
17331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17332
|
+
for (const s of statements) {
|
|
17333
|
+
if (s.outcome !== "open") continue;
|
|
17334
|
+
if (s.register !== "BLOCK") continue;
|
|
17335
|
+
if (s.carried) continue;
|
|
17336
|
+
if (reviewed.has(s.file)) continue;
|
|
17337
|
+
if (!s.line_sha) continue;
|
|
17338
|
+
if (lineShaAt(s.file, s.line) !== s.line_sha) continue;
|
|
17339
|
+
const key = `${s.file}::${s.pattern_id}`;
|
|
17340
|
+
if (seen.has(key)) continue;
|
|
17341
|
+
seen.add(key);
|
|
17342
|
+
out.push({ file: s.file, line: s.line, pattern_id: s.pattern_id });
|
|
17343
|
+
}
|
|
17344
|
+
return out;
|
|
17345
|
+
}
|
|
17346
|
+
function describeOpenElsewhere(open) {
|
|
17347
|
+
if (open.length === 0) return null;
|
|
17348
|
+
const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
|
|
17349
|
+
const more = open.length > 5 ? `
|
|
17350
|
+
(+${open.length - 5} more)` : "";
|
|
17351
|
+
return `STILL OPEN ELSEWHERE. ${open.length} blocking finding(s) Verity raised earlier are still present in files this run did not review:
|
|
17352
|
+
${lines.join("\n")}${more}
|
|
17353
|
+
This verdict covers the current change only. The tree is not clean.`;
|
|
17354
|
+
}
|
|
17355
|
+
|
|
17159
17356
|
// src/lib/channel.ts
|
|
17160
17357
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
17161
17358
|
var MAX_AGENT_ITEMS = 5;
|
|
@@ -17205,6 +17402,7 @@ function buildAgentContext(input) {
|
|
|
17205
17402
|
}
|
|
17206
17403
|
for (const p of input.pendingItems ?? []) {
|
|
17207
17404
|
if (lines.length >= MAX_AGENT_ITEMS) break;
|
|
17405
|
+
if (p.pattern_id === "intent-misalignment") continue;
|
|
17208
17406
|
const text = p.description ?? p.title ?? p.reason;
|
|
17209
17407
|
if (!text) continue;
|
|
17210
17408
|
lines.push(renderItem("", text, p.pattern_id, p.file, p.line));
|
|
@@ -17243,6 +17441,39 @@ function channelSilence(input) {
|
|
|
17243
17441
|
return null;
|
|
17244
17442
|
}
|
|
17245
17443
|
|
|
17444
|
+
// src/lib/emit.ts
|
|
17445
|
+
var YELLOW2 = "\x1B[33m";
|
|
17446
|
+
var NC2 = "\x1B[0m";
|
|
17447
|
+
function emitVerdict(input) {
|
|
17448
|
+
const exit = input.exit ?? ((code) => process.exit(code));
|
|
17449
|
+
const { coverage, unaccounted } = reconcileCoverage(input.changed, input.coverage);
|
|
17450
|
+
let verdict = resolveVerdict(input.proposed, coverage);
|
|
17451
|
+
const openElsewhere = input.openElsewhere ?? [];
|
|
17452
|
+
if (verdict === "PASS" && openElsewhere.length > 0) verdict = "WARN";
|
|
17453
|
+
const note = [describeCoverage(coverage), describeOpenElsewhere(openElsewhere)].filter(Boolean).join("\n\n") || null;
|
|
17454
|
+
if (unaccounted.length > 0) {
|
|
17455
|
+
process.stderr.write(
|
|
17456
|
+
`${YELLOW2}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
|
|
17457
|
+
`
|
|
17458
|
+
);
|
|
17459
|
+
}
|
|
17460
|
+
if (verdict === "FAIL") {
|
|
17461
|
+
input.renderBlocking?.();
|
|
17462
|
+
if (input.agentContext) {
|
|
17463
|
+
process.stderr.write(`
|
|
17464
|
+
${input.agentContext}
|
|
17465
|
+
`);
|
|
17466
|
+
}
|
|
17467
|
+
if (note && !input.silenced) process.stderr.write(`
|
|
17468
|
+
${YELLOW2}${note}${NC2}
|
|
17469
|
+
`);
|
|
17470
|
+
return exit(2);
|
|
17471
|
+
}
|
|
17472
|
+
const agentBlock = input.silenced ? null : [input.agentContext, note].filter(Boolean).join("\n\n") || null;
|
|
17473
|
+
printJsonCompact(buildHookOutput(verdict, input.userSummary, agentBlock));
|
|
17474
|
+
return exit(0);
|
|
17475
|
+
}
|
|
17476
|
+
|
|
17246
17477
|
// src/lib/cache-cleanup.ts
|
|
17247
17478
|
var import_node_fs21 = require("node:fs");
|
|
17248
17479
|
var import_node_path18 = require("node:path");
|
|
@@ -17422,46 +17653,6 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
17422
17653
|
return false;
|
|
17423
17654
|
}
|
|
17424
17655
|
|
|
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
17656
|
// src/lib/transcript.ts
|
|
17466
17657
|
var import_node_fs22 = require("node:fs");
|
|
17467
17658
|
var MAX_READ_BYTES = 256 * 1024;
|
|
@@ -17475,9 +17666,11 @@ var MAX_SUMMARY_BYTES = 4096;
|
|
|
17475
17666
|
var HOME = process.env.HOME ?? "";
|
|
17476
17667
|
async function extractActionSummary(transcriptPath) {
|
|
17477
17668
|
try {
|
|
17478
|
-
const
|
|
17479
|
-
if (!
|
|
17480
|
-
|
|
17669
|
+
const read = readTurnLines(transcriptPath);
|
|
17670
|
+
if (!read || read.lines.length === 0) return null;
|
|
17671
|
+
const summary = buildSummary(read.lines);
|
|
17672
|
+
if (summary) summary.transcript_windowed = read.window;
|
|
17673
|
+
return summary;
|
|
17481
17674
|
} catch {
|
|
17482
17675
|
return null;
|
|
17483
17676
|
}
|
|
@@ -17491,9 +17684,11 @@ function readTurnLines(transcriptPath) {
|
|
|
17491
17684
|
}
|
|
17492
17685
|
if (size === 0) return null;
|
|
17493
17686
|
let raw;
|
|
17687
|
+
let windowed = false;
|
|
17494
17688
|
if (size <= SMALL_FILE_BYTES) {
|
|
17495
17689
|
raw = (0, import_node_fs22.readFileSync)(transcriptPath, "utf-8");
|
|
17496
17690
|
} else {
|
|
17691
|
+
windowed = true;
|
|
17497
17692
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
17498
17693
|
const fd = require("node:fs").openSync(transcriptPath, "r");
|
|
17499
17694
|
try {
|
|
@@ -17511,17 +17706,22 @@ function readTurnLines(transcriptPath) {
|
|
|
17511
17706
|
const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
17512
17707
|
if (allLines.length === 0) return null;
|
|
17513
17708
|
let turnStart = 0;
|
|
17709
|
+
let boundaryFound = false;
|
|
17514
17710
|
for (let i = allLines.length - 1; i >= 0; i--) {
|
|
17515
17711
|
try {
|
|
17516
17712
|
const parsed = JSON.parse(allLines[i]);
|
|
17517
17713
|
if (parsed.type === "user" && isRealUserMessage(parsed)) {
|
|
17518
17714
|
turnStart = i;
|
|
17715
|
+
boundaryFound = true;
|
|
17519
17716
|
break;
|
|
17520
17717
|
}
|
|
17521
17718
|
} catch {
|
|
17522
17719
|
}
|
|
17523
17720
|
}
|
|
17524
|
-
return
|
|
17721
|
+
return {
|
|
17722
|
+
lines: allLines.slice(turnStart),
|
|
17723
|
+
window: !windowed ? "whole" : boundaryFound ? "windowed" : "orphaned"
|
|
17724
|
+
};
|
|
17525
17725
|
}
|
|
17526
17726
|
function isRealUserMessage(parsed) {
|
|
17527
17727
|
const message = parsed.message;
|
|
@@ -17625,6 +17825,13 @@ function buildSummary(lines) {
|
|
|
17625
17825
|
files_read: capArray(filesRead, MAX_FILES_LIST),
|
|
17626
17826
|
files_edited: capArray(filesEdited, MAX_FILES_LIST),
|
|
17627
17827
|
files_created: capArray(filesCreated, MAX_CREATED_LIST),
|
|
17828
|
+
// The complement of the two caps that affect SCOPE. `files_read` is excluded
|
|
17829
|
+
// deliberately: reading a file is not authoring it, so a capped read list
|
|
17830
|
+
// narrows nothing.
|
|
17831
|
+
capped_out: [
|
|
17832
|
+
...cappedOut(filesEdited, MAX_FILES_LIST),
|
|
17833
|
+
...cappedOut(filesCreated, MAX_CREATED_LIST)
|
|
17834
|
+
],
|
|
17628
17835
|
searches,
|
|
17629
17836
|
commands,
|
|
17630
17837
|
subagents,
|
|
@@ -17675,6 +17882,9 @@ function sanitizeCommand(rawCmd) {
|
|
|
17675
17882
|
function capArray(set, max) {
|
|
17676
17883
|
return Array.from(set).slice(0, max);
|
|
17677
17884
|
}
|
|
17885
|
+
function cappedOut(set, max) {
|
|
17886
|
+
return Array.from(set).slice(max);
|
|
17887
|
+
}
|
|
17678
17888
|
|
|
17679
17889
|
// src/lib/run-mode.ts
|
|
17680
17890
|
function parseAutonomousEnv(raw) {
|
|
@@ -18084,12 +18294,45 @@ function agentContextFor(response, intentRepeat = 0) {
|
|
|
18084
18294
|
});
|
|
18085
18295
|
}
|
|
18086
18296
|
var beaconCtx = null;
|
|
18087
|
-
async function passAndExit(reason, skip) {
|
|
18297
|
+
async function passAndExit(reason, skip, kindOverride) {
|
|
18088
18298
|
const sent = await sendSkipBeacon(beaconCtx, skip);
|
|
18089
18299
|
logEvent("skip", { reason: skip, beacon: sent });
|
|
18090
|
-
|
|
18300
|
+
const POLICY_SKIPS = /* @__PURE__ */ new Set([
|
|
18301
|
+
"no-analyzable-files",
|
|
18302
|
+
"verity-command",
|
|
18303
|
+
"bare-acknowledgment",
|
|
18304
|
+
"reflection-prompt",
|
|
18305
|
+
"skip-mode",
|
|
18306
|
+
"zero-increment",
|
|
18307
|
+
"debounce",
|
|
18308
|
+
"no-delta-since-last-review"
|
|
18309
|
+
]);
|
|
18310
|
+
const skipKind = kindOverride ?? (POLICY_SKIPS.has(skip) ? "policy" : "capacity");
|
|
18311
|
+
const changed = skipCoverageChanged;
|
|
18312
|
+
const { coverage, unaccounted } = reconcileCoverage(changed, {
|
|
18313
|
+
reviewed: [],
|
|
18314
|
+
notReviewed: changed.map((path) => ({ path, reason: skip, stage: "pre-flight", kind: skipKind }))
|
|
18315
|
+
});
|
|
18316
|
+
const verdict = resolveVerdict("PASS", coverage);
|
|
18317
|
+
const note = describeCoverage(coverage);
|
|
18318
|
+
if (unaccounted.length > 0) {
|
|
18319
|
+
logEvent("coverage_unaccounted", { where: "passAndExit", skip, count: unaccounted.length });
|
|
18320
|
+
}
|
|
18321
|
+
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([]);
|
|
18322
|
+
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18323
|
+
printJsonCompact(
|
|
18324
|
+
buildHookOutput(
|
|
18325
|
+
verdict,
|
|
18326
|
+
`Verity: ${reason}`,
|
|
18327
|
+
// The agent's ONLY input is additionalContext. Sixteen of the nineteen
|
|
18328
|
+
// terminating paths wrote `systemMessage` — the human's field — and told
|
|
18329
|
+
// the agent nothing at all.
|
|
18330
|
+
agentNote
|
|
18331
|
+
)
|
|
18332
|
+
);
|
|
18091
18333
|
process.exit(0);
|
|
18092
18334
|
}
|
|
18335
|
+
var skipCoverageChanged = [];
|
|
18093
18336
|
var EMPTY_STATIC = {
|
|
18094
18337
|
tool: "@codacy/analysis-cli",
|
|
18095
18338
|
findings: [],
|
|
@@ -18104,7 +18347,7 @@ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
|
18104
18347
|
}
|
|
18105
18348
|
function localOnlyAndExit(staticResults) {
|
|
18106
18349
|
printJsonCompact({
|
|
18107
|
-
gate_decision: "
|
|
18350
|
+
gate_decision: "WARN",
|
|
18108
18351
|
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
18352
|
unauthenticated: true,
|
|
18110
18353
|
static_results: staticResults
|
|
@@ -18164,6 +18407,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18164
18407
|
});
|
|
18165
18408
|
}
|
|
18166
18409
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18410
|
+
skipCoverageChanged = allChanged;
|
|
18167
18411
|
const analyzable = filterAnalyzable(allChanged);
|
|
18168
18412
|
const reviewable = filterReviewable(allChanged);
|
|
18169
18413
|
const securityFiles = filterSecurity(allChanged);
|
|
@@ -18175,14 +18419,24 @@ async function runAnalyze(opts, globals) {
|
|
|
18175
18419
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
18176
18420
|
const specs = discoverSpecs();
|
|
18177
18421
|
const plans = discoverPlans();
|
|
18422
|
+
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18423
|
+
const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18424
|
+
const canSeeTurnAuthorship = !!actionSummary || !!baseline;
|
|
18178
18425
|
const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
|
|
18179
18426
|
if (/^\s*\/verity-/i.test(latestPrompt)) {
|
|
18427
|
+
const setupAuthored = [
|
|
18428
|
+
...actionSummary?.files_edited ?? [],
|
|
18429
|
+
...actionSummary?.files_created ?? []
|
|
18430
|
+
];
|
|
18431
|
+
if (setupAuthored.length > 0) {
|
|
18432
|
+
const adopted = absorbIntoBaseline(setupAuthored, baselineSessionId);
|
|
18433
|
+
logEvent("baseline_absorbed", { skip: "verity-command", offered: setupAuthored.length, adopted });
|
|
18434
|
+
}
|
|
18180
18435
|
await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
|
|
18181
18436
|
}
|
|
18182
|
-
if (
|
|
18437
|
+
if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
|
|
18183
18438
|
await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
|
|
18184
18439
|
}
|
|
18185
|
-
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18186
18440
|
if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
|
|
18187
18441
|
await passAndExit("Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
|
|
18188
18442
|
}
|
|
@@ -18229,7 +18483,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18229
18483
|
);
|
|
18230
18484
|
}
|
|
18231
18485
|
if (analysisMode === "skip") {
|
|
18232
|
-
await passAndExit(
|
|
18486
|
+
await passAndExit(
|
|
18487
|
+
"Skip mode \u2014 no code work to analyze",
|
|
18488
|
+
"skip-mode",
|
|
18489
|
+
turnAuthoredCode ? "capacity" : void 0
|
|
18490
|
+
);
|
|
18233
18491
|
}
|
|
18234
18492
|
let staticResults = {
|
|
18235
18493
|
tool: "@codacy/analysis-cli",
|
|
@@ -18239,7 +18497,8 @@ async function runAnalyze(opts, globals) {
|
|
|
18239
18497
|
let codeDelta = {
|
|
18240
18498
|
files: [],
|
|
18241
18499
|
total_lines: 0,
|
|
18242
|
-
total_files: 0
|
|
18500
|
+
total_files: 0,
|
|
18501
|
+
excluded: []
|
|
18243
18502
|
};
|
|
18244
18503
|
let snapshotResult = { has_snapshots: false, diffs: [] };
|
|
18245
18504
|
let contentHash = null;
|
|
@@ -18304,7 +18563,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18304
18563
|
if (assistantResponse) {
|
|
18305
18564
|
analysisMode = "plan";
|
|
18306
18565
|
} else {
|
|
18307
|
-
await passAndExit(
|
|
18566
|
+
await passAndExit(
|
|
18567
|
+
"No files within size limits to analyze",
|
|
18568
|
+
"size-limit",
|
|
18569
|
+
codeDelta.excluded.length > 0 ? "capacity" : "policy"
|
|
18570
|
+
);
|
|
18308
18571
|
}
|
|
18309
18572
|
}
|
|
18310
18573
|
}
|
|
@@ -18317,19 +18580,13 @@ async function runAnalyze(opts, globals) {
|
|
|
18317
18580
|
snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
18318
18581
|
}
|
|
18319
18582
|
currentCommit = getCurrentCommit();
|
|
18320
|
-
|
|
18321
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
18322
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18323
|
-
iteration = iterResult.iteration;
|
|
18583
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18324
18584
|
}
|
|
18325
18585
|
}
|
|
18326
18586
|
if (analysisMode === "plan") {
|
|
18327
18587
|
recordAnalysisStart();
|
|
18328
18588
|
currentCommit = getCurrentCommit();
|
|
18329
|
-
|
|
18330
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations);
|
|
18331
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18332
|
-
iteration = iterResult.iteration;
|
|
18589
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18333
18590
|
}
|
|
18334
18591
|
const contextFiles = gatherContextFiles(contextFilePaths, codeDelta.files);
|
|
18335
18592
|
for (const f of codeDelta.files) {
|
|
@@ -18502,7 +18759,30 @@ async function runAnalyze(opts, globals) {
|
|
|
18502
18759
|
hasUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
18503
18760
|
isTTY: process.stdout.isTTY === true
|
|
18504
18761
|
});
|
|
18762
|
+
const excludedByReason = {};
|
|
18763
|
+
for (const e of codeDelta.excluded ?? []) {
|
|
18764
|
+
excludedByReason[e.reason] = (excludedByReason[e.reason] ?? 0) + 1;
|
|
18765
|
+
}
|
|
18766
|
+
const coverageTelemetry = {
|
|
18767
|
+
// git's whole answer, before ANY narrowing. The number that has never been sent.
|
|
18768
|
+
changed_all: allChanged.length,
|
|
18769
|
+
analyzable: analyzable.length,
|
|
18770
|
+
reviewable: reviewable.length,
|
|
18771
|
+
security: securityFiles.length,
|
|
18772
|
+
// after the allowlist, before authorship scoping and the caps
|
|
18773
|
+
for_review: allForReview.length,
|
|
18774
|
+
// what actually reaches the reviewer
|
|
18775
|
+
sent: codeDelta.files.length,
|
|
18776
|
+
// the two silent narrowings, counted separately so they can be told apart
|
|
18777
|
+
capped_out: actionSummary?.capped_out?.length ?? 0,
|
|
18778
|
+
excluded: (codeDelta.excluded ?? []).length,
|
|
18779
|
+
excluded_by_reason: excludedByReason,
|
|
18780
|
+
// was the transcript itself truncated? The 256 KB window means "this turn"
|
|
18781
|
+
// can quietly mean "the last 256 KB of it".
|
|
18782
|
+
transcript_windowed: actionSummary?.transcript_windowed ?? null
|
|
18783
|
+
};
|
|
18505
18784
|
const requestBody = {
|
|
18785
|
+
coverage_telemetry: coverageTelemetry,
|
|
18506
18786
|
static_results: staticResults,
|
|
18507
18787
|
code_delta: codeDelta,
|
|
18508
18788
|
changed_files: allForReview,
|
|
@@ -18588,6 +18868,54 @@ async function runAnalyze(opts, globals) {
|
|
|
18588
18868
|
// replaced a population floor with ≈35% power that was sub-integer for
|
|
18589
18869
|
// three-quarters of the fleet.
|
|
18590
18870
|
conservation: foldConservation,
|
|
18871
|
+
// ⚠ VRT-52 — RECORDED, NOT APPLIED. The number nobody has.
|
|
18872
|
+
//
|
|
18873
|
+
// The whole "task-scoped delta" design space rests on an assumption that
|
|
18874
|
+
// has been observed exactly ONCE: that delta files routinely belong to
|
|
18875
|
+
// earlier work. Three designs were built on it and all three were killed
|
|
18876
|
+
// adversarially — two by measurement — so before another is attempted,
|
|
18877
|
+
// measure the base rate.
|
|
18878
|
+
//
|
|
18879
|
+
// `authored_under_earlier_goal` counts delta paths whose LAST authorship
|
|
18880
|
+
// event precedes the seq of the goal now in force. Both numbers come from
|
|
18881
|
+
// the same append-only counter (`nextSeq`), so the comparison is exact.
|
|
18882
|
+
//
|
|
18883
|
+
// Keyed on the GOAL, deliberately, not on the task id. The task classifier
|
|
18884
|
+
// reported `is_new_task` on two consecutive turns of one task 25 seconds
|
|
18885
|
+
// apart, so a task-keyed number would measure its unreliability rather
|
|
18886
|
+
// than the phenomenon. And this only became meaningful once `recordGoal`
|
|
18887
|
+
// stopped letting a bare "ok" supersede the goal — before that the seq
|
|
18888
|
+
// advanced every turn and this would have degenerated to "not edited this
|
|
18889
|
+
// turn", which is the exact mistake that sank one of the three designs.
|
|
18890
|
+
//
|
|
18891
|
+
// Changes no payload the reviewer sees, no narrowing, no verdict.
|
|
18892
|
+
vrt52: (() => {
|
|
18893
|
+
const goalSeq = memory?.projection.goal?.seq;
|
|
18894
|
+
if (goalSeq === void 0 || !memorySession) return { known: false };
|
|
18895
|
+
const lastSeq2 = new Map(
|
|
18896
|
+
foldDossier(memorySession.d).authored_all.map((a) => [a.path, a.last_seq])
|
|
18897
|
+
);
|
|
18898
|
+
let earlier = 0;
|
|
18899
|
+
let unknown = 0;
|
|
18900
|
+
for (const f of codeDelta.files) {
|
|
18901
|
+
const seen = lastSeq2.get(f.path);
|
|
18902
|
+
if (seen === void 0) unknown++;
|
|
18903
|
+
else if (seen < goalSeq) earlier++;
|
|
18904
|
+
}
|
|
18905
|
+
return {
|
|
18906
|
+
known: true,
|
|
18907
|
+
goal_seq: goalSeq,
|
|
18908
|
+
delta: codeDelta.files.length,
|
|
18909
|
+
// Files this delta carries that were last written under an EARLIER
|
|
18910
|
+
// instruction. If this stays near zero, VRT-52's code half is
|
|
18911
|
+
// unnecessary and should be closed saying so.
|
|
18912
|
+
authored_under_earlier_goal: earlier,
|
|
18913
|
+
// Delta files the dossier has no authorship record for at all —
|
|
18914
|
+
// pre-existing tree state, or an authorship channel the fold cannot
|
|
18915
|
+
// see. Reported separately so a blind spot is never counted as a zero.
|
|
18916
|
+
no_authorship_record: unknown
|
|
18917
|
+
};
|
|
18918
|
+
})(),
|
|
18591
18919
|
// P2 — RECORDED, NOT APPLIED. What this run WOULD have reviewed if it
|
|
18592
18920
|
// narrowed to the within-session increment: what changed since the last
|
|
18593
18921
|
// VERDICT rather than since task start.
|
|
@@ -18620,7 +18948,20 @@ async function runAnalyze(opts, globals) {
|
|
|
18620
18948
|
const intentContext = {};
|
|
18621
18949
|
if (conversation && conversation.prompts.length > 0) {
|
|
18622
18950
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
18623
|
-
|
|
18951
|
+
const goalPrompt = resolveGoalPrompt(conversation.prompts) ?? { entry: latest, turnsBack: 0 };
|
|
18952
|
+
intentContext.user_prompt = goalPrompt.entry.prompt;
|
|
18953
|
+
if (isContinuationPrompt(intentContext.user_prompt)) {
|
|
18954
|
+
const carried = memory?.projection.goal?.text;
|
|
18955
|
+
if (carried && !isContinuationPrompt(carried)) {
|
|
18956
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
18957
|
+
intentContext.user_prompt = carried;
|
|
18958
|
+
logEvent("goal_from_dossier", { chars: carried.length });
|
|
18959
|
+
}
|
|
18960
|
+
}
|
|
18961
|
+
if (goalPrompt.turnsBack > 0) {
|
|
18962
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
18963
|
+
logEvent("goal_walked_back", { turns_back: goalPrompt.turnsBack });
|
|
18964
|
+
}
|
|
18624
18965
|
intentContext.session_id = latest.session_id || void 0;
|
|
18625
18966
|
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
18626
18967
|
if (conversation.prompts.length > 1) {
|
|
@@ -18712,6 +19053,85 @@ async function runAnalyze(opts, globals) {
|
|
|
18712
19053
|
const response = result.data;
|
|
18713
19054
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
18714
19055
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
19056
|
+
let openElsewhere = [];
|
|
19057
|
+
if (memorySession) {
|
|
19058
|
+
try {
|
|
19059
|
+
const st = foldDossier(memorySession.d);
|
|
19060
|
+
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
19061
|
+
try {
|
|
19062
|
+
const src = (0, import_node_fs24.readFileSync)((0, import_node_path20.join)(repoRoot(), file), "utf8").split("\n");
|
|
19063
|
+
const at = src[line - 1];
|
|
19064
|
+
return at === void 0 ? null : lineSha(at);
|
|
19065
|
+
} catch {
|
|
19066
|
+
return null;
|
|
19067
|
+
}
|
|
19068
|
+
});
|
|
19069
|
+
} catch {
|
|
19070
|
+
}
|
|
19071
|
+
}
|
|
19072
|
+
const reviewCoverage = {
|
|
19073
|
+
reviewed: sentPaths,
|
|
19074
|
+
// Declared drops from the stages that DO report themselves today. The other
|
|
19075
|
+
// stages surface via `unaccounted`, which is the tripwire, not the design.
|
|
19076
|
+
notReviewed: [
|
|
19077
|
+
// Every exit from the collection loop, each named. Six reasons where there
|
|
19078
|
+
// used to be two recorded and four silent — the silent ones including the
|
|
19079
|
+
// per-file size cap, which could drop a whole source file without leaving a
|
|
19080
|
+
// trace anywhere in the payload or the run row.
|
|
19081
|
+
...codeDelta.excluded,
|
|
19082
|
+
// The server-side 300-line middle-out truncation. It only bites on the
|
|
19083
|
+
// full-file branch (a first analysis, before snapshots exist) because
|
|
19084
|
+
// analyze normally sends diffs — but on that branch the reviewer sees the
|
|
19085
|
+
// first and last 100 lines and nothing between, and until now said so to
|
|
19086
|
+
// nobody. CAPACITY: a partial look is not a look.
|
|
19087
|
+
...(response.metadata?.truncated_files ?? []).map((path) => ({
|
|
19088
|
+
path,
|
|
19089
|
+
reason: "file-middle-truncated-300-lines",
|
|
19090
|
+
stage: "prompt-builder",
|
|
19091
|
+
kind: "capacity"
|
|
19092
|
+
})),
|
|
19093
|
+
// The 20-entry edit cap. CAPACITY, and the sharpest of the lot: it narrows
|
|
19094
|
+
// what is REVIEWED, not merely what is summarised — a session editing 25
|
|
19095
|
+
// files had five silently excluded from the reviewed set.
|
|
19096
|
+
...(actionSummary?.capped_out ?? []).map((path) => ({
|
|
19097
|
+
path,
|
|
19098
|
+
reason: "edit-list-cap-20",
|
|
19099
|
+
stage: "extractActionSummary",
|
|
19100
|
+
kind: "capacity"
|
|
19101
|
+
})),
|
|
19102
|
+
// ⚠ BASELINE SCOPING — the biggest source of false NOT A CLEAN REVIEW.
|
|
19103
|
+
//
|
|
19104
|
+
// The universe is `allChanged`, git's whole dirty tree. The reviewed set is
|
|
19105
|
+
// scoped to what THIS SESSION authored (the VRT-26 contamination cure), so
|
|
19106
|
+
// every pre-existing dirty file is in the universe, absent from `reviewed`,
|
|
19107
|
+
// and — until now — declared by nobody. It fell through to `unaccounted`,
|
|
19108
|
+
// became capacity, and produced "NOT A CLEAN REVIEW: admin.js" over a file
|
|
19109
|
+
// that was never this session's to review.
|
|
19110
|
+
//
|
|
19111
|
+
// Measured 2026-08-04: three consecutive runs over an untouched tree gave
|
|
19112
|
+
// three different answers — .claude/settings.json, then admin.js, then six
|
|
19113
|
+
// files — because each run took a different path and each path had a
|
|
19114
|
+
// different idea of the universe. POLICY: not this session's work is not a
|
|
19115
|
+
// coverage gap, it is the cure working.
|
|
19116
|
+
...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) => ({
|
|
19117
|
+
path,
|
|
19118
|
+
reason: "not-authored-this-session",
|
|
19119
|
+
stage: "baseline-scoping",
|
|
19120
|
+
kind: "policy"
|
|
19121
|
+
})),
|
|
19122
|
+
// The extension allowlist, and it is POLICY rather than capacity: a changed
|
|
19123
|
+
// README was never going to be reviewed, and treating that as a coverage
|
|
19124
|
+
// gap would downgrade nearly every PASS to WARN until WARN meant nothing.
|
|
19125
|
+
// Recorded so the ledger balances and so "what did Verity ignore entirely"
|
|
19126
|
+
// is answerable — but it never touches the verdict.
|
|
19127
|
+
...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
|
|
19128
|
+
path,
|
|
19129
|
+
reason: "not-a-reviewed-file-type",
|
|
19130
|
+
stage: "extension-allowlist",
|
|
19131
|
+
kind: "policy"
|
|
19132
|
+
}))
|
|
19133
|
+
]
|
|
19134
|
+
};
|
|
18715
19135
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18716
19136
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
18717
19137
|
let silenced = null;
|
|
@@ -18864,9 +19284,41 @@ async function runAnalyze(opts, globals) {
|
|
|
18864
19284
|
reverify_by: response.reverify_by
|
|
18865
19285
|
});
|
|
18866
19286
|
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
18867
|
-
|
|
19287
|
+
let capReleased = false;
|
|
19288
|
+
let effectiveDecision = decision;
|
|
19289
|
+
if (decision === "FAIL") {
|
|
19290
|
+
const blocking = (response.findings ?? []).filter((f) => {
|
|
19291
|
+
const sev = String(f.severity ?? "").toLowerCase();
|
|
19292
|
+
return sev === "critical" || sev === "high";
|
|
19293
|
+
});
|
|
19294
|
+
const fingerprint = findingsFingerprint(blocking);
|
|
19295
|
+
const prior = readIterationState(currentCommit);
|
|
19296
|
+
const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
|
|
19297
|
+
const nextIteration = sameProblem ? prior.iteration + 1 : 1;
|
|
19298
|
+
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
19299
|
+
writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
|
|
19300
|
+
iteration = nextIteration;
|
|
19301
|
+
if (nextIteration > maxIterations) {
|
|
19302
|
+
capReleased = true;
|
|
19303
|
+
effectiveDecision = "WARN";
|
|
19304
|
+
logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
|
|
19305
|
+
}
|
|
19306
|
+
}
|
|
19307
|
+
if (capReleased) {
|
|
19308
|
+
const findings = response.findings ?? [];
|
|
19309
|
+
const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
|
|
19310
|
+
emitVerdict({
|
|
19311
|
+
proposed: "WARN",
|
|
19312
|
+
changed: skipCoverageChanged,
|
|
19313
|
+
coverage: reviewCoverage,
|
|
19314
|
+
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.
|
|
19315
|
+
${lines.join("\n")}`,
|
|
19316
|
+
agentContext: null,
|
|
19317
|
+
silenced: true
|
|
19318
|
+
});
|
|
19319
|
+
}
|
|
19320
|
+
switch (effectiveDecision) {
|
|
18868
19321
|
case "FAIL": {
|
|
18869
|
-
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
18870
19322
|
const assessment = response.assessment;
|
|
18871
19323
|
const narrative = assessment?.narrative ?? "";
|
|
18872
19324
|
const findings = response.findings ?? [];
|
|
@@ -18943,7 +19395,19 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18943
19395
|
if (grantNudge) process.stderr.write(`
|
|
18944
19396
|
${YELLOW}${grantNudge.trim()}${NC}
|
|
18945
19397
|
`);
|
|
18946
|
-
|
|
19398
|
+
emitVerdict({
|
|
19399
|
+
proposed: "FAIL",
|
|
19400
|
+
changed: skipCoverageChanged,
|
|
19401
|
+
coverage: reviewCoverage,
|
|
19402
|
+
userSummary: "",
|
|
19403
|
+
// Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
|
|
19404
|
+
// the findings themselves are rendered above by the blocking renderer,
|
|
19405
|
+
// so what the cut removes is the repeated commentary, never the defect.
|
|
19406
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19407
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19408
|
+
silenced: !!silenced,
|
|
19409
|
+
openElsewhere
|
|
19410
|
+
});
|
|
18947
19411
|
break;
|
|
18948
19412
|
}
|
|
18949
19413
|
case "PASS": {
|
|
@@ -18955,10 +19419,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18955
19419
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18956
19420
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18957
19421
|
userSummary += loginNudge + grantNudge;
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
19422
|
+
emitVerdict({
|
|
19423
|
+
proposed: "PASS",
|
|
19424
|
+
changed: skipCoverageChanged,
|
|
19425
|
+
coverage: reviewCoverage,
|
|
19426
|
+
userSummary,
|
|
19427
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19428
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19429
|
+
silenced: !!silenced,
|
|
19430
|
+
openElsewhere
|
|
19431
|
+
});
|
|
18962
19432
|
break;
|
|
18963
19433
|
}
|
|
18964
19434
|
case "WARN": {
|
|
@@ -18969,10 +19439,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18969
19439
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18970
19440
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18971
19441
|
userSummary += loginNudge + grantNudge;
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
19442
|
+
emitVerdict({
|
|
19443
|
+
proposed: "WARN",
|
|
19444
|
+
changed: skipCoverageChanged,
|
|
19445
|
+
coverage: reviewCoverage,
|
|
19446
|
+
userSummary,
|
|
19447
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19448
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19449
|
+
silenced: !!silenced,
|
|
19450
|
+
openElsewhere
|
|
19451
|
+
});
|
|
18976
19452
|
break;
|
|
18977
19453
|
}
|
|
18978
19454
|
default: {
|
|
@@ -19085,8 +19561,8 @@ async function runReview(opts, globals) {
|
|
|
19085
19561
|
for (const p of specPaths) {
|
|
19086
19562
|
if (!(0, import_node_fs26.existsSync)(p)) continue;
|
|
19087
19563
|
try {
|
|
19088
|
-
const { readFileSync:
|
|
19089
|
-
const content =
|
|
19564
|
+
const { readFileSync: readFileSync16 } = await import("node:fs");
|
|
19565
|
+
const content = readFileSync16(p, "utf-8");
|
|
19090
19566
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
19091
19567
|
} catch {
|
|
19092
19568
|
}
|
|
@@ -20772,7 +21248,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20772
21248
|
}
|
|
20773
21249
|
|
|
20774
21250
|
// src/cli.ts
|
|
20775
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
21251
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.4410c26").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
21252
|
try {
|
|
20777
21253
|
await foldLegacyLocalCredential();
|
|
20778
21254
|
} catch {
|