@codacy/verity-cli 0.28.1-experimental.4da2503 → 0.28.1-experimental.6eac6aa
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 +646 -102
- 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");
|
|
@@ -13960,6 +14023,12 @@ function reduce(state, events, now) {
|
|
|
13960
14023
|
}
|
|
13961
14024
|
case "verdict": {
|
|
13962
14025
|
state.meta.last_verdict_seq = ev.seq;
|
|
14026
|
+
state.meta.channel = {
|
|
14027
|
+
emittedLast: ev.emitted === true,
|
|
14028
|
+
// Reset by ANY movement, so the counter measures a standstill rather
|
|
14029
|
+
// than session length.
|
|
14030
|
+
consecutiveIdle: ev.idle === false ? 0 : (state.meta.channel?.consecutiveIdle ?? 0) + 1
|
|
14031
|
+
};
|
|
13963
14032
|
state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
|
|
13964
14033
|
if (ev.intent_sig) {
|
|
13965
14034
|
state.meta.intent_repeat = state.meta.intent_repeat && state.meta.intent_repeat.sig === ev.intent_sig ? { sig: ev.intent_sig, consecutive: state.meta.intent_repeat.consecutive + 1 } : { sig: ev.intent_sig, consecutive: 1 };
|
|
@@ -14327,6 +14396,10 @@ function projectMemory(state, opts) {
|
|
|
14327
14396
|
...active.delivered && { delivered: active.delivered }
|
|
14328
14397
|
};
|
|
14329
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
|
+
}
|
|
14330
14403
|
if (state.meta.last_adjudication) {
|
|
14331
14404
|
const a = state.meta.last_adjudication;
|
|
14332
14405
|
p.last_adjudication = { verdict: a.verdict, score: a.score, at: a.at };
|
|
@@ -14573,7 +14646,8 @@ function recall(d, input) {
|
|
|
14573
14646
|
continuity,
|
|
14574
14647
|
spoken: reanchored.spoken,
|
|
14575
14648
|
refused: reanchored.dropped.length,
|
|
14576
|
-
lastVerdictSeq
|
|
14649
|
+
lastVerdictSeq,
|
|
14650
|
+
...input.capture && { capture: input.capture }
|
|
14577
14651
|
});
|
|
14578
14652
|
return {
|
|
14579
14653
|
state: effective,
|
|
@@ -14684,7 +14758,19 @@ function sessionDossier(token, sessionId) {
|
|
|
14684
14758
|
const d = openDossier(identity);
|
|
14685
14759
|
return d ? { d, identity } : null;
|
|
14686
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
|
+
}
|
|
14687
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
|
+
}
|
|
14688
14774
|
const text = prompt.slice(0, MAX_GOAL_CHARS);
|
|
14689
14775
|
appendEvent(d, {
|
|
14690
14776
|
k: "goal",
|
|
@@ -14800,6 +14886,8 @@ function recordVerdict(d, v) {
|
|
|
14800
14886
|
branch: v.branch,
|
|
14801
14887
|
decision: v.decision,
|
|
14802
14888
|
...sig && { intent_sig: sig },
|
|
14889
|
+
emitted: v.emitted === true,
|
|
14890
|
+
idle: v.idle !== false,
|
|
14803
14891
|
...v.intent?.verdict && { intent_verdict: v.intent.verdict },
|
|
14804
14892
|
...typeof v.intent?.score === "number" && { intent_score: v.intent.score }
|
|
14805
14893
|
});
|
|
@@ -14846,7 +14934,9 @@ function recallMemory(d, identity, opts) {
|
|
|
14846
14934
|
const state = foldDossier(d);
|
|
14847
14935
|
const watermark = state.meta.watermark?.sha ?? null;
|
|
14848
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;
|
|
14849
14938
|
const r = recall(d, {
|
|
14939
|
+
...captureCmp && { capture: captureCmp },
|
|
14850
14940
|
identity,
|
|
14851
14941
|
currentSessionKey: opts.currentSessionKey,
|
|
14852
14942
|
branchNow: getCurrentBranch(),
|
|
@@ -15103,6 +15193,8 @@ function collectCodeDelta(files, opts) {
|
|
|
15103
15193
|
let totalSize = 0;
|
|
15104
15194
|
let truncationReason = null;
|
|
15105
15195
|
const droppedPaths = [];
|
|
15196
|
+
const excluded = [];
|
|
15197
|
+
const exclude = (path, reason) => excluded.push({ path, reason, stage: "collectCodeDelta", kind: "capacity" });
|
|
15106
15198
|
for (const filepath of sorted) {
|
|
15107
15199
|
if (result.length >= maxFiles) {
|
|
15108
15200
|
truncationReason ??= "max_files";
|
|
@@ -15110,14 +15202,21 @@ function collectCodeDelta(files, opts) {
|
|
|
15110
15202
|
continue;
|
|
15111
15203
|
}
|
|
15112
15204
|
const resolved = resolveFile(filepath);
|
|
15113
|
-
if (!resolved)
|
|
15205
|
+
if (!resolved) {
|
|
15206
|
+
exclude(filepath, "path-not-resolvable");
|
|
15207
|
+
continue;
|
|
15208
|
+
}
|
|
15114
15209
|
let size;
|
|
15115
15210
|
try {
|
|
15116
15211
|
size = (0, import_node_fs11.statSync)(resolved).size;
|
|
15117
15212
|
} catch {
|
|
15213
|
+
exclude(filepath, "not-stattable");
|
|
15214
|
+
continue;
|
|
15215
|
+
}
|
|
15216
|
+
if (size > maxFileBytes) {
|
|
15217
|
+
exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
|
|
15118
15218
|
continue;
|
|
15119
15219
|
}
|
|
15120
|
-
if (size > maxFileBytes) continue;
|
|
15121
15220
|
if (totalSize + size > maxTotalBytes) {
|
|
15122
15221
|
truncationReason ??= "max_total_bytes";
|
|
15123
15222
|
const idx = sorted.indexOf(filepath);
|
|
@@ -15128,6 +15227,7 @@ function collectCodeDelta(files, opts) {
|
|
|
15128
15227
|
try {
|
|
15129
15228
|
content = (0, import_node_fs11.readFileSync)(resolved, "utf-8");
|
|
15130
15229
|
} catch {
|
|
15230
|
+
exclude(filepath, "not-readable");
|
|
15131
15231
|
continue;
|
|
15132
15232
|
}
|
|
15133
15233
|
totalSize += size;
|
|
@@ -15141,10 +15241,14 @@ function collectCodeDelta(files, opts) {
|
|
|
15141
15241
|
(sum, f) => sum + f.content.split("\n").length,
|
|
15142
15242
|
0
|
|
15143
15243
|
);
|
|
15244
|
+
for (const path of droppedPaths) {
|
|
15245
|
+
exclude(path, truncationReason === "max_files" ? "max-files-cap" : "max-total-bytes-cap");
|
|
15246
|
+
}
|
|
15144
15247
|
return {
|
|
15145
15248
|
files: result,
|
|
15146
15249
|
total_lines: totalLines,
|
|
15147
15250
|
total_files: result.length,
|
|
15251
|
+
excluded,
|
|
15148
15252
|
...truncationReason && {
|
|
15149
15253
|
truncated: {
|
|
15150
15254
|
reason: truncationReason,
|
|
@@ -15438,6 +15542,34 @@ ${addedLines}`,
|
|
|
15438
15542
|
}
|
|
15439
15543
|
return { diffs, has_baseline: true };
|
|
15440
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
|
+
}
|
|
15441
15573
|
function changedSinceBaseline(repoRelPath, baseline) {
|
|
15442
15574
|
const pre = preImage(repoRelPath, baseline);
|
|
15443
15575
|
let current;
|
|
@@ -16314,40 +16446,40 @@ function narrowToRecent(files, sessionId) {
|
|
|
16314
16446
|
});
|
|
16315
16447
|
return recent.length > 0 ? recent : files;
|
|
16316
16448
|
}
|
|
16317
|
-
function
|
|
16318
|
-
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 };
|
|
16319
16451
|
try {
|
|
16320
16452
|
const stored = (0, import_node_fs15.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16321
16453
|
const parts = stored.split(":");
|
|
16322
16454
|
const iter = parseInt(parts[0], 10);
|
|
16323
16455
|
const storedCommit = parts[1] ?? "";
|
|
16324
16456
|
const storedTimestamp = parseInt(parts[2] ?? "0", 10);
|
|
16325
|
-
|
|
16326
|
-
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 };
|
|
16327
16460
|
if (storedTimestamp > 0) {
|
|
16328
16461
|
const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
|
|
16329
|
-
if (elapsed > 600) return 1;
|
|
16462
|
+
if (elapsed > 600) return { iteration: 1, fingerprint: null };
|
|
16330
16463
|
}
|
|
16331
|
-
return iter;
|
|
16464
|
+
return { iteration: iter, fingerprint };
|
|
16332
16465
|
} catch {
|
|
16333
|
-
return 1;
|
|
16466
|
+
return { iteration: 1, fingerprint: null };
|
|
16334
16467
|
}
|
|
16335
16468
|
}
|
|
16336
|
-
function
|
|
16337
|
-
const
|
|
16338
|
-
|
|
16339
|
-
writeIteration(1, currentCommit, contentHash);
|
|
16340
|
-
return {
|
|
16341
|
-
skip: `Max Verity iterations (${maxIterations}) reached \u2014 accepting to prevent infinite loop. Human review required before deploying.`,
|
|
16342
|
-
iteration
|
|
16343
|
-
};
|
|
16344
|
-
}
|
|
16345
|
-
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(",");
|
|
16346
16472
|
}
|
|
16347
|
-
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) {
|
|
16348
16479
|
(0, import_node_fs15.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16349
16480
|
const ts = Math.floor(Date.now() / 1e3);
|
|
16350
|
-
|
|
16481
|
+
const fp = fingerprint ? `:${fingerprint}` : "";
|
|
16482
|
+
(0, import_node_fs15.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
|
|
16351
16483
|
}
|
|
16352
16484
|
|
|
16353
16485
|
// src/lib/static-analysis.ts
|
|
@@ -16596,7 +16728,7 @@ function resolveTaskContext(opts) {
|
|
|
16596
16728
|
// src/lib/cli-version.ts
|
|
16597
16729
|
function cliVersion() {
|
|
16598
16730
|
try {
|
|
16599
|
-
return true ? "0.28.1-experimental.
|
|
16731
|
+
return true ? "0.28.1-experimental.6eac6aa" : "dev";
|
|
16600
16732
|
} catch {
|
|
16601
16733
|
return "dev";
|
|
16602
16734
|
}
|
|
@@ -16822,6 +16954,15 @@ var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
|
16822
16954
|
]);
|
|
16823
16955
|
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit", "MultiEdit"]);
|
|
16824
16956
|
var DISPATCH_TOOLS = /* @__PURE__ */ new Set(["Task", "Workflow"]);
|
|
16957
|
+
function hasUserText(record) {
|
|
16958
|
+
const message = record.message;
|
|
16959
|
+
const content = message?.content ?? record.content;
|
|
16960
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
16961
|
+
if (!Array.isArray(content)) return false;
|
|
16962
|
+
return content.some(
|
|
16963
|
+
(b) => b?.type === "text" && typeof b.text === "string" && b.text.trim().length > 0
|
|
16964
|
+
);
|
|
16965
|
+
}
|
|
16825
16966
|
var COMMAND_CLASSES = [
|
|
16826
16967
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?test\b|\bvitest\b|\bjest\b|\bpytest\b|\bgo test\b/, "test"],
|
|
16827
16968
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?build\b|\btsc\b|\bwebpack\b|\bcargo build\b/, "build"],
|
|
@@ -16949,6 +17090,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16949
17090
|
malformed: 0,
|
|
16950
17091
|
subagentFiles: 0,
|
|
16951
17092
|
dispatched: 0,
|
|
17093
|
+
userMessages: 0,
|
|
16952
17094
|
subagentSkipped: 0,
|
|
16953
17095
|
compactions: 0,
|
|
16954
17096
|
complete: false
|
|
@@ -16975,6 +17117,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16975
17117
|
if (type === "system" && record.subtype === "compact_boundary") {
|
|
16976
17118
|
result.coverage.compactions++;
|
|
16977
17119
|
}
|
|
17120
|
+
if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
|
|
16978
17121
|
collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot, result.coverage);
|
|
16979
17122
|
}
|
|
16980
17123
|
};
|
|
@@ -17130,6 +17273,86 @@ function checkConservation(changedFiles, result, repoRoot2) {
|
|
|
17130
17273
|
};
|
|
17131
17274
|
}
|
|
17132
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
|
+
|
|
17133
17356
|
// src/lib/channel.ts
|
|
17134
17357
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
17135
17358
|
var MAX_AGENT_ITEMS = 5;
|
|
@@ -17179,6 +17402,7 @@ function buildAgentContext(input) {
|
|
|
17179
17402
|
}
|
|
17180
17403
|
for (const p of input.pendingItems ?? []) {
|
|
17181
17404
|
if (lines.length >= MAX_AGENT_ITEMS) break;
|
|
17405
|
+
if (p.pattern_id === "intent-misalignment") continue;
|
|
17182
17406
|
const text = p.description ?? p.title ?? p.reason;
|
|
17183
17407
|
if (!text) continue;
|
|
17184
17408
|
lines.push(renderItem("", text, p.pattern_id, p.file, p.line));
|
|
@@ -17208,6 +17432,47 @@ function buildHookOutput(gateDecision, systemMessage, agentContext) {
|
|
|
17208
17432
|
} : {}
|
|
17209
17433
|
};
|
|
17210
17434
|
}
|
|
17435
|
+
var IDLE_EPISODE_CAP = 3;
|
|
17436
|
+
function channelSilence(input) {
|
|
17437
|
+
const movedSomething = input.newUserPrompt || input.newAuthorship;
|
|
17438
|
+
if (movedSomething) return null;
|
|
17439
|
+
if (input.consecutiveIdle >= IDLE_EPISODE_CAP) return "idle-episode-cap";
|
|
17440
|
+
if (input.emittedLast) return "caused-by-our-own-emission";
|
|
17441
|
+
return null;
|
|
17442
|
+
}
|
|
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
|
+
}
|
|
17211
17476
|
|
|
17212
17477
|
// src/lib/cache-cleanup.ts
|
|
17213
17478
|
var import_node_fs21 = require("node:fs");
|
|
@@ -17388,46 +17653,6 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
17388
17653
|
return false;
|
|
17389
17654
|
}
|
|
17390
17655
|
|
|
17391
|
-
// src/lib/skip-detection.ts
|
|
17392
|
-
function isBareAckPrompt(prompt) {
|
|
17393
|
-
if (typeof prompt !== "string") return false;
|
|
17394
|
-
const trimmed = prompt.trim();
|
|
17395
|
-
if (trimmed.length === 0) return false;
|
|
17396
|
-
if (trimmed.length > 20) return false;
|
|
17397
|
-
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;
|
|
17398
|
-
return bareAckPattern.test(trimmed);
|
|
17399
|
-
}
|
|
17400
|
-
function isReflectionQuestion(response) {
|
|
17401
|
-
if (!response || typeof response !== "string") return false;
|
|
17402
|
-
const markers = [
|
|
17403
|
-
/reflection\s+for\s+future\s+agents/i,
|
|
17404
|
-
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
17405
|
-
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
17406
|
-
/quick\s+reflection\s+question/i,
|
|
17407
|
-
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
17408
|
-
// interactive, asks the user to confirm/correct before recording. That
|
|
17409
|
-
// turn authors no code either, so it's still a reflection turn.
|
|
17410
|
-
/reflection\s+draft/i,
|
|
17411
|
-
/confirm,?\s+correct,?\s+or\s+add/i
|
|
17412
|
-
];
|
|
17413
|
-
return markers.some((m) => m.test(response));
|
|
17414
|
-
}
|
|
17415
|
-
function isMetaTaskLabel(label2) {
|
|
17416
|
-
if (label2 === null || label2 === void 0) return false;
|
|
17417
|
-
if (typeof label2 !== "string") return false;
|
|
17418
|
-
const trimmed = label2.trim();
|
|
17419
|
-
if (trimmed.length === 0) return true;
|
|
17420
|
-
const metaPatterns = [
|
|
17421
|
-
/^verity\s+[\w-]+\s+response$/i,
|
|
17422
|
-
// "Verity reflect response"
|
|
17423
|
-
/^simple user response$/i,
|
|
17424
|
-
/^verity\s+command$/i,
|
|
17425
|
-
// "Verity command"
|
|
17426
|
-
/^user\s+(question|reply|response|ack)$/i
|
|
17427
|
-
];
|
|
17428
|
-
return metaPatterns.some((p) => p.test(trimmed));
|
|
17429
|
-
}
|
|
17430
|
-
|
|
17431
17656
|
// src/lib/transcript.ts
|
|
17432
17657
|
var import_node_fs22 = require("node:fs");
|
|
17433
17658
|
var MAX_READ_BYTES = 256 * 1024;
|
|
@@ -17441,9 +17666,11 @@ var MAX_SUMMARY_BYTES = 4096;
|
|
|
17441
17666
|
var HOME = process.env.HOME ?? "";
|
|
17442
17667
|
async function extractActionSummary(transcriptPath) {
|
|
17443
17668
|
try {
|
|
17444
|
-
const
|
|
17445
|
-
if (!
|
|
17446
|
-
|
|
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;
|
|
17447
17674
|
} catch {
|
|
17448
17675
|
return null;
|
|
17449
17676
|
}
|
|
@@ -17457,9 +17684,11 @@ function readTurnLines(transcriptPath) {
|
|
|
17457
17684
|
}
|
|
17458
17685
|
if (size === 0) return null;
|
|
17459
17686
|
let raw;
|
|
17687
|
+
let windowed = false;
|
|
17460
17688
|
if (size <= SMALL_FILE_BYTES) {
|
|
17461
17689
|
raw = (0, import_node_fs22.readFileSync)(transcriptPath, "utf-8");
|
|
17462
17690
|
} else {
|
|
17691
|
+
windowed = true;
|
|
17463
17692
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
17464
17693
|
const fd = require("node:fs").openSync(transcriptPath, "r");
|
|
17465
17694
|
try {
|
|
@@ -17477,17 +17706,22 @@ function readTurnLines(transcriptPath) {
|
|
|
17477
17706
|
const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
17478
17707
|
if (allLines.length === 0) return null;
|
|
17479
17708
|
let turnStart = 0;
|
|
17709
|
+
let boundaryFound = false;
|
|
17480
17710
|
for (let i = allLines.length - 1; i >= 0; i--) {
|
|
17481
17711
|
try {
|
|
17482
17712
|
const parsed = JSON.parse(allLines[i]);
|
|
17483
17713
|
if (parsed.type === "user" && isRealUserMessage(parsed)) {
|
|
17484
17714
|
turnStart = i;
|
|
17715
|
+
boundaryFound = true;
|
|
17485
17716
|
break;
|
|
17486
17717
|
}
|
|
17487
17718
|
} catch {
|
|
17488
17719
|
}
|
|
17489
17720
|
}
|
|
17490
|
-
return
|
|
17721
|
+
return {
|
|
17722
|
+
lines: allLines.slice(turnStart),
|
|
17723
|
+
window: !windowed ? "whole" : boundaryFound ? "windowed" : "orphaned"
|
|
17724
|
+
};
|
|
17491
17725
|
}
|
|
17492
17726
|
function isRealUserMessage(parsed) {
|
|
17493
17727
|
const message = parsed.message;
|
|
@@ -17591,6 +17825,13 @@ function buildSummary(lines) {
|
|
|
17591
17825
|
files_read: capArray(filesRead, MAX_FILES_LIST),
|
|
17592
17826
|
files_edited: capArray(filesEdited, MAX_FILES_LIST),
|
|
17593
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
|
+
],
|
|
17594
17835
|
searches,
|
|
17595
17836
|
commands,
|
|
17596
17837
|
subagents,
|
|
@@ -17641,6 +17882,9 @@ function sanitizeCommand(rawCmd) {
|
|
|
17641
17882
|
function capArray(set, max) {
|
|
17642
17883
|
return Array.from(set).slice(0, max);
|
|
17643
17884
|
}
|
|
17885
|
+
function cappedOut(set, max) {
|
|
17886
|
+
return Array.from(set).slice(max);
|
|
17887
|
+
}
|
|
17644
17888
|
|
|
17645
17889
|
// src/lib/run-mode.ts
|
|
17646
17890
|
function parseAutonomousEnv(raw) {
|
|
@@ -18050,12 +18294,45 @@ function agentContextFor(response, intentRepeat = 0) {
|
|
|
18050
18294
|
});
|
|
18051
18295
|
}
|
|
18052
18296
|
var beaconCtx = null;
|
|
18053
|
-
async function passAndExit(reason, skip) {
|
|
18297
|
+
async function passAndExit(reason, skip, kindOverride) {
|
|
18054
18298
|
const sent = await sendSkipBeacon(beaconCtx, skip);
|
|
18055
18299
|
logEvent("skip", { reason: skip, beacon: sent });
|
|
18056
|
-
|
|
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
|
+
);
|
|
18057
18333
|
process.exit(0);
|
|
18058
18334
|
}
|
|
18335
|
+
var skipCoverageChanged = [];
|
|
18059
18336
|
var EMPTY_STATIC = {
|
|
18060
18337
|
tool: "@codacy/analysis-cli",
|
|
18061
18338
|
findings: [],
|
|
@@ -18070,7 +18347,7 @@ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
|
18070
18347
|
}
|
|
18071
18348
|
function localOnlyAndExit(staticResults) {
|
|
18072
18349
|
printJsonCompact({
|
|
18073
|
-
gate_decision: "
|
|
18350
|
+
gate_decision: "WARN",
|
|
18074
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.",
|
|
18075
18352
|
unauthenticated: true,
|
|
18076
18353
|
static_results: staticResults
|
|
@@ -18130,6 +18407,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18130
18407
|
});
|
|
18131
18408
|
}
|
|
18132
18409
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18410
|
+
skipCoverageChanged = allChanged;
|
|
18133
18411
|
const analyzable = filterAnalyzable(allChanged);
|
|
18134
18412
|
const reviewable = filterReviewable(allChanged);
|
|
18135
18413
|
const securityFiles = filterSecurity(allChanged);
|
|
@@ -18141,14 +18419,24 @@ async function runAnalyze(opts, globals) {
|
|
|
18141
18419
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
18142
18420
|
const specs = discoverSpecs();
|
|
18143
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;
|
|
18144
18425
|
const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
|
|
18145
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
|
+
}
|
|
18146
18435
|
await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
|
|
18147
18436
|
}
|
|
18148
|
-
if (
|
|
18437
|
+
if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
|
|
18149
18438
|
await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
|
|
18150
18439
|
}
|
|
18151
|
-
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18152
18440
|
if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
|
|
18153
18441
|
await passAndExit("Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
|
|
18154
18442
|
}
|
|
@@ -18195,7 +18483,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18195
18483
|
);
|
|
18196
18484
|
}
|
|
18197
18485
|
if (analysisMode === "skip") {
|
|
18198
|
-
await passAndExit(
|
|
18486
|
+
await passAndExit(
|
|
18487
|
+
"Skip mode \u2014 no code work to analyze",
|
|
18488
|
+
"skip-mode",
|
|
18489
|
+
turnAuthoredCode ? "capacity" : void 0
|
|
18490
|
+
);
|
|
18199
18491
|
}
|
|
18200
18492
|
let staticResults = {
|
|
18201
18493
|
tool: "@codacy/analysis-cli",
|
|
@@ -18205,7 +18497,8 @@ async function runAnalyze(opts, globals) {
|
|
|
18205
18497
|
let codeDelta = {
|
|
18206
18498
|
files: [],
|
|
18207
18499
|
total_lines: 0,
|
|
18208
|
-
total_files: 0
|
|
18500
|
+
total_files: 0,
|
|
18501
|
+
excluded: []
|
|
18209
18502
|
};
|
|
18210
18503
|
let snapshotResult = { has_snapshots: false, diffs: [] };
|
|
18211
18504
|
let contentHash = null;
|
|
@@ -18270,7 +18563,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18270
18563
|
if (assistantResponse) {
|
|
18271
18564
|
analysisMode = "plan";
|
|
18272
18565
|
} else {
|
|
18273
|
-
await passAndExit(
|
|
18566
|
+
await passAndExit(
|
|
18567
|
+
"No files within size limits to analyze",
|
|
18568
|
+
"size-limit",
|
|
18569
|
+
codeDelta.excluded.length > 0 ? "capacity" : "policy"
|
|
18570
|
+
);
|
|
18274
18571
|
}
|
|
18275
18572
|
}
|
|
18276
18573
|
}
|
|
@@ -18283,19 +18580,13 @@ async function runAnalyze(opts, globals) {
|
|
|
18283
18580
|
snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
18284
18581
|
}
|
|
18285
18582
|
currentCommit = getCurrentCommit();
|
|
18286
|
-
|
|
18287
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
18288
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18289
|
-
iteration = iterResult.iteration;
|
|
18583
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18290
18584
|
}
|
|
18291
18585
|
}
|
|
18292
18586
|
if (analysisMode === "plan") {
|
|
18293
18587
|
recordAnalysisStart();
|
|
18294
18588
|
currentCommit = getCurrentCommit();
|
|
18295
|
-
|
|
18296
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations);
|
|
18297
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18298
|
-
iteration = iterResult.iteration;
|
|
18589
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18299
18590
|
}
|
|
18300
18591
|
const contextFiles = gatherContextFiles(contextFilePaths, codeDelta.files);
|
|
18301
18592
|
for (const f of codeDelta.files) {
|
|
@@ -18444,7 +18735,10 @@ async function runAnalyze(opts, globals) {
|
|
|
18444
18735
|
priorState.capabilities
|
|
18445
18736
|
);
|
|
18446
18737
|
memory = recallMemory(memorySession.d, memorySession.identity, {
|
|
18447
|
-
currentSessionKey: memorySession.identity.sessionKey
|
|
18738
|
+
currentSessionKey: memorySession.identity.sessionKey,
|
|
18739
|
+
// The independent witness. Only meaningful when a transcript was folded —
|
|
18740
|
+
// otherwise it stays undefined and capture coverage reads as UNKNOWN.
|
|
18741
|
+
...foldResult && { userMessagesSeen: foldResult.coverage.userMessages }
|
|
18448
18742
|
});
|
|
18449
18743
|
if (memory && !memory.provenanceHolds) {
|
|
18450
18744
|
process.stderr.write("Verity: working-memory provenance check failed \u2014 recall suppressed.\n");
|
|
@@ -18465,7 +18759,30 @@ async function runAnalyze(opts, globals) {
|
|
|
18465
18759
|
hasUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
18466
18760
|
isTTY: process.stdout.isTTY === true
|
|
18467
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
|
+
};
|
|
18468
18784
|
const requestBody = {
|
|
18785
|
+
coverage_telemetry: coverageTelemetry,
|
|
18469
18786
|
static_results: staticResults,
|
|
18470
18787
|
code_delta: codeDelta,
|
|
18471
18788
|
changed_files: allForReview,
|
|
@@ -18551,6 +18868,54 @@ async function runAnalyze(opts, globals) {
|
|
|
18551
18868
|
// replaced a population floor with ≈35% power that was sub-integer for
|
|
18552
18869
|
// three-quarters of the fleet.
|
|
18553
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
|
+
})(),
|
|
18554
18919
|
// P2 — RECORDED, NOT APPLIED. What this run WOULD have reviewed if it
|
|
18555
18920
|
// narrowed to the within-session increment: what changed since the last
|
|
18556
18921
|
// VERDICT rather than since task start.
|
|
@@ -18583,7 +18948,20 @@ async function runAnalyze(opts, globals) {
|
|
|
18583
18948
|
const intentContext = {};
|
|
18584
18949
|
if (conversation && conversation.prompts.length > 0) {
|
|
18585
18950
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
18586
|
-
|
|
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
|
+
}
|
|
18587
18965
|
intentContext.session_id = latest.session_id || void 0;
|
|
18588
18966
|
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
18589
18967
|
if (conversation.prompts.length > 1) {
|
|
@@ -18675,8 +19053,114 @@ async function runAnalyze(opts, globals) {
|
|
|
18675
19053
|
const response = result.data;
|
|
18676
19054
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
18677
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
|
+
};
|
|
18678
19135
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18679
19136
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
19137
|
+
let silenced = null;
|
|
19138
|
+
let turnIsIdleForChannel = true;
|
|
19139
|
+
if (memorySession) {
|
|
19140
|
+
try {
|
|
19141
|
+
const st = foldDossier(memorySession.d);
|
|
19142
|
+
turnIsIdleForChannel = st.authored_all.every((a) => a.hash_now === a.hash_at_last_verdict);
|
|
19143
|
+
silenced = channelSilence({
|
|
19144
|
+
// The BUFFER, not intentContext.user_prompt: the latter falls back to a
|
|
19145
|
+
// linked issue (VRT-53 W4) when no human spoke, and a fallback goal is
|
|
19146
|
+
// not a user utterance. Treating it as one would keep the loop alive on
|
|
19147
|
+
// exactly the autonomous cohort.
|
|
19148
|
+
newUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
19149
|
+
newAuthorship: !turnIsIdleForChannel,
|
|
19150
|
+
emittedLast: st.meta.channel?.emittedLast === true,
|
|
19151
|
+
consecutiveIdle: st.meta.channel?.consecutiveIdle ?? 0
|
|
19152
|
+
});
|
|
19153
|
+
} catch {
|
|
19154
|
+
silenced = null;
|
|
19155
|
+
}
|
|
19156
|
+
}
|
|
19157
|
+
if (silenced) {
|
|
19158
|
+
logEvent("channel_silenced", {
|
|
19159
|
+
reason: silenced,
|
|
19160
|
+
run_id: response.run_id ?? turnId,
|
|
19161
|
+
decision
|
|
19162
|
+
});
|
|
19163
|
+
}
|
|
18680
19164
|
let intentRepeatCount = 0;
|
|
18681
19165
|
if (memorySession) {
|
|
18682
19166
|
try {
|
|
@@ -18696,7 +19180,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18696
19180
|
// The same signal F1 introduced: bytes differing from the hash frozen at
|
|
18697
19181
|
// the last verdict. A turn that moved nothing is the only kind that can
|
|
18698
19182
|
// accumulate a repeat.
|
|
18699
|
-
idle:
|
|
19183
|
+
idle: turnIsIdleForChannel,
|
|
19184
|
+
// What next turn reads as `emittedLast`. A suppressed turn did not
|
|
19185
|
+
// speak, so it cannot be the cause of the turn after it — which is what
|
|
19186
|
+
// keeps this from becoming a permanent gag.
|
|
19187
|
+
emitted: !silenced
|
|
18700
19188
|
});
|
|
18701
19189
|
intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
|
|
18702
19190
|
} catch {
|
|
@@ -18796,9 +19284,41 @@ async function runAnalyze(opts, globals) {
|
|
|
18796
19284
|
reverify_by: response.reverify_by
|
|
18797
19285
|
});
|
|
18798
19286
|
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
18799
|
-
|
|
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) {
|
|
18800
19321
|
case "FAIL": {
|
|
18801
|
-
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
18802
19322
|
const assessment = response.assessment;
|
|
18803
19323
|
const narrative = assessment?.narrative ?? "";
|
|
18804
19324
|
const findings = response.findings ?? [];
|
|
@@ -18875,7 +19395,19 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18875
19395
|
if (grantNudge) process.stderr.write(`
|
|
18876
19396
|
${YELLOW}${grantNudge.trim()}${NC}
|
|
18877
19397
|
`);
|
|
18878
|
-
|
|
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
|
+
});
|
|
18879
19411
|
break;
|
|
18880
19412
|
}
|
|
18881
19413
|
case "PASS": {
|
|
@@ -18887,10 +19419,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18887
19419
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18888
19420
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18889
19421
|
userSummary += loginNudge + grantNudge;
|
|
18890
|
-
|
|
18891
|
-
|
|
18892
|
-
|
|
18893
|
-
|
|
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
|
+
});
|
|
18894
19432
|
break;
|
|
18895
19433
|
}
|
|
18896
19434
|
case "WARN": {
|
|
@@ -18901,10 +19439,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18901
19439
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18902
19440
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18903
19441
|
userSummary += loginNudge + grantNudge;
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
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
|
+
});
|
|
18908
19452
|
break;
|
|
18909
19453
|
}
|
|
18910
19454
|
default: {
|
|
@@ -19017,8 +19561,8 @@ async function runReview(opts, globals) {
|
|
|
19017
19561
|
for (const p of specPaths) {
|
|
19018
19562
|
if (!(0, import_node_fs26.existsSync)(p)) continue;
|
|
19019
19563
|
try {
|
|
19020
|
-
const { readFileSync:
|
|
19021
|
-
const content =
|
|
19564
|
+
const { readFileSync: readFileSync16 } = await import("node:fs");
|
|
19565
|
+
const content = readFileSync16(p, "utf-8");
|
|
19022
19566
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
19023
19567
|
} catch {
|
|
19024
19568
|
}
|
|
@@ -20704,7 +21248,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20704
21248
|
}
|
|
20705
21249
|
|
|
20706
21250
|
// src/cli.ts
|
|
20707
|
-
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.6eac6aa").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
20708
21252
|
try {
|
|
20709
21253
|
await foldLegacyLocalCredential();
|
|
20710
21254
|
} catch {
|