@codacy/verity-cli 0.28.1-experimental.af3c52d → 0.28.1-experimental.c2dc717
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 +506 -75
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10636,14 +10636,14 @@ async function readGlobalCredential(remote) {
|
|
|
10636
10636
|
const parsed = parseCredentialLine(line);
|
|
10637
10637
|
if (parsed && parsed.remote === key) last = parsed.rec;
|
|
10638
10638
|
}
|
|
10639
|
-
if (last) return last;
|
|
10639
|
+
if (last) return { ...last, keyed: true };
|
|
10640
10640
|
}
|
|
10641
10641
|
let plain = null;
|
|
10642
10642
|
for (const line of lines) {
|
|
10643
10643
|
const parsed = parseCredentialLine(line);
|
|
10644
10644
|
if (parsed && parsed.remote === "") plain = parsed.rec;
|
|
10645
10645
|
}
|
|
10646
|
-
return plain;
|
|
10646
|
+
return plain ? { ...plain, keyed: false } : null;
|
|
10647
10647
|
}
|
|
10648
10648
|
async function upsertGlobalCredential(remote, rec) {
|
|
10649
10649
|
const path = globalCredentialsPath();
|
|
@@ -11218,7 +11218,13 @@ async function resolveToken(flagToken) {
|
|
|
11218
11218
|
if (rec) {
|
|
11219
11219
|
return {
|
|
11220
11220
|
ok: true,
|
|
11221
|
-
data: {
|
|
11221
|
+
data: {
|
|
11222
|
+
token: rec.token,
|
|
11223
|
+
source: "global",
|
|
11224
|
+
userId: rec.userId,
|
|
11225
|
+
email: rec.email,
|
|
11226
|
+
keyed: rec.keyed
|
|
11227
|
+
}
|
|
11222
11228
|
};
|
|
11223
11229
|
}
|
|
11224
11230
|
const local = await readLegacyLocalCredential();
|
|
@@ -11250,6 +11256,16 @@ function reverifyNudge(who) {
|
|
|
11250
11256
|
}
|
|
11251
11257
|
return null;
|
|
11252
11258
|
}
|
|
11259
|
+
function isLegacyPerRepoCredential(auth2) {
|
|
11260
|
+
return auth2.source === "local" || auth2.source === "global" && auth2.keyed === true;
|
|
11261
|
+
}
|
|
11262
|
+
async function shouldUpgradeOnLogin(auth2) {
|
|
11263
|
+
if (!isLegacyPerRepoCredential(auth2)) return false;
|
|
11264
|
+
if (auth2.userId == null) return true;
|
|
11265
|
+
const bare = await readGlobalCredential("");
|
|
11266
|
+
if (bare?.userId == null) return true;
|
|
11267
|
+
return bare.userId === auth2.userId;
|
|
11268
|
+
}
|
|
11253
11269
|
function authDenialRemedy(error) {
|
|
11254
11270
|
if (error.startsWith("STALE_VERIFICATION")) {
|
|
11255
11271
|
return {
|
|
@@ -11680,13 +11696,19 @@ function registerLoginCommand(program2) {
|
|
|
11680
11696
|
const who = await whoami(existing.data.token, serviceUrl, globals.verbose);
|
|
11681
11697
|
if (who.ok && who.data.logged_in) {
|
|
11682
11698
|
const nudge = reverifyNudge(who.data);
|
|
11683
|
-
|
|
11699
|
+
const upgrade = await shouldUpgradeOnLogin(existing.data);
|
|
11700
|
+
if (!nudge && !upgrade) {
|
|
11684
11701
|
printInfo(`Already logged in as ${who.data.email ?? `user #${who.data.user_id}`}. \u2713`);
|
|
11685
11702
|
printInfo(" Re-authenticate with: verity login --force");
|
|
11686
11703
|
return;
|
|
11687
11704
|
}
|
|
11688
|
-
|
|
11689
|
-
|
|
11705
|
+
if (nudge) {
|
|
11706
|
+
printWarn(nudge);
|
|
11707
|
+
printInfo("Re-verifying your repository access\u2026");
|
|
11708
|
+
} else {
|
|
11709
|
+
printInfo("You are signed in with a per-repository token (the old format).");
|
|
11710
|
+
printInfo(" Upgrading to a single login that covers every repository you can write to\u2026");
|
|
11711
|
+
}
|
|
11690
11712
|
} else if (who.ok && who.data.anonymous) {
|
|
11691
11713
|
printInfo("You have an anonymous token (the gate runs, but nothing is saved). Logging you in\u2026");
|
|
11692
11714
|
} else if (!who.ok) {
|
|
@@ -11737,14 +11759,24 @@ function registerLoginCommand(program2) {
|
|
|
11737
11759
|
const rec = await readGlobalCredential(remote);
|
|
11738
11760
|
if (rec && rec.token !== out.token) {
|
|
11739
11761
|
const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
|
|
11762
|
+
const otherIdentity = rec.userId != null && out.userId != null && rec.userId !== out.userId;
|
|
11740
11763
|
if (otherBackend) {
|
|
11741
11764
|
printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
|
|
11742
11765
|
printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
|
|
11743
11766
|
printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
|
|
11767
|
+
printWarn(' Until that line is removed, "verity login" here cannot fast-path and will run');
|
|
11768
|
+
printWarn(" the full GitHub flow every time.");
|
|
11769
|
+
} else if (otherIdentity) {
|
|
11770
|
+
printWarn(" Note: this repository uses a different account's credential, which takes");
|
|
11771
|
+
printWarn(' precedence here \u2014 this login leaves it in place, and "verity login" in this');
|
|
11772
|
+
printWarn(" repository will report that account. Remove its line from ~/.verity/credentials");
|
|
11773
|
+
printWarn(" only if you want this repository on the login you just completed.");
|
|
11744
11774
|
} else {
|
|
11745
11775
|
const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
|
|
11746
11776
|
printWarn(` Note: this repository has a ${kind} credential that takes`);
|
|
11747
11777
|
printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
|
|
11778
|
+
printWarn(' Until then, "verity login" in this repository re-runs the full GitHub flow');
|
|
11779
|
+
printWarn(" every time.");
|
|
11748
11780
|
}
|
|
11749
11781
|
}
|
|
11750
11782
|
}
|
|
@@ -13559,6 +13591,65 @@ var import_node_fs10 = require("node:fs");
|
|
|
13559
13591
|
var import_node_crypto5 = require("node:crypto");
|
|
13560
13592
|
var import_node_path11 = require("node:path");
|
|
13561
13593
|
|
|
13594
|
+
// src/lib/skip-detection.ts
|
|
13595
|
+
function isBareAckPrompt(prompt) {
|
|
13596
|
+
if (typeof prompt !== "string") return false;
|
|
13597
|
+
const trimmed = prompt.trim();
|
|
13598
|
+
if (trimmed.length === 0) return false;
|
|
13599
|
+
if (trimmed.length > 20) return false;
|
|
13600
|
+
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
13601
|
+
return bareAckPattern.test(trimmed);
|
|
13602
|
+
}
|
|
13603
|
+
function isContinuationPrompt(prompt) {
|
|
13604
|
+
if (typeof prompt !== "string") return false;
|
|
13605
|
+
const trimmed = prompt.trim();
|
|
13606
|
+
if (trimmed.length === 0) return false;
|
|
13607
|
+
if (trimmed.length > 24) return false;
|
|
13608
|
+
const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
|
|
13609
|
+
return continuation.test(trimmed) || isBareAckPrompt(trimmed);
|
|
13610
|
+
}
|
|
13611
|
+
function resolveGoalPrompt(prompts) {
|
|
13612
|
+
if (prompts.length === 0) return null;
|
|
13613
|
+
const latest = prompts[prompts.length - 1];
|
|
13614
|
+
if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
|
|
13615
|
+
for (let i = prompts.length - 2; i >= 0; i--) {
|
|
13616
|
+
if (!isContinuationPrompt(prompts[i].prompt)) {
|
|
13617
|
+
return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
|
|
13618
|
+
}
|
|
13619
|
+
}
|
|
13620
|
+
return { entry: latest, turnsBack: 0 };
|
|
13621
|
+
}
|
|
13622
|
+
function isReflectionQuestion(response) {
|
|
13623
|
+
if (!response || typeof response !== "string") return false;
|
|
13624
|
+
const markers = [
|
|
13625
|
+
/reflection\s+for\s+future\s+agents/i,
|
|
13626
|
+
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
13627
|
+
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
13628
|
+
/quick\s+reflection\s+question/i,
|
|
13629
|
+
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
13630
|
+
// interactive, asks the user to confirm/correct before recording. That
|
|
13631
|
+
// turn authors no code either, so it's still a reflection turn.
|
|
13632
|
+
/reflection\s+draft/i,
|
|
13633
|
+
/confirm,?\s+correct,?\s+or\s+add/i
|
|
13634
|
+
];
|
|
13635
|
+
return markers.some((m) => m.test(response));
|
|
13636
|
+
}
|
|
13637
|
+
function isMetaTaskLabel(label2) {
|
|
13638
|
+
if (label2 === null || label2 === void 0) return false;
|
|
13639
|
+
if (typeof label2 !== "string") return false;
|
|
13640
|
+
const trimmed = label2.trim();
|
|
13641
|
+
if (trimmed.length === 0) return true;
|
|
13642
|
+
const metaPatterns = [
|
|
13643
|
+
/^verity\s+[\w-]+\s+response$/i,
|
|
13644
|
+
// "Verity reflect response"
|
|
13645
|
+
/^simple user response$/i,
|
|
13646
|
+
/^verity\s+command$/i,
|
|
13647
|
+
// "Verity command"
|
|
13648
|
+
/^user\s+(question|reply|response|ack)$/i
|
|
13649
|
+
];
|
|
13650
|
+
return metaPatterns.some((p) => p.test(trimmed));
|
|
13651
|
+
}
|
|
13652
|
+
|
|
13562
13653
|
// src/lib/dossier.ts
|
|
13563
13654
|
var import_node_fs9 = require("node:fs");
|
|
13564
13655
|
var import_node_crypto4 = require("node:crypto");
|
|
@@ -13928,6 +14019,12 @@ function reduce(state, events, now) {
|
|
|
13928
14019
|
}
|
|
13929
14020
|
case "verdict": {
|
|
13930
14021
|
state.meta.last_verdict_seq = ev.seq;
|
|
14022
|
+
state.meta.channel = {
|
|
14023
|
+
emittedLast: ev.emitted === true,
|
|
14024
|
+
// Reset by ANY movement, so the counter measures a standstill rather
|
|
14025
|
+
// than session length.
|
|
14026
|
+
consecutiveIdle: ev.idle === false ? 0 : (state.meta.channel?.consecutiveIdle ?? 0) + 1
|
|
14027
|
+
};
|
|
13931
14028
|
state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
|
|
13932
14029
|
if (ev.intent_sig) {
|
|
13933
14030
|
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 };
|
|
@@ -14295,6 +14392,10 @@ function projectMemory(state, opts) {
|
|
|
14295
14392
|
...active.delivered && { delivered: active.delivered }
|
|
14296
14393
|
};
|
|
14297
14394
|
}
|
|
14395
|
+
if (opts.capture) {
|
|
14396
|
+
const missed = Math.max(0, opts.capture.seen - opts.capture.captured);
|
|
14397
|
+
p.capture = { seen: opts.capture.seen, captured: opts.capture.captured, missed };
|
|
14398
|
+
}
|
|
14298
14399
|
if (state.meta.last_adjudication) {
|
|
14299
14400
|
const a = state.meta.last_adjudication;
|
|
14300
14401
|
p.last_adjudication = { verdict: a.verdict, score: a.score, at: a.at };
|
|
@@ -14541,7 +14642,8 @@ function recall(d, input) {
|
|
|
14541
14642
|
continuity,
|
|
14542
14643
|
spoken: reanchored.spoken,
|
|
14543
14644
|
refused: reanchored.dropped.length,
|
|
14544
|
-
lastVerdictSeq
|
|
14645
|
+
lastVerdictSeq,
|
|
14646
|
+
...input.capture && { capture: input.capture }
|
|
14545
14647
|
});
|
|
14546
14648
|
return {
|
|
14547
14649
|
state: effective,
|
|
@@ -14652,7 +14754,19 @@ function sessionDossier(token, sessionId) {
|
|
|
14652
14754
|
const d = openDossier(identity);
|
|
14653
14755
|
return d ? { d, identity } : null;
|
|
14654
14756
|
}
|
|
14757
|
+
function hasActiveGoal(d) {
|
|
14758
|
+
try {
|
|
14759
|
+
if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return false;
|
|
14760
|
+
return (0, import_node_fs10.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
|
|
14761
|
+
} catch {
|
|
14762
|
+
return false;
|
|
14763
|
+
}
|
|
14764
|
+
}
|
|
14655
14765
|
function recordGoal(d, prompt, source = "prompt") {
|
|
14766
|
+
if (source === "prompt" && isContinuationPrompt(prompt) && hasActiveGoal(d)) {
|
|
14767
|
+
appendEvent(d, { k: "goal_continue", text: prompt.slice(0, 64) });
|
|
14768
|
+
return;
|
|
14769
|
+
}
|
|
14656
14770
|
const text = prompt.slice(0, MAX_GOAL_CHARS);
|
|
14657
14771
|
appendEvent(d, {
|
|
14658
14772
|
k: "goal",
|
|
@@ -14755,6 +14869,11 @@ function recordVerdict(d, v) {
|
|
|
14755
14869
|
line_sha: at !== void 0 ? lineSha(at) : null
|
|
14756
14870
|
});
|
|
14757
14871
|
}
|
|
14872
|
+
const foldedNow = foldDossier(d);
|
|
14873
|
+
const sig = intentSignature(v.intent, {
|
|
14874
|
+
goalSeq: foldedNow.goal.find((g) => g.status === "active")?.seq ?? 0,
|
|
14875
|
+
idle: v.idle !== false
|
|
14876
|
+
});
|
|
14758
14877
|
appendEvent(d, {
|
|
14759
14878
|
k: "verdict",
|
|
14760
14879
|
run_id: v.runId,
|
|
@@ -14762,15 +14881,19 @@ function recordVerdict(d, v) {
|
|
|
14762
14881
|
watermark_sha: v.watermarkSha,
|
|
14763
14882
|
branch: v.branch,
|
|
14764
14883
|
decision: v.decision,
|
|
14765
|
-
...
|
|
14884
|
+
...sig && { intent_sig: sig },
|
|
14885
|
+
emitted: v.emitted === true,
|
|
14886
|
+
idle: v.idle !== false,
|
|
14766
14887
|
...v.intent?.verdict && { intent_verdict: v.intent.verdict },
|
|
14767
14888
|
...typeof v.intent?.score === "number" && { intent_score: v.intent.score }
|
|
14768
14889
|
});
|
|
14769
14890
|
}
|
|
14770
|
-
function intentSignature(intent) {
|
|
14891
|
+
function intentSignature(intent, ctx) {
|
|
14771
14892
|
if (!intent?.verdict) return null;
|
|
14772
14893
|
if (intent.verdict !== "misaligned" && intent.verdict !== "partial") return null;
|
|
14773
|
-
|
|
14894
|
+
const goal = ctx ? `g${ctx.goalSeq}` : "g?";
|
|
14895
|
+
const moved = ctx?.idle === false ? "active" : "idle";
|
|
14896
|
+
return `${intent.verdict}:${goal}:${moved}`;
|
|
14774
14897
|
}
|
|
14775
14898
|
function toRegister(severity) {
|
|
14776
14899
|
switch (severity) {
|
|
@@ -14807,7 +14930,9 @@ function recallMemory(d, identity, opts) {
|
|
|
14807
14930
|
const state = foldDossier(d);
|
|
14808
14931
|
const watermark = state.meta.watermark?.sha ?? null;
|
|
14809
14932
|
const watermarkPaths = (state.authored ?? []).map((a) => a.path);
|
|
14933
|
+
const captureCmp = typeof opts.userMessagesSeen === "number" ? { seen: opts.userMessagesSeen, captured: state.meta.goal_chain } : void 0;
|
|
14810
14934
|
const r = recall(d, {
|
|
14935
|
+
...captureCmp && { capture: captureCmp },
|
|
14811
14936
|
identity,
|
|
14812
14937
|
currentSessionKey: opts.currentSessionKey,
|
|
14813
14938
|
branchNow: getCurrentBranch(),
|
|
@@ -15064,6 +15189,8 @@ function collectCodeDelta(files, opts) {
|
|
|
15064
15189
|
let totalSize = 0;
|
|
15065
15190
|
let truncationReason = null;
|
|
15066
15191
|
const droppedPaths = [];
|
|
15192
|
+
const excluded = [];
|
|
15193
|
+
const exclude = (path, reason) => excluded.push({ path, reason, stage: "collectCodeDelta", kind: "capacity" });
|
|
15067
15194
|
for (const filepath of sorted) {
|
|
15068
15195
|
if (result.length >= maxFiles) {
|
|
15069
15196
|
truncationReason ??= "max_files";
|
|
@@ -15071,14 +15198,21 @@ function collectCodeDelta(files, opts) {
|
|
|
15071
15198
|
continue;
|
|
15072
15199
|
}
|
|
15073
15200
|
const resolved = resolveFile(filepath);
|
|
15074
|
-
if (!resolved)
|
|
15201
|
+
if (!resolved) {
|
|
15202
|
+
exclude(filepath, "path-not-resolvable");
|
|
15203
|
+
continue;
|
|
15204
|
+
}
|
|
15075
15205
|
let size;
|
|
15076
15206
|
try {
|
|
15077
15207
|
size = (0, import_node_fs11.statSync)(resolved).size;
|
|
15078
15208
|
} catch {
|
|
15209
|
+
exclude(filepath, "not-stattable");
|
|
15210
|
+
continue;
|
|
15211
|
+
}
|
|
15212
|
+
if (size > maxFileBytes) {
|
|
15213
|
+
exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
|
|
15079
15214
|
continue;
|
|
15080
15215
|
}
|
|
15081
|
-
if (size > maxFileBytes) continue;
|
|
15082
15216
|
if (totalSize + size > maxTotalBytes) {
|
|
15083
15217
|
truncationReason ??= "max_total_bytes";
|
|
15084
15218
|
const idx = sorted.indexOf(filepath);
|
|
@@ -15089,6 +15223,7 @@ function collectCodeDelta(files, opts) {
|
|
|
15089
15223
|
try {
|
|
15090
15224
|
content = (0, import_node_fs11.readFileSync)(resolved, "utf-8");
|
|
15091
15225
|
} catch {
|
|
15226
|
+
exclude(filepath, "not-readable");
|
|
15092
15227
|
continue;
|
|
15093
15228
|
}
|
|
15094
15229
|
totalSize += size;
|
|
@@ -15102,10 +15237,14 @@ function collectCodeDelta(files, opts) {
|
|
|
15102
15237
|
(sum, f) => sum + f.content.split("\n").length,
|
|
15103
15238
|
0
|
|
15104
15239
|
);
|
|
15240
|
+
for (const path of droppedPaths) {
|
|
15241
|
+
exclude(path, truncationReason === "max_files" ? "max-files-cap" : "max-total-bytes-cap");
|
|
15242
|
+
}
|
|
15105
15243
|
return {
|
|
15106
15244
|
files: result,
|
|
15107
15245
|
total_lines: totalLines,
|
|
15108
15246
|
total_files: result.length,
|
|
15247
|
+
excluded,
|
|
15109
15248
|
...truncationReason && {
|
|
15110
15249
|
truncated: {
|
|
15111
15250
|
reason: truncationReason,
|
|
@@ -16557,7 +16696,7 @@ function resolveTaskContext(opts) {
|
|
|
16557
16696
|
// src/lib/cli-version.ts
|
|
16558
16697
|
function cliVersion() {
|
|
16559
16698
|
try {
|
|
16560
|
-
return true ? "0.28.1-experimental.
|
|
16699
|
+
return true ? "0.28.1-experimental.c2dc717" : "dev";
|
|
16561
16700
|
} catch {
|
|
16562
16701
|
return "dev";
|
|
16563
16702
|
}
|
|
@@ -16782,6 +16921,16 @@ var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
|
16782
16921
|
"subagent"
|
|
16783
16922
|
]);
|
|
16784
16923
|
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit", "MultiEdit"]);
|
|
16924
|
+
var DISPATCH_TOOLS = /* @__PURE__ */ new Set(["Task", "Workflow"]);
|
|
16925
|
+
function hasUserText(record) {
|
|
16926
|
+
const message = record.message;
|
|
16927
|
+
const content = message?.content ?? record.content;
|
|
16928
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
16929
|
+
if (!Array.isArray(content)) return false;
|
|
16930
|
+
return content.some(
|
|
16931
|
+
(b) => b?.type === "text" && typeof b.text === "string" && b.text.trim().length > 0
|
|
16932
|
+
);
|
|
16933
|
+
}
|
|
16785
16934
|
var COMMAND_CLASSES = [
|
|
16786
16935
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?test\b|\bvitest\b|\bjest\b|\bpytest\b|\bgo test\b/, "test"],
|
|
16787
16936
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?build\b|\btsc\b|\bwebpack\b|\bcargo build\b/, "build"],
|
|
@@ -16908,6 +17057,8 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16908
17057
|
totalRecords: 0,
|
|
16909
17058
|
malformed: 0,
|
|
16910
17059
|
subagentFiles: 0,
|
|
17060
|
+
dispatched: 0,
|
|
17061
|
+
userMessages: 0,
|
|
16911
17062
|
subagentSkipped: 0,
|
|
16912
17063
|
compactions: 0,
|
|
16913
17064
|
complete: false
|
|
@@ -16934,7 +17085,8 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16934
17085
|
if (type === "system" && record.subtype === "compact_boundary") {
|
|
16935
17086
|
result.coverage.compactions++;
|
|
16936
17087
|
}
|
|
16937
|
-
|
|
17088
|
+
if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
|
|
17089
|
+
collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot, result.coverage);
|
|
16938
17090
|
}
|
|
16939
17091
|
};
|
|
16940
17092
|
try {
|
|
@@ -17006,7 +17158,7 @@ function classifyUnobserved(path) {
|
|
|
17006
17158
|
if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
|
|
17007
17159
|
return "no_edit_record";
|
|
17008
17160
|
}
|
|
17009
|
-
function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2) {
|
|
17161
|
+
function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2, tally) {
|
|
17010
17162
|
const message = record.message;
|
|
17011
17163
|
const content = message?.content ?? record.content;
|
|
17012
17164
|
const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
|
|
@@ -17028,6 +17180,9 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
|
|
|
17028
17180
|
byPath.set(path, entry);
|
|
17029
17181
|
}
|
|
17030
17182
|
}
|
|
17183
|
+
if (DISPATCH_TOOLS.has(name) && tally) {
|
|
17184
|
+
tally.dispatched += 1;
|
|
17185
|
+
}
|
|
17031
17186
|
if (name === "Bash") {
|
|
17032
17187
|
const cmd = typeof input.command === "string" ? input.command : "";
|
|
17033
17188
|
if (cmd) {
|
|
@@ -17086,6 +17241,86 @@ function checkConservation(changedFiles, result, repoRoot2) {
|
|
|
17086
17241
|
};
|
|
17087
17242
|
}
|
|
17088
17243
|
|
|
17244
|
+
// src/lib/verdict.ts
|
|
17245
|
+
function reconcileCoverage(changed, coverage) {
|
|
17246
|
+
const changedSet = new Set(changed);
|
|
17247
|
+
const reviewed = coverage.reviewed.filter((p) => changedSet.has(p));
|
|
17248
|
+
const claimed = /* @__PURE__ */ new Set([...reviewed, ...coverage.notReviewed.map((n) => n.path)]);
|
|
17249
|
+
const unaccounted = [...changedSet].filter((p) => !claimed.has(p)).sort();
|
|
17250
|
+
const notReviewed = [
|
|
17251
|
+
...coverage.notReviewed.filter((n) => changedSet.has(n.path)),
|
|
17252
|
+
...unaccounted.map((path) => ({
|
|
17253
|
+
path,
|
|
17254
|
+
reason: "unaccounted",
|
|
17255
|
+
// Named so the eventual bug report writes itself: some stage removed this
|
|
17256
|
+
// path and did not say so.
|
|
17257
|
+
stage: "unknown-stage",
|
|
17258
|
+
// An undeclared drop is CAPACITY by default. A stage that cannot be
|
|
17259
|
+
// bothered to say why it dropped a file does not get the benefit of the
|
|
17260
|
+
// doubt — that default is what makes forgetting expensive.
|
|
17261
|
+
kind: "capacity"
|
|
17262
|
+
}))
|
|
17263
|
+
];
|
|
17264
|
+
return {
|
|
17265
|
+
coverage: { reviewed: [...new Set(reviewed)].sort(), notReviewed },
|
|
17266
|
+
unaccounted,
|
|
17267
|
+
balances: unaccounted.length === 0
|
|
17268
|
+
};
|
|
17269
|
+
}
|
|
17270
|
+
function resolveVerdict(proposed, coverage) {
|
|
17271
|
+
if (proposed === "FAIL") return "FAIL";
|
|
17272
|
+
const blocking = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17273
|
+
if (blocking.length === 0) return proposed;
|
|
17274
|
+
return "WARN";
|
|
17275
|
+
}
|
|
17276
|
+
function describeCoverage(coverage, maxPaths = 5) {
|
|
17277
|
+
const relevant = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17278
|
+
if (relevant.length === 0) return null;
|
|
17279
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
17280
|
+
for (const n of relevant) {
|
|
17281
|
+
const key = `${n.reason}`;
|
|
17282
|
+
const list = byReason.get(key) ?? [];
|
|
17283
|
+
list.push(n.path);
|
|
17284
|
+
byReason.set(key, list);
|
|
17285
|
+
}
|
|
17286
|
+
const lines = [];
|
|
17287
|
+
for (const [reason, paths] of [...byReason.entries()].sort()) {
|
|
17288
|
+
const shown = paths.slice(0, maxPaths).join(", ");
|
|
17289
|
+
const more = paths.length > maxPaths ? ` (+${paths.length - maxPaths} more)` : "";
|
|
17290
|
+
lines.push(` ${paths.length} not reviewed \u2014 ${reason}: ${shown}${more}`);
|
|
17291
|
+
}
|
|
17292
|
+
return `NOT A CLEAN REVIEW. ${relevant.length} changed file(s) never reached the reviewer, so this verdict does not cover them:
|
|
17293
|
+
${lines.join("\n")}
|
|
17294
|
+
Treat those files as UNCHECKED, not as approved.`;
|
|
17295
|
+
}
|
|
17296
|
+
function openBlockingElsewhere(statements, reviewedNow, lineShaAt) {
|
|
17297
|
+
const reviewed = new Set(reviewedNow);
|
|
17298
|
+
const out = [];
|
|
17299
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17300
|
+
for (const s of statements) {
|
|
17301
|
+
if (s.outcome !== "open") continue;
|
|
17302
|
+
if (s.register !== "BLOCK") continue;
|
|
17303
|
+
if (s.carried) continue;
|
|
17304
|
+
if (reviewed.has(s.file)) continue;
|
|
17305
|
+
if (!s.line_sha) continue;
|
|
17306
|
+
if (lineShaAt(s.file, s.line) !== s.line_sha) continue;
|
|
17307
|
+
const key = `${s.file}::${s.pattern_id}`;
|
|
17308
|
+
if (seen.has(key)) continue;
|
|
17309
|
+
seen.add(key);
|
|
17310
|
+
out.push({ file: s.file, line: s.line, pattern_id: s.pattern_id });
|
|
17311
|
+
}
|
|
17312
|
+
return out;
|
|
17313
|
+
}
|
|
17314
|
+
function describeOpenElsewhere(open) {
|
|
17315
|
+
if (open.length === 0) return null;
|
|
17316
|
+
const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
|
|
17317
|
+
const more = open.length > 5 ? `
|
|
17318
|
+
(+${open.length - 5} more)` : "";
|
|
17319
|
+
return `STILL OPEN ELSEWHERE. ${open.length} blocking finding(s) Verity raised earlier are still present in files this run did not review:
|
|
17320
|
+
${lines.join("\n")}${more}
|
|
17321
|
+
This verdict covers the current change only. The tree is not clean.`;
|
|
17322
|
+
}
|
|
17323
|
+
|
|
17089
17324
|
// src/lib/channel.ts
|
|
17090
17325
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
17091
17326
|
var MAX_AGENT_ITEMS = 5;
|
|
@@ -17164,6 +17399,47 @@ function buildHookOutput(gateDecision, systemMessage, agentContext) {
|
|
|
17164
17399
|
} : {}
|
|
17165
17400
|
};
|
|
17166
17401
|
}
|
|
17402
|
+
var IDLE_EPISODE_CAP = 3;
|
|
17403
|
+
function channelSilence(input) {
|
|
17404
|
+
const movedSomething = input.newUserPrompt || input.newAuthorship;
|
|
17405
|
+
if (movedSomething) return null;
|
|
17406
|
+
if (input.consecutiveIdle >= IDLE_EPISODE_CAP) return "idle-episode-cap";
|
|
17407
|
+
if (input.emittedLast) return "caused-by-our-own-emission";
|
|
17408
|
+
return null;
|
|
17409
|
+
}
|
|
17410
|
+
|
|
17411
|
+
// src/lib/emit.ts
|
|
17412
|
+
var YELLOW2 = "\x1B[33m";
|
|
17413
|
+
var NC2 = "\x1B[0m";
|
|
17414
|
+
function emitVerdict(input) {
|
|
17415
|
+
const exit = input.exit ?? ((code) => process.exit(code));
|
|
17416
|
+
const { coverage, unaccounted } = reconcileCoverage(input.changed, input.coverage);
|
|
17417
|
+
let verdict = resolveVerdict(input.proposed, coverage);
|
|
17418
|
+
const openElsewhere = input.openElsewhere ?? [];
|
|
17419
|
+
if (verdict === "PASS" && openElsewhere.length > 0) verdict = "WARN";
|
|
17420
|
+
const note = [describeCoverage(coverage), describeOpenElsewhere(openElsewhere)].filter(Boolean).join("\n\n") || null;
|
|
17421
|
+
if (unaccounted.length > 0) {
|
|
17422
|
+
process.stderr.write(
|
|
17423
|
+
`${YELLOW2}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
|
|
17424
|
+
`
|
|
17425
|
+
);
|
|
17426
|
+
}
|
|
17427
|
+
if (verdict === "FAIL") {
|
|
17428
|
+
input.renderBlocking?.();
|
|
17429
|
+
if (input.agentContext) {
|
|
17430
|
+
process.stderr.write(`
|
|
17431
|
+
${input.agentContext}
|
|
17432
|
+
`);
|
|
17433
|
+
}
|
|
17434
|
+
if (note && !input.silenced) process.stderr.write(`
|
|
17435
|
+
${YELLOW2}${note}${NC2}
|
|
17436
|
+
`);
|
|
17437
|
+
return exit(2);
|
|
17438
|
+
}
|
|
17439
|
+
const agentBlock = input.silenced ? null : [input.agentContext, note].filter(Boolean).join("\n\n") || null;
|
|
17440
|
+
printJsonCompact(buildHookOutput(verdict, input.userSummary, agentBlock));
|
|
17441
|
+
return exit(0);
|
|
17442
|
+
}
|
|
17167
17443
|
|
|
17168
17444
|
// src/lib/cache-cleanup.ts
|
|
17169
17445
|
var import_node_fs21 = require("node:fs");
|
|
@@ -17344,46 +17620,6 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
17344
17620
|
return false;
|
|
17345
17621
|
}
|
|
17346
17622
|
|
|
17347
|
-
// src/lib/skip-detection.ts
|
|
17348
|
-
function isBareAckPrompt(prompt) {
|
|
17349
|
-
if (typeof prompt !== "string") return false;
|
|
17350
|
-
const trimmed = prompt.trim();
|
|
17351
|
-
if (trimmed.length === 0) return false;
|
|
17352
|
-
if (trimmed.length > 20) return false;
|
|
17353
|
-
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;
|
|
17354
|
-
return bareAckPattern.test(trimmed);
|
|
17355
|
-
}
|
|
17356
|
-
function isReflectionQuestion(response) {
|
|
17357
|
-
if (!response || typeof response !== "string") return false;
|
|
17358
|
-
const markers = [
|
|
17359
|
-
/reflection\s+for\s+future\s+agents/i,
|
|
17360
|
-
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
17361
|
-
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
17362
|
-
/quick\s+reflection\s+question/i,
|
|
17363
|
-
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
17364
|
-
// interactive, asks the user to confirm/correct before recording. That
|
|
17365
|
-
// turn authors no code either, so it's still a reflection turn.
|
|
17366
|
-
/reflection\s+draft/i,
|
|
17367
|
-
/confirm,?\s+correct,?\s+or\s+add/i
|
|
17368
|
-
];
|
|
17369
|
-
return markers.some((m) => m.test(response));
|
|
17370
|
-
}
|
|
17371
|
-
function isMetaTaskLabel(label2) {
|
|
17372
|
-
if (label2 === null || label2 === void 0) return false;
|
|
17373
|
-
if (typeof label2 !== "string") return false;
|
|
17374
|
-
const trimmed = label2.trim();
|
|
17375
|
-
if (trimmed.length === 0) return true;
|
|
17376
|
-
const metaPatterns = [
|
|
17377
|
-
/^verity\s+[\w-]+\s+response$/i,
|
|
17378
|
-
// "Verity reflect response"
|
|
17379
|
-
/^simple user response$/i,
|
|
17380
|
-
/^verity\s+command$/i,
|
|
17381
|
-
// "Verity command"
|
|
17382
|
-
/^user\s+(question|reply|response|ack)$/i
|
|
17383
|
-
];
|
|
17384
|
-
return metaPatterns.some((p) => p.test(trimmed));
|
|
17385
|
-
}
|
|
17386
|
-
|
|
17387
17623
|
// src/lib/transcript.ts
|
|
17388
17624
|
var import_node_fs22 = require("node:fs");
|
|
17389
17625
|
var MAX_READ_BYTES = 256 * 1024;
|
|
@@ -17547,6 +17783,13 @@ function buildSummary(lines) {
|
|
|
17547
17783
|
files_read: capArray(filesRead, MAX_FILES_LIST),
|
|
17548
17784
|
files_edited: capArray(filesEdited, MAX_FILES_LIST),
|
|
17549
17785
|
files_created: capArray(filesCreated, MAX_CREATED_LIST),
|
|
17786
|
+
// The complement of the two caps that affect SCOPE. `files_read` is excluded
|
|
17787
|
+
// deliberately: reading a file is not authoring it, so a capped read list
|
|
17788
|
+
// narrows nothing.
|
|
17789
|
+
capped_out: [
|
|
17790
|
+
...cappedOut(filesEdited, MAX_FILES_LIST),
|
|
17791
|
+
...cappedOut(filesCreated, MAX_CREATED_LIST)
|
|
17792
|
+
],
|
|
17550
17793
|
searches,
|
|
17551
17794
|
commands,
|
|
17552
17795
|
subagents,
|
|
@@ -17597,6 +17840,9 @@ function sanitizeCommand(rawCmd) {
|
|
|
17597
17840
|
function capArray(set, max) {
|
|
17598
17841
|
return Array.from(set).slice(0, max);
|
|
17599
17842
|
}
|
|
17843
|
+
function cappedOut(set, max) {
|
|
17844
|
+
return Array.from(set).slice(max);
|
|
17845
|
+
}
|
|
17600
17846
|
|
|
17601
17847
|
// src/lib/run-mode.ts
|
|
17602
17848
|
function parseAutonomousEnv(raw) {
|
|
@@ -18006,12 +18252,45 @@ function agentContextFor(response, intentRepeat = 0) {
|
|
|
18006
18252
|
});
|
|
18007
18253
|
}
|
|
18008
18254
|
var beaconCtx = null;
|
|
18009
|
-
async function passAndExit(reason, skip) {
|
|
18255
|
+
async function passAndExit(reason, skip, kindOverride) {
|
|
18010
18256
|
const sent = await sendSkipBeacon(beaconCtx, skip);
|
|
18011
18257
|
logEvent("skip", { reason: skip, beacon: sent });
|
|
18012
|
-
|
|
18258
|
+
const POLICY_SKIPS = /* @__PURE__ */ new Set([
|
|
18259
|
+
"no-analyzable-files",
|
|
18260
|
+
"verity-command",
|
|
18261
|
+
"bare-acknowledgment",
|
|
18262
|
+
"reflection-prompt",
|
|
18263
|
+
"skip-mode",
|
|
18264
|
+
"zero-increment",
|
|
18265
|
+
"debounce",
|
|
18266
|
+
"no-delta-since-last-review"
|
|
18267
|
+
]);
|
|
18268
|
+
const skipKind = kindOverride ?? (POLICY_SKIPS.has(skip) ? "policy" : "capacity");
|
|
18269
|
+
const changed = skipCoverageChanged;
|
|
18270
|
+
const { coverage, unaccounted } = reconcileCoverage(changed, {
|
|
18271
|
+
reviewed: [],
|
|
18272
|
+
notReviewed: changed.map((path) => ({ path, reason: skip, stage: "pre-flight", kind: skipKind }))
|
|
18273
|
+
});
|
|
18274
|
+
const verdict = resolveVerdict("PASS", coverage);
|
|
18275
|
+
const note = describeCoverage(coverage);
|
|
18276
|
+
if (unaccounted.length > 0) {
|
|
18277
|
+
logEvent("coverage_unaccounted", { where: "passAndExit", skip, count: unaccounted.length });
|
|
18278
|
+
}
|
|
18279
|
+
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set(["iteration-cap"]);
|
|
18280
|
+
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18281
|
+
printJsonCompact(
|
|
18282
|
+
buildHookOutput(
|
|
18283
|
+
verdict,
|
|
18284
|
+
`Verity: ${reason}`,
|
|
18285
|
+
// The agent's ONLY input is additionalContext. Sixteen of the nineteen
|
|
18286
|
+
// terminating paths wrote `systemMessage` — the human's field — and told
|
|
18287
|
+
// the agent nothing at all.
|
|
18288
|
+
agentNote
|
|
18289
|
+
)
|
|
18290
|
+
);
|
|
18013
18291
|
process.exit(0);
|
|
18014
18292
|
}
|
|
18293
|
+
var skipCoverageChanged = [];
|
|
18015
18294
|
var EMPTY_STATIC = {
|
|
18016
18295
|
tool: "@codacy/analysis-cli",
|
|
18017
18296
|
findings: [],
|
|
@@ -18026,7 +18305,7 @@ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
|
18026
18305
|
}
|
|
18027
18306
|
function localOnlyAndExit(staticResults) {
|
|
18028
18307
|
printJsonCompact({
|
|
18029
|
-
gate_decision: "
|
|
18308
|
+
gate_decision: "WARN",
|
|
18030
18309
|
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.",
|
|
18031
18310
|
unauthenticated: true,
|
|
18032
18311
|
static_results: staticResults
|
|
@@ -18086,6 +18365,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18086
18365
|
});
|
|
18087
18366
|
}
|
|
18088
18367
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18368
|
+
skipCoverageChanged = allChanged;
|
|
18089
18369
|
const analyzable = filterAnalyzable(allChanged);
|
|
18090
18370
|
const reviewable = filterReviewable(allChanged);
|
|
18091
18371
|
const securityFiles = filterSecurity(allChanged);
|
|
@@ -18161,7 +18441,8 @@ async function runAnalyze(opts, globals) {
|
|
|
18161
18441
|
let codeDelta = {
|
|
18162
18442
|
files: [],
|
|
18163
18443
|
total_lines: 0,
|
|
18164
|
-
total_files: 0
|
|
18444
|
+
total_files: 0,
|
|
18445
|
+
excluded: []
|
|
18165
18446
|
};
|
|
18166
18447
|
let snapshotResult = { has_snapshots: false, diffs: [] };
|
|
18167
18448
|
let contentHash = null;
|
|
@@ -18226,7 +18507,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18226
18507
|
if (assistantResponse) {
|
|
18227
18508
|
analysisMode = "plan";
|
|
18228
18509
|
} else {
|
|
18229
|
-
await passAndExit(
|
|
18510
|
+
await passAndExit(
|
|
18511
|
+
"No files within size limits to analyze",
|
|
18512
|
+
"size-limit",
|
|
18513
|
+
codeDelta.excluded.length > 0 ? "capacity" : "policy"
|
|
18514
|
+
);
|
|
18230
18515
|
}
|
|
18231
18516
|
}
|
|
18232
18517
|
}
|
|
@@ -18400,7 +18685,10 @@ async function runAnalyze(opts, globals) {
|
|
|
18400
18685
|
priorState.capabilities
|
|
18401
18686
|
);
|
|
18402
18687
|
memory = recallMemory(memorySession.d, memorySession.identity, {
|
|
18403
|
-
currentSessionKey: memorySession.identity.sessionKey
|
|
18688
|
+
currentSessionKey: memorySession.identity.sessionKey,
|
|
18689
|
+
// The independent witness. Only meaningful when a transcript was folded —
|
|
18690
|
+
// otherwise it stays undefined and capture coverage reads as UNKNOWN.
|
|
18691
|
+
...foldResult && { userMessagesSeen: foldResult.coverage.userMessages }
|
|
18404
18692
|
});
|
|
18405
18693
|
if (memory && !memory.provenanceHolds) {
|
|
18406
18694
|
process.stderr.write("Verity: working-memory provenance check failed \u2014 recall suppressed.\n");
|
|
@@ -18539,7 +18827,12 @@ async function runAnalyze(opts, globals) {
|
|
|
18539
18827
|
const intentContext = {};
|
|
18540
18828
|
if (conversation && conversation.prompts.length > 0) {
|
|
18541
18829
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
18542
|
-
|
|
18830
|
+
const goalPrompt = resolveGoalPrompt(conversation.prompts) ?? { entry: latest, turnsBack: 0 };
|
|
18831
|
+
intentContext.user_prompt = goalPrompt.entry.prompt;
|
|
18832
|
+
if (goalPrompt.turnsBack > 0) {
|
|
18833
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
18834
|
+
logEvent("goal_walked_back", { turns_back: goalPrompt.turnsBack });
|
|
18835
|
+
}
|
|
18543
18836
|
intentContext.session_id = latest.session_id || void 0;
|
|
18544
18837
|
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
18545
18838
|
if (conversation.prompts.length > 1) {
|
|
@@ -18631,8 +18924,114 @@ async function runAnalyze(opts, globals) {
|
|
|
18631
18924
|
const response = result.data;
|
|
18632
18925
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
18633
18926
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
18927
|
+
let openElsewhere = [];
|
|
18928
|
+
if (memorySession) {
|
|
18929
|
+
try {
|
|
18930
|
+
const st = foldDossier(memorySession.d);
|
|
18931
|
+
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
18932
|
+
try {
|
|
18933
|
+
const src = (0, import_node_fs24.readFileSync)((0, import_node_path20.join)(repoRoot(), file), "utf8").split("\n");
|
|
18934
|
+
const at = src[line - 1];
|
|
18935
|
+
return at === void 0 ? null : lineSha(at);
|
|
18936
|
+
} catch {
|
|
18937
|
+
return null;
|
|
18938
|
+
}
|
|
18939
|
+
});
|
|
18940
|
+
} catch {
|
|
18941
|
+
}
|
|
18942
|
+
}
|
|
18943
|
+
const reviewCoverage = {
|
|
18944
|
+
reviewed: sentPaths,
|
|
18945
|
+
// Declared drops from the stages that DO report themselves today. The other
|
|
18946
|
+
// stages surface via `unaccounted`, which is the tripwire, not the design.
|
|
18947
|
+
notReviewed: [
|
|
18948
|
+
// Every exit from the collection loop, each named. Six reasons where there
|
|
18949
|
+
// used to be two recorded and four silent — the silent ones including the
|
|
18950
|
+
// per-file size cap, which could drop a whole source file without leaving a
|
|
18951
|
+
// trace anywhere in the payload or the run row.
|
|
18952
|
+
...codeDelta.excluded,
|
|
18953
|
+
// The server-side 300-line middle-out truncation. It only bites on the
|
|
18954
|
+
// full-file branch (a first analysis, before snapshots exist) because
|
|
18955
|
+
// analyze normally sends diffs — but on that branch the reviewer sees the
|
|
18956
|
+
// first and last 100 lines and nothing between, and until now said so to
|
|
18957
|
+
// nobody. CAPACITY: a partial look is not a look.
|
|
18958
|
+
...(response.metadata?.truncated_files ?? []).map((path) => ({
|
|
18959
|
+
path,
|
|
18960
|
+
reason: "file-middle-truncated-300-lines",
|
|
18961
|
+
stage: "prompt-builder",
|
|
18962
|
+
kind: "capacity"
|
|
18963
|
+
})),
|
|
18964
|
+
// The 20-entry edit cap. CAPACITY, and the sharpest of the lot: it narrows
|
|
18965
|
+
// what is REVIEWED, not merely what is summarised — a session editing 25
|
|
18966
|
+
// files had five silently excluded from the reviewed set.
|
|
18967
|
+
...(actionSummary?.capped_out ?? []).map((path) => ({
|
|
18968
|
+
path,
|
|
18969
|
+
reason: "edit-list-cap-20",
|
|
18970
|
+
stage: "extractActionSummary",
|
|
18971
|
+
kind: "capacity"
|
|
18972
|
+
})),
|
|
18973
|
+
// ⚠ BASELINE SCOPING — the biggest source of false NOT A CLEAN REVIEW.
|
|
18974
|
+
//
|
|
18975
|
+
// The universe is `allChanged`, git's whole dirty tree. The reviewed set is
|
|
18976
|
+
// scoped to what THIS SESSION authored (the VRT-26 contamination cure), so
|
|
18977
|
+
// every pre-existing dirty file is in the universe, absent from `reviewed`,
|
|
18978
|
+
// and — until now — declared by nobody. It fell through to `unaccounted`,
|
|
18979
|
+
// became capacity, and produced "NOT A CLEAN REVIEW: admin.js" over a file
|
|
18980
|
+
// that was never this session's to review.
|
|
18981
|
+
//
|
|
18982
|
+
// Measured 2026-08-04: three consecutive runs over an untouched tree gave
|
|
18983
|
+
// three different answers — .claude/settings.json, then admin.js, then six
|
|
18984
|
+
// files — because each run took a different path and each path had a
|
|
18985
|
+
// different idea of the universe. POLICY: not this session's work is not a
|
|
18986
|
+
// coverage gap, it is the cure working.
|
|
18987
|
+
...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) => ({
|
|
18988
|
+
path,
|
|
18989
|
+
reason: "not-authored-this-session",
|
|
18990
|
+
stage: "baseline-scoping",
|
|
18991
|
+
kind: "policy"
|
|
18992
|
+
})),
|
|
18993
|
+
// The extension allowlist, and it is POLICY rather than capacity: a changed
|
|
18994
|
+
// README was never going to be reviewed, and treating that as a coverage
|
|
18995
|
+
// gap would downgrade nearly every PASS to WARN until WARN meant nothing.
|
|
18996
|
+
// Recorded so the ledger balances and so "what did Verity ignore entirely"
|
|
18997
|
+
// is answerable — but it never touches the verdict.
|
|
18998
|
+
...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
|
|
18999
|
+
path,
|
|
19000
|
+
reason: "not-a-reviewed-file-type",
|
|
19001
|
+
stage: "extension-allowlist",
|
|
19002
|
+
kind: "policy"
|
|
19003
|
+
}))
|
|
19004
|
+
]
|
|
19005
|
+
};
|
|
18634
19006
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18635
19007
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
19008
|
+
let silenced = null;
|
|
19009
|
+
let turnIsIdleForChannel = true;
|
|
19010
|
+
if (memorySession) {
|
|
19011
|
+
try {
|
|
19012
|
+
const st = foldDossier(memorySession.d);
|
|
19013
|
+
turnIsIdleForChannel = st.authored_all.every((a) => a.hash_now === a.hash_at_last_verdict);
|
|
19014
|
+
silenced = channelSilence({
|
|
19015
|
+
// The BUFFER, not intentContext.user_prompt: the latter falls back to a
|
|
19016
|
+
// linked issue (VRT-53 W4) when no human spoke, and a fallback goal is
|
|
19017
|
+
// not a user utterance. Treating it as one would keep the loop alive on
|
|
19018
|
+
// exactly the autonomous cohort.
|
|
19019
|
+
newUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
19020
|
+
newAuthorship: !turnIsIdleForChannel,
|
|
19021
|
+
emittedLast: st.meta.channel?.emittedLast === true,
|
|
19022
|
+
consecutiveIdle: st.meta.channel?.consecutiveIdle ?? 0
|
|
19023
|
+
});
|
|
19024
|
+
} catch {
|
|
19025
|
+
silenced = null;
|
|
19026
|
+
}
|
|
19027
|
+
}
|
|
19028
|
+
if (silenced) {
|
|
19029
|
+
logEvent("channel_silenced", {
|
|
19030
|
+
reason: silenced,
|
|
19031
|
+
run_id: response.run_id ?? turnId,
|
|
19032
|
+
decision
|
|
19033
|
+
});
|
|
19034
|
+
}
|
|
18636
19035
|
let intentRepeatCount = 0;
|
|
18637
19036
|
if (memorySession) {
|
|
18638
19037
|
try {
|
|
@@ -18648,7 +19047,15 @@ async function runAnalyze(opts, globals) {
|
|
|
18648
19047
|
title: f.title,
|
|
18649
19048
|
severity: f.severity
|
|
18650
19049
|
})) ?? [],
|
|
18651
|
-
intent: response.intent_alignment ?? null
|
|
19050
|
+
intent: response.intent_alignment ?? null,
|
|
19051
|
+
// The same signal F1 introduced: bytes differing from the hash frozen at
|
|
19052
|
+
// the last verdict. A turn that moved nothing is the only kind that can
|
|
19053
|
+
// accumulate a repeat.
|
|
19054
|
+
idle: turnIsIdleForChannel,
|
|
19055
|
+
// What next turn reads as `emittedLast`. A suppressed turn did not
|
|
19056
|
+
// speak, so it cannot be the cause of the turn after it — which is what
|
|
19057
|
+
// keeps this from becoming a permanent gag.
|
|
19058
|
+
emitted: !silenced
|
|
18652
19059
|
});
|
|
18653
19060
|
intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
|
|
18654
19061
|
} catch {
|
|
@@ -18827,7 +19234,19 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18827
19234
|
if (grantNudge) process.stderr.write(`
|
|
18828
19235
|
${YELLOW}${grantNudge.trim()}${NC}
|
|
18829
19236
|
`);
|
|
18830
|
-
|
|
19237
|
+
emitVerdict({
|
|
19238
|
+
proposed: "FAIL",
|
|
19239
|
+
changed: skipCoverageChanged,
|
|
19240
|
+
coverage: reviewCoverage,
|
|
19241
|
+
userSummary: "",
|
|
19242
|
+
// Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
|
|
19243
|
+
// the findings themselves are rendered above by the blocking renderer,
|
|
19244
|
+
// so what the cut removes is the repeated commentary, never the defect.
|
|
19245
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19246
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19247
|
+
silenced: !!silenced,
|
|
19248
|
+
openElsewhere
|
|
19249
|
+
});
|
|
18831
19250
|
break;
|
|
18832
19251
|
}
|
|
18833
19252
|
case "PASS": {
|
|
@@ -18839,10 +19258,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18839
19258
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18840
19259
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18841
19260
|
userSummary += loginNudge + grantNudge;
|
|
18842
|
-
|
|
18843
|
-
|
|
18844
|
-
|
|
18845
|
-
|
|
19261
|
+
emitVerdict({
|
|
19262
|
+
proposed: "PASS",
|
|
19263
|
+
changed: skipCoverageChanged,
|
|
19264
|
+
coverage: reviewCoverage,
|
|
19265
|
+
userSummary,
|
|
19266
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19267
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19268
|
+
silenced: !!silenced,
|
|
19269
|
+
openElsewhere
|
|
19270
|
+
});
|
|
18846
19271
|
break;
|
|
18847
19272
|
}
|
|
18848
19273
|
case "WARN": {
|
|
@@ -18853,10 +19278,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18853
19278
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18854
19279
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18855
19280
|
userSummary += loginNudge + grantNudge;
|
|
18856
|
-
|
|
18857
|
-
|
|
18858
|
-
|
|
18859
|
-
|
|
19281
|
+
emitVerdict({
|
|
19282
|
+
proposed: "WARN",
|
|
19283
|
+
changed: skipCoverageChanged,
|
|
19284
|
+
coverage: reviewCoverage,
|
|
19285
|
+
userSummary,
|
|
19286
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19287
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19288
|
+
silenced: !!silenced,
|
|
19289
|
+
openElsewhere
|
|
19290
|
+
});
|
|
18860
19291
|
break;
|
|
18861
19292
|
}
|
|
18862
19293
|
default: {
|
|
@@ -18969,8 +19400,8 @@ async function runReview(opts, globals) {
|
|
|
18969
19400
|
for (const p of specPaths) {
|
|
18970
19401
|
if (!(0, import_node_fs26.existsSync)(p)) continue;
|
|
18971
19402
|
try {
|
|
18972
|
-
const { readFileSync:
|
|
18973
|
-
const content =
|
|
19403
|
+
const { readFileSync: readFileSync16 } = await import("node:fs");
|
|
19404
|
+
const content = readFileSync16(p, "utf-8");
|
|
18974
19405
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
18975
19406
|
} catch {
|
|
18976
19407
|
}
|
|
@@ -20656,7 +21087,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20656
21087
|
}
|
|
20657
21088
|
|
|
20658
21089
|
// src/cli.ts
|
|
20659
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
21090
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.c2dc717").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
20660
21091
|
try {
|
|
20661
21092
|
await foldLegacyLocalCredential();
|
|
20662
21093
|
} catch {
|