@codacy/verity-cli 0.28.1-experimental.8cc9c66 → 0.28.1-experimental.902dbd9
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 +466 -94
- 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,143 @@ 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
|
+
|
|
13657
|
+
// src/lib/pending-repeat.ts
|
|
13658
|
+
var STOP = /* @__PURE__ */ new Set([
|
|
13659
|
+
"the",
|
|
13660
|
+
"and",
|
|
13661
|
+
"that",
|
|
13662
|
+
"this",
|
|
13663
|
+
"with",
|
|
13664
|
+
"from",
|
|
13665
|
+
"have",
|
|
13666
|
+
"been",
|
|
13667
|
+
"were",
|
|
13668
|
+
"what",
|
|
13669
|
+
"when",
|
|
13670
|
+
"which",
|
|
13671
|
+
"their",
|
|
13672
|
+
"there",
|
|
13673
|
+
"these",
|
|
13674
|
+
"those",
|
|
13675
|
+
"would",
|
|
13676
|
+
"could",
|
|
13677
|
+
"should",
|
|
13678
|
+
"must",
|
|
13679
|
+
"will",
|
|
13680
|
+
"also",
|
|
13681
|
+
"just",
|
|
13682
|
+
"only",
|
|
13683
|
+
"into",
|
|
13684
|
+
"over",
|
|
13685
|
+
"than",
|
|
13686
|
+
"then",
|
|
13687
|
+
"them",
|
|
13688
|
+
"some",
|
|
13689
|
+
"such",
|
|
13690
|
+
"more",
|
|
13691
|
+
"most",
|
|
13692
|
+
"other",
|
|
13693
|
+
"about",
|
|
13694
|
+
"after",
|
|
13695
|
+
"before",
|
|
13696
|
+
"since",
|
|
13697
|
+
"because",
|
|
13698
|
+
"while",
|
|
13699
|
+
"where",
|
|
13700
|
+
"whether",
|
|
13701
|
+
"ensure",
|
|
13702
|
+
"confirm",
|
|
13703
|
+
"verify",
|
|
13704
|
+
"check"
|
|
13705
|
+
]);
|
|
13706
|
+
function pendingTokens(text) {
|
|
13707
|
+
if (!text || typeof text !== "string") return [];
|
|
13708
|
+
const out = /* @__PURE__ */ new Set();
|
|
13709
|
+
for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
13710
|
+
if (raw.length <= 3) continue;
|
|
13711
|
+
if (STOP.has(raw)) continue;
|
|
13712
|
+
out.add(raw);
|
|
13713
|
+
}
|
|
13714
|
+
return [...out].sort();
|
|
13715
|
+
}
|
|
13716
|
+
var REPEAT_THRESHOLD = 0.3;
|
|
13717
|
+
function overlapCoefficient(a, b) {
|
|
13718
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
13719
|
+
const setB = new Set(b);
|
|
13720
|
+
let shared = 0;
|
|
13721
|
+
for (const t of a) if (setB.has(t)) shared++;
|
|
13722
|
+
return shared / Math.min(a.length, b.length);
|
|
13723
|
+
}
|
|
13724
|
+
function isRepeatOfAny(text, priorFingerprints, threshold = REPEAT_THRESHOLD) {
|
|
13725
|
+
const tokens = pendingTokens(text);
|
|
13726
|
+
if (tokens.length === 0) return false;
|
|
13727
|
+
return priorFingerprints.some((prior) => overlapCoefficient(tokens, prior) >= threshold);
|
|
13728
|
+
}
|
|
13729
|
+
|
|
13594
13730
|
// src/lib/dossier.ts
|
|
13595
13731
|
var import_node_fs9 = require("node:fs");
|
|
13596
13732
|
var import_node_crypto4 = require("node:crypto");
|
|
@@ -13599,6 +13735,7 @@ var MAX_LINE_BYTES = 4096;
|
|
|
13599
13735
|
var MAX_GOAL_CHARS = 2e3;
|
|
13600
13736
|
var GOAL_KEEP = 8;
|
|
13601
13737
|
var GOAL_TOTAL_CAP = 32;
|
|
13738
|
+
var RECENT_PENDING_CAP = 20;
|
|
13602
13739
|
var HASH_WIDTH = 16;
|
|
13603
13740
|
var AUTHORED_CAP = 300;
|
|
13604
13741
|
var NOT_MINE_CAP = 300;
|
|
@@ -13967,6 +14104,10 @@ function reduce(state, events, now) {
|
|
|
13967
14104
|
consecutiveIdle: ev.idle === false ? 0 : (state.meta.channel?.consecutiveIdle ?? 0) + 1
|
|
13968
14105
|
};
|
|
13969
14106
|
state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
|
|
14107
|
+
if (Array.isArray(ev.pending_sigs) && ev.pending_sigs.length > 0) {
|
|
14108
|
+
const prior = state.meta.recent_pending_sigs ?? [];
|
|
14109
|
+
state.meta.recent_pending_sigs = [...prior, ...ev.pending_sigs].slice(-RECENT_PENDING_CAP);
|
|
14110
|
+
}
|
|
13970
14111
|
if (ev.intent_sig) {
|
|
13971
14112
|
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 };
|
|
13972
14113
|
} else {
|
|
@@ -14695,7 +14836,19 @@ function sessionDossier(token, sessionId) {
|
|
|
14695
14836
|
const d = openDossier(identity);
|
|
14696
14837
|
return d ? { d, identity } : null;
|
|
14697
14838
|
}
|
|
14839
|
+
function hasActiveGoal(d) {
|
|
14840
|
+
try {
|
|
14841
|
+
if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return false;
|
|
14842
|
+
return (0, import_node_fs10.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
|
|
14843
|
+
} catch {
|
|
14844
|
+
return false;
|
|
14845
|
+
}
|
|
14846
|
+
}
|
|
14698
14847
|
function recordGoal(d, prompt, source = "prompt") {
|
|
14848
|
+
if (source === "prompt" && isContinuationPrompt(prompt) && hasActiveGoal(d)) {
|
|
14849
|
+
appendEvent(d, { k: "goal_continue", text: prompt.slice(0, 64) });
|
|
14850
|
+
return;
|
|
14851
|
+
}
|
|
14699
14852
|
const text = prompt.slice(0, MAX_GOAL_CHARS);
|
|
14700
14853
|
appendEvent(d, {
|
|
14701
14854
|
k: "goal",
|
|
@@ -14811,6 +14964,13 @@ function recordVerdict(d, v) {
|
|
|
14811
14964
|
branch: v.branch,
|
|
14812
14965
|
decision: v.decision,
|
|
14813
14966
|
...sig && { intent_sig: sig },
|
|
14967
|
+
// Fingerprints of the pending items this verdict delivered, so the NEXT turn
|
|
14968
|
+
// can tell a repeat from a new requirement. Only recorded when the channel
|
|
14969
|
+
// actually spoke — a silenced turn delivered nothing, so nothing was "said
|
|
14970
|
+
// before" and labelling the next turn's items as repeats would be a lie.
|
|
14971
|
+
...v.emitted === true && v.pendingTexts && v.pendingTexts.length > 0 && {
|
|
14972
|
+
pending_sigs: v.pendingTexts.slice(0, 8).map((t) => pendingTokens(t).slice(0, 16))
|
|
14973
|
+
},
|
|
14814
14974
|
emitted: v.emitted === true,
|
|
14815
14975
|
idle: v.idle !== false,
|
|
14816
14976
|
...v.intent?.verdict && { intent_verdict: v.intent.verdict },
|
|
@@ -15467,6 +15627,34 @@ ${addedLines}`,
|
|
|
15467
15627
|
}
|
|
15468
15628
|
return { diffs, has_baseline: true };
|
|
15469
15629
|
}
|
|
15630
|
+
function absorbIntoBaseline(paths, sessionId) {
|
|
15631
|
+
const baseline = readBaseline(sessionId);
|
|
15632
|
+
if (!baseline || paths.length === 0) return 0;
|
|
15633
|
+
const dir = sessionDir(sessionKey(baseline.session_id));
|
|
15634
|
+
let adopted = 0;
|
|
15635
|
+
const dirty = new Set(baseline.dirty_paths);
|
|
15636
|
+
for (const p of paths) {
|
|
15637
|
+
try {
|
|
15638
|
+
const content = safeReadForMirror(projectPath(p));
|
|
15639
|
+
if (content === null) continue;
|
|
15640
|
+
const dest = mirrorPath(dir, p);
|
|
15641
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
|
|
15642
|
+
(0, import_node_fs13.writeFileSync)(dest, content);
|
|
15643
|
+
dirty.add(p);
|
|
15644
|
+
adopted++;
|
|
15645
|
+
} catch {
|
|
15646
|
+
}
|
|
15647
|
+
}
|
|
15648
|
+
if (adopted === 0) return 0;
|
|
15649
|
+
try {
|
|
15650
|
+
const updated = { ...baseline, dirty_paths: [...dirty] };
|
|
15651
|
+
(0, import_node_fs13.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
|
|
15652
|
+
preImageCache.delete(baseline);
|
|
15653
|
+
} catch {
|
|
15654
|
+
return 0;
|
|
15655
|
+
}
|
|
15656
|
+
return adopted;
|
|
15657
|
+
}
|
|
15470
15658
|
function changedSinceBaseline(repoRelPath, baseline) {
|
|
15471
15659
|
const pre = preImage(repoRelPath, baseline);
|
|
15472
15660
|
let current;
|
|
@@ -16343,40 +16531,40 @@ function narrowToRecent(files, sessionId) {
|
|
|
16343
16531
|
});
|
|
16344
16532
|
return recent.length > 0 ? recent : files;
|
|
16345
16533
|
}
|
|
16346
|
-
function
|
|
16347
|
-
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return 1;
|
|
16534
|
+
function readIterationState(currentCommit) {
|
|
16535
|
+
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
|
|
16348
16536
|
try {
|
|
16349
16537
|
const stored = (0, import_node_fs15.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16350
16538
|
const parts = stored.split(":");
|
|
16351
16539
|
const iter = parseInt(parts[0], 10);
|
|
16352
16540
|
const storedCommit = parts[1] ?? "";
|
|
16353
16541
|
const storedTimestamp = parseInt(parts[2] ?? "0", 10);
|
|
16354
|
-
|
|
16355
|
-
if (
|
|
16542
|
+
const fingerprint = parts.slice(3).join(":") || null;
|
|
16543
|
+
if (isNaN(iter)) return { iteration: 1, fingerprint: null };
|
|
16544
|
+
if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
|
|
16356
16545
|
if (storedTimestamp > 0) {
|
|
16357
16546
|
const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
|
|
16358
|
-
if (elapsed > 600) return 1;
|
|
16547
|
+
if (elapsed > 600) return { iteration: 1, fingerprint: null };
|
|
16359
16548
|
}
|
|
16360
|
-
return iter;
|
|
16549
|
+
return { iteration: iter, fingerprint };
|
|
16361
16550
|
} catch {
|
|
16362
|
-
return 1;
|
|
16551
|
+
return { iteration: 1, fingerprint: null };
|
|
16363
16552
|
}
|
|
16364
16553
|
}
|
|
16365
|
-
function
|
|
16366
|
-
const
|
|
16367
|
-
|
|
16368
|
-
|
|
16369
|
-
|
|
16370
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
}
|
|
16374
|
-
return { skip: null, iteration };
|
|
16554
|
+
function findingsFingerprint(findings) {
|
|
16555
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16556
|
+
return [...new Set(keys)].sort().join(",");
|
|
16557
|
+
}
|
|
16558
|
+
function isSameProblem(previous, current) {
|
|
16559
|
+
if (!previous || !current) return false;
|
|
16560
|
+
const prev = new Set(previous.split(","));
|
|
16561
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16375
16562
|
}
|
|
16376
|
-
function writeIteration(iteration, commit, _contentHash) {
|
|
16563
|
+
function writeIteration(iteration, commit, _contentHash, fingerprint) {
|
|
16377
16564
|
(0, import_node_fs15.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16378
16565
|
const ts = Math.floor(Date.now() / 1e3);
|
|
16379
|
-
|
|
16566
|
+
const fp = fingerprint ? `:${fingerprint}` : "";
|
|
16567
|
+
(0, import_node_fs15.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
|
|
16380
16568
|
}
|
|
16381
16569
|
|
|
16382
16570
|
// src/lib/static-analysis.ts
|
|
@@ -16625,7 +16813,7 @@ function resolveTaskContext(opts) {
|
|
|
16625
16813
|
// src/lib/cli-version.ts
|
|
16626
16814
|
function cliVersion() {
|
|
16627
16815
|
try {
|
|
16628
|
-
return true ? "0.28.1-experimental.
|
|
16816
|
+
return true ? "0.28.1-experimental.902dbd9" : "dev";
|
|
16629
16817
|
} catch {
|
|
16630
16818
|
return "dev";
|
|
16631
16819
|
}
|
|
@@ -17222,6 +17410,33 @@ function describeCoverage(coverage, maxPaths = 5) {
|
|
|
17222
17410
|
${lines.join("\n")}
|
|
17223
17411
|
Treat those files as UNCHECKED, not as approved.`;
|
|
17224
17412
|
}
|
|
17413
|
+
function openBlockingElsewhere(statements, reviewedNow, lineShaAt) {
|
|
17414
|
+
const reviewed = new Set(reviewedNow);
|
|
17415
|
+
const out = [];
|
|
17416
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17417
|
+
for (const s of statements) {
|
|
17418
|
+
if (s.outcome !== "open") continue;
|
|
17419
|
+
if (s.register !== "BLOCK") continue;
|
|
17420
|
+
if (s.carried) continue;
|
|
17421
|
+
if (reviewed.has(s.file)) continue;
|
|
17422
|
+
if (!s.line_sha) continue;
|
|
17423
|
+
if (lineShaAt(s.file, s.line) !== s.line_sha) continue;
|
|
17424
|
+
const key = `${s.file}::${s.pattern_id}`;
|
|
17425
|
+
if (seen.has(key)) continue;
|
|
17426
|
+
seen.add(key);
|
|
17427
|
+
out.push({ file: s.file, line: s.line, pattern_id: s.pattern_id });
|
|
17428
|
+
}
|
|
17429
|
+
return out;
|
|
17430
|
+
}
|
|
17431
|
+
function describeOpenElsewhere(open) {
|
|
17432
|
+
if (open.length === 0) return null;
|
|
17433
|
+
const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
|
|
17434
|
+
const more = open.length > 5 ? `
|
|
17435
|
+
(+${open.length - 5} more)` : "";
|
|
17436
|
+
return `STILL OPEN ELSEWHERE. ${open.length} blocking finding(s) Verity raised earlier are still present in files this run did not review:
|
|
17437
|
+
${lines.join("\n")}${more}
|
|
17438
|
+
This verdict covers the current change only. The tree is not clean.`;
|
|
17439
|
+
}
|
|
17225
17440
|
|
|
17226
17441
|
// src/lib/channel.ts
|
|
17227
17442
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
@@ -17272,9 +17487,13 @@ function buildAgentContext(input) {
|
|
|
17272
17487
|
}
|
|
17273
17488
|
for (const p of input.pendingItems ?? []) {
|
|
17274
17489
|
if (lines.length >= MAX_AGENT_ITEMS) break;
|
|
17490
|
+
if (p.pattern_id === "intent-misalignment") continue;
|
|
17275
17491
|
const text = p.description ?? p.title ?? p.reason;
|
|
17276
17492
|
if (!text) continue;
|
|
17277
|
-
|
|
17493
|
+
const seenBefore = isRepeatOfAny(text, input.priorPendingFingerprints ?? []);
|
|
17494
|
+
lines.push(
|
|
17495
|
+
renderItem("", text, p.pattern_id, p.file, p.line) + (seenBefore ? "\n (raised earlier this session and still open \u2014 do not re-explain it; act on it or carry on)" : "")
|
|
17496
|
+
);
|
|
17278
17497
|
}
|
|
17279
17498
|
if (lines.length === 0) return null;
|
|
17280
17499
|
const body = `${REPORT_PREFIX}
|
|
@@ -17316,8 +17535,10 @@ var NC2 = "\x1B[0m";
|
|
|
17316
17535
|
function emitVerdict(input) {
|
|
17317
17536
|
const exit = input.exit ?? ((code) => process.exit(code));
|
|
17318
17537
|
const { coverage, unaccounted } = reconcileCoverage(input.changed, input.coverage);
|
|
17319
|
-
|
|
17320
|
-
const
|
|
17538
|
+
let verdict = resolveVerdict(input.proposed, coverage);
|
|
17539
|
+
const openElsewhere = input.openElsewhere ?? [];
|
|
17540
|
+
if (verdict === "PASS" && openElsewhere.length > 0) verdict = "WARN";
|
|
17541
|
+
const note = [describeCoverage(coverage), describeOpenElsewhere(openElsewhere)].filter(Boolean).join("\n\n") || null;
|
|
17321
17542
|
if (unaccounted.length > 0) {
|
|
17322
17543
|
process.stderr.write(
|
|
17323
17544
|
`${YELLOW2}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
|
|
@@ -17331,12 +17552,12 @@ function emitVerdict(input) {
|
|
|
17331
17552
|
${input.agentContext}
|
|
17332
17553
|
`);
|
|
17333
17554
|
}
|
|
17334
|
-
if (note) process.stderr.write(`
|
|
17555
|
+
if (note && !input.silenced) process.stderr.write(`
|
|
17335
17556
|
${YELLOW2}${note}${NC2}
|
|
17336
17557
|
`);
|
|
17337
17558
|
return exit(2);
|
|
17338
17559
|
}
|
|
17339
|
-
const agentBlock = [input.agentContext, note].filter(Boolean).join("\n\n") || null;
|
|
17560
|
+
const agentBlock = input.silenced ? null : [input.agentContext, note].filter(Boolean).join("\n\n") || null;
|
|
17340
17561
|
printJsonCompact(buildHookOutput(verdict, input.userSummary, agentBlock));
|
|
17341
17562
|
return exit(0);
|
|
17342
17563
|
}
|
|
@@ -17520,46 +17741,6 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
17520
17741
|
return false;
|
|
17521
17742
|
}
|
|
17522
17743
|
|
|
17523
|
-
// src/lib/skip-detection.ts
|
|
17524
|
-
function isBareAckPrompt(prompt) {
|
|
17525
|
-
if (typeof prompt !== "string") return false;
|
|
17526
|
-
const trimmed = prompt.trim();
|
|
17527
|
-
if (trimmed.length === 0) return false;
|
|
17528
|
-
if (trimmed.length > 20) return false;
|
|
17529
|
-
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;
|
|
17530
|
-
return bareAckPattern.test(trimmed);
|
|
17531
|
-
}
|
|
17532
|
-
function isReflectionQuestion(response) {
|
|
17533
|
-
if (!response || typeof response !== "string") return false;
|
|
17534
|
-
const markers = [
|
|
17535
|
-
/reflection\s+for\s+future\s+agents/i,
|
|
17536
|
-
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
17537
|
-
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
17538
|
-
/quick\s+reflection\s+question/i,
|
|
17539
|
-
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
17540
|
-
// interactive, asks the user to confirm/correct before recording. That
|
|
17541
|
-
// turn authors no code either, so it's still a reflection turn.
|
|
17542
|
-
/reflection\s+draft/i,
|
|
17543
|
-
/confirm,?\s+correct,?\s+or\s+add/i
|
|
17544
|
-
];
|
|
17545
|
-
return markers.some((m) => m.test(response));
|
|
17546
|
-
}
|
|
17547
|
-
function isMetaTaskLabel(label2) {
|
|
17548
|
-
if (label2 === null || label2 === void 0) return false;
|
|
17549
|
-
if (typeof label2 !== "string") return false;
|
|
17550
|
-
const trimmed = label2.trim();
|
|
17551
|
-
if (trimmed.length === 0) return true;
|
|
17552
|
-
const metaPatterns = [
|
|
17553
|
-
/^verity\s+[\w-]+\s+response$/i,
|
|
17554
|
-
// "Verity reflect response"
|
|
17555
|
-
/^simple user response$/i,
|
|
17556
|
-
/^verity\s+command$/i,
|
|
17557
|
-
// "Verity command"
|
|
17558
|
-
/^user\s+(question|reply|response|ack)$/i
|
|
17559
|
-
];
|
|
17560
|
-
return metaPatterns.some((p) => p.test(trimmed));
|
|
17561
|
-
}
|
|
17562
|
-
|
|
17563
17744
|
// src/lib/transcript.ts
|
|
17564
17745
|
var import_node_fs22 = require("node:fs");
|
|
17565
17746
|
var MAX_READ_BYTES = 256 * 1024;
|
|
@@ -17573,9 +17754,11 @@ var MAX_SUMMARY_BYTES = 4096;
|
|
|
17573
17754
|
var HOME = process.env.HOME ?? "";
|
|
17574
17755
|
async function extractActionSummary(transcriptPath) {
|
|
17575
17756
|
try {
|
|
17576
|
-
const
|
|
17577
|
-
if (!
|
|
17578
|
-
|
|
17757
|
+
const read = readTurnLines(transcriptPath);
|
|
17758
|
+
if (!read || read.lines.length === 0) return null;
|
|
17759
|
+
const summary = buildSummary(read.lines);
|
|
17760
|
+
if (summary) summary.transcript_windowed = read.window;
|
|
17761
|
+
return summary;
|
|
17579
17762
|
} catch {
|
|
17580
17763
|
return null;
|
|
17581
17764
|
}
|
|
@@ -17589,9 +17772,11 @@ function readTurnLines(transcriptPath) {
|
|
|
17589
17772
|
}
|
|
17590
17773
|
if (size === 0) return null;
|
|
17591
17774
|
let raw;
|
|
17775
|
+
let windowed = false;
|
|
17592
17776
|
if (size <= SMALL_FILE_BYTES) {
|
|
17593
17777
|
raw = (0, import_node_fs22.readFileSync)(transcriptPath, "utf-8");
|
|
17594
17778
|
} else {
|
|
17779
|
+
windowed = true;
|
|
17595
17780
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
17596
17781
|
const fd = require("node:fs").openSync(transcriptPath, "r");
|
|
17597
17782
|
try {
|
|
@@ -17609,17 +17794,22 @@ function readTurnLines(transcriptPath) {
|
|
|
17609
17794
|
const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
17610
17795
|
if (allLines.length === 0) return null;
|
|
17611
17796
|
let turnStart = 0;
|
|
17797
|
+
let boundaryFound = false;
|
|
17612
17798
|
for (let i = allLines.length - 1; i >= 0; i--) {
|
|
17613
17799
|
try {
|
|
17614
17800
|
const parsed = JSON.parse(allLines[i]);
|
|
17615
17801
|
if (parsed.type === "user" && isRealUserMessage(parsed)) {
|
|
17616
17802
|
turnStart = i;
|
|
17803
|
+
boundaryFound = true;
|
|
17617
17804
|
break;
|
|
17618
17805
|
}
|
|
17619
17806
|
} catch {
|
|
17620
17807
|
}
|
|
17621
17808
|
}
|
|
17622
|
-
return
|
|
17809
|
+
return {
|
|
17810
|
+
lines: allLines.slice(turnStart),
|
|
17811
|
+
window: !windowed ? "whole" : boundaryFound ? "windowed" : "orphaned"
|
|
17812
|
+
};
|
|
17623
17813
|
}
|
|
17624
17814
|
function isRealUserMessage(parsed) {
|
|
17625
17815
|
const message = parsed.message;
|
|
@@ -18177,11 +18367,12 @@ async function readStopHookStdin() {
|
|
|
18177
18367
|
return empty;
|
|
18178
18368
|
}
|
|
18179
18369
|
}
|
|
18180
|
-
function agentContextFor(response, intentRepeat = 0) {
|
|
18370
|
+
function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
|
|
18181
18371
|
const metadata = response.metadata ?? {};
|
|
18182
18372
|
const intent = response.intent_alignment ?? {};
|
|
18183
18373
|
return buildAgentContext({
|
|
18184
18374
|
intentRepeat,
|
|
18375
|
+
priorPendingFingerprints,
|
|
18185
18376
|
gateDecision: String(response.gate_decision ?? ""),
|
|
18186
18377
|
findings: response.findings ?? [],
|
|
18187
18378
|
pendingItems: response.pending_items ?? [],
|
|
@@ -18216,6 +18407,8 @@ async function passAndExit(reason, skip, kindOverride) {
|
|
|
18216
18407
|
if (unaccounted.length > 0) {
|
|
18217
18408
|
logEvent("coverage_unaccounted", { where: "passAndExit", skip, count: unaccounted.length });
|
|
18218
18409
|
}
|
|
18410
|
+
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([]);
|
|
18411
|
+
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18219
18412
|
printJsonCompact(
|
|
18220
18413
|
buildHookOutput(
|
|
18221
18414
|
verdict,
|
|
@@ -18223,7 +18416,7 @@ async function passAndExit(reason, skip, kindOverride) {
|
|
|
18223
18416
|
// The agent's ONLY input is additionalContext. Sixteen of the nineteen
|
|
18224
18417
|
// terminating paths wrote `systemMessage` — the human's field — and told
|
|
18225
18418
|
// the agent nothing at all.
|
|
18226
|
-
|
|
18419
|
+
agentNote
|
|
18227
18420
|
)
|
|
18228
18421
|
);
|
|
18229
18422
|
process.exit(0);
|
|
@@ -18315,14 +18508,24 @@ async function runAnalyze(opts, globals) {
|
|
|
18315
18508
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
18316
18509
|
const specs = discoverSpecs();
|
|
18317
18510
|
const plans = discoverPlans();
|
|
18511
|
+
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18512
|
+
const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18513
|
+
const canSeeTurnAuthorship = !!actionSummary || !!baseline;
|
|
18318
18514
|
const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
|
|
18319
18515
|
if (/^\s*\/verity-/i.test(latestPrompt)) {
|
|
18516
|
+
const setupAuthored = [
|
|
18517
|
+
...actionSummary?.files_edited ?? [],
|
|
18518
|
+
...actionSummary?.files_created ?? []
|
|
18519
|
+
];
|
|
18520
|
+
if (setupAuthored.length > 0) {
|
|
18521
|
+
const adopted = absorbIntoBaseline(setupAuthored, baselineSessionId);
|
|
18522
|
+
logEvent("baseline_absorbed", { skip: "verity-command", offered: setupAuthored.length, adopted });
|
|
18523
|
+
}
|
|
18320
18524
|
await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
|
|
18321
18525
|
}
|
|
18322
|
-
if (
|
|
18526
|
+
if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
|
|
18323
18527
|
await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
|
|
18324
18528
|
}
|
|
18325
|
-
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18326
18529
|
if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
|
|
18327
18530
|
await passAndExit("Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
|
|
18328
18531
|
}
|
|
@@ -18369,7 +18572,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18369
18572
|
);
|
|
18370
18573
|
}
|
|
18371
18574
|
if (analysisMode === "skip") {
|
|
18372
|
-
await passAndExit(
|
|
18575
|
+
await passAndExit(
|
|
18576
|
+
"Skip mode \u2014 no code work to analyze",
|
|
18577
|
+
"skip-mode",
|
|
18578
|
+
turnAuthoredCode ? "capacity" : void 0
|
|
18579
|
+
);
|
|
18373
18580
|
}
|
|
18374
18581
|
let staticResults = {
|
|
18375
18582
|
tool: "@codacy/analysis-cli",
|
|
@@ -18462,19 +18669,13 @@ async function runAnalyze(opts, globals) {
|
|
|
18462
18669
|
snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
18463
18670
|
}
|
|
18464
18671
|
currentCommit = getCurrentCommit();
|
|
18465
|
-
|
|
18466
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
18467
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18468
|
-
iteration = iterResult.iteration;
|
|
18672
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18469
18673
|
}
|
|
18470
18674
|
}
|
|
18471
18675
|
if (analysisMode === "plan") {
|
|
18472
18676
|
recordAnalysisStart();
|
|
18473
18677
|
currentCommit = getCurrentCommit();
|
|
18474
|
-
|
|
18475
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations);
|
|
18476
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18477
|
-
iteration = iterResult.iteration;
|
|
18678
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18478
18679
|
}
|
|
18479
18680
|
const contextFiles = gatherContextFiles(contextFilePaths, codeDelta.files);
|
|
18480
18681
|
for (const f of codeDelta.files) {
|
|
@@ -18647,7 +18848,30 @@ async function runAnalyze(opts, globals) {
|
|
|
18647
18848
|
hasUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
18648
18849
|
isTTY: process.stdout.isTTY === true
|
|
18649
18850
|
});
|
|
18851
|
+
const excludedByReason = {};
|
|
18852
|
+
for (const e of codeDelta.excluded ?? []) {
|
|
18853
|
+
excludedByReason[e.reason] = (excludedByReason[e.reason] ?? 0) + 1;
|
|
18854
|
+
}
|
|
18855
|
+
const coverageTelemetry = {
|
|
18856
|
+
// git's whole answer, before ANY narrowing. The number that has never been sent.
|
|
18857
|
+
changed_all: allChanged.length,
|
|
18858
|
+
analyzable: analyzable.length,
|
|
18859
|
+
reviewable: reviewable.length,
|
|
18860
|
+
security: securityFiles.length,
|
|
18861
|
+
// after the allowlist, before authorship scoping and the caps
|
|
18862
|
+
for_review: allForReview.length,
|
|
18863
|
+
// what actually reaches the reviewer
|
|
18864
|
+
sent: codeDelta.files.length,
|
|
18865
|
+
// the two silent narrowings, counted separately so they can be told apart
|
|
18866
|
+
capped_out: actionSummary?.capped_out?.length ?? 0,
|
|
18867
|
+
excluded: (codeDelta.excluded ?? []).length,
|
|
18868
|
+
excluded_by_reason: excludedByReason,
|
|
18869
|
+
// was the transcript itself truncated? The 256 KB window means "this turn"
|
|
18870
|
+
// can quietly mean "the last 256 KB of it".
|
|
18871
|
+
transcript_windowed: actionSummary?.transcript_windowed ?? null
|
|
18872
|
+
};
|
|
18650
18873
|
const requestBody = {
|
|
18874
|
+
coverage_telemetry: coverageTelemetry,
|
|
18651
18875
|
static_results: staticResults,
|
|
18652
18876
|
code_delta: codeDelta,
|
|
18653
18877
|
changed_files: allForReview,
|
|
@@ -18733,6 +18957,54 @@ async function runAnalyze(opts, globals) {
|
|
|
18733
18957
|
// replaced a population floor with ≈35% power that was sub-integer for
|
|
18734
18958
|
// three-quarters of the fleet.
|
|
18735
18959
|
conservation: foldConservation,
|
|
18960
|
+
// ⚠ VRT-52 — RECORDED, NOT APPLIED. The number nobody has.
|
|
18961
|
+
//
|
|
18962
|
+
// The whole "task-scoped delta" design space rests on an assumption that
|
|
18963
|
+
// has been observed exactly ONCE: that delta files routinely belong to
|
|
18964
|
+
// earlier work. Three designs were built on it and all three were killed
|
|
18965
|
+
// adversarially — two by measurement — so before another is attempted,
|
|
18966
|
+
// measure the base rate.
|
|
18967
|
+
//
|
|
18968
|
+
// `authored_under_earlier_goal` counts delta paths whose LAST authorship
|
|
18969
|
+
// event precedes the seq of the goal now in force. Both numbers come from
|
|
18970
|
+
// the same append-only counter (`nextSeq`), so the comparison is exact.
|
|
18971
|
+
//
|
|
18972
|
+
// Keyed on the GOAL, deliberately, not on the task id. The task classifier
|
|
18973
|
+
// reported `is_new_task` on two consecutive turns of one task 25 seconds
|
|
18974
|
+
// apart, so a task-keyed number would measure its unreliability rather
|
|
18975
|
+
// than the phenomenon. And this only became meaningful once `recordGoal`
|
|
18976
|
+
// stopped letting a bare "ok" supersede the goal — before that the seq
|
|
18977
|
+
// advanced every turn and this would have degenerated to "not edited this
|
|
18978
|
+
// turn", which is the exact mistake that sank one of the three designs.
|
|
18979
|
+
//
|
|
18980
|
+
// Changes no payload the reviewer sees, no narrowing, no verdict.
|
|
18981
|
+
vrt52: (() => {
|
|
18982
|
+
const goalSeq = memory?.projection.goal?.seq;
|
|
18983
|
+
if (goalSeq === void 0 || !memorySession) return { known: false };
|
|
18984
|
+
const lastSeq2 = new Map(
|
|
18985
|
+
foldDossier(memorySession.d).authored_all.map((a) => [a.path, a.last_seq])
|
|
18986
|
+
);
|
|
18987
|
+
let earlier = 0;
|
|
18988
|
+
let unknown = 0;
|
|
18989
|
+
for (const f of codeDelta.files) {
|
|
18990
|
+
const seen = lastSeq2.get(f.path);
|
|
18991
|
+
if (seen === void 0) unknown++;
|
|
18992
|
+
else if (seen < goalSeq) earlier++;
|
|
18993
|
+
}
|
|
18994
|
+
return {
|
|
18995
|
+
known: true,
|
|
18996
|
+
goal_seq: goalSeq,
|
|
18997
|
+
delta: codeDelta.files.length,
|
|
18998
|
+
// Files this delta carries that were last written under an EARLIER
|
|
18999
|
+
// instruction. If this stays near zero, VRT-52's code half is
|
|
19000
|
+
// unnecessary and should be closed saying so.
|
|
19001
|
+
authored_under_earlier_goal: earlier,
|
|
19002
|
+
// Delta files the dossier has no authorship record for at all —
|
|
19003
|
+
// pre-existing tree state, or an authorship channel the fold cannot
|
|
19004
|
+
// see. Reported separately so a blind spot is never counted as a zero.
|
|
19005
|
+
no_authorship_record: unknown
|
|
19006
|
+
};
|
|
19007
|
+
})(),
|
|
18736
19008
|
// P2 — RECORDED, NOT APPLIED. What this run WOULD have reviewed if it
|
|
18737
19009
|
// narrowed to the within-session increment: what changed since the last
|
|
18738
19010
|
// VERDICT rather than since task start.
|
|
@@ -18765,7 +19037,20 @@ async function runAnalyze(opts, globals) {
|
|
|
18765
19037
|
const intentContext = {};
|
|
18766
19038
|
if (conversation && conversation.prompts.length > 0) {
|
|
18767
19039
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
18768
|
-
|
|
19040
|
+
const goalPrompt = resolveGoalPrompt(conversation.prompts) ?? { entry: latest, turnsBack: 0 };
|
|
19041
|
+
intentContext.user_prompt = goalPrompt.entry.prompt;
|
|
19042
|
+
if (isContinuationPrompt(intentContext.user_prompt)) {
|
|
19043
|
+
const carried = memory?.projection.goal?.text;
|
|
19044
|
+
if (carried && !isContinuationPrompt(carried)) {
|
|
19045
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
19046
|
+
intentContext.user_prompt = carried;
|
|
19047
|
+
logEvent("goal_from_dossier", { chars: carried.length });
|
|
19048
|
+
}
|
|
19049
|
+
}
|
|
19050
|
+
if (goalPrompt.turnsBack > 0) {
|
|
19051
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
19052
|
+
logEvent("goal_walked_back", { turns_back: goalPrompt.turnsBack });
|
|
19053
|
+
}
|
|
18769
19054
|
intentContext.session_id = latest.session_id || void 0;
|
|
18770
19055
|
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
18771
19056
|
if (conversation.prompts.length > 1) {
|
|
@@ -18857,6 +19142,22 @@ async function runAnalyze(opts, globals) {
|
|
|
18857
19142
|
const response = result.data;
|
|
18858
19143
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
18859
19144
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
19145
|
+
let openElsewhere = [];
|
|
19146
|
+
if (memorySession) {
|
|
19147
|
+
try {
|
|
19148
|
+
const st = foldDossier(memorySession.d);
|
|
19149
|
+
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
19150
|
+
try {
|
|
19151
|
+
const src = (0, import_node_fs24.readFileSync)((0, import_node_path20.join)(repoRoot(), file), "utf8").split("\n");
|
|
19152
|
+
const at = src[line - 1];
|
|
19153
|
+
return at === void 0 ? null : lineSha(at);
|
|
19154
|
+
} catch {
|
|
19155
|
+
return null;
|
|
19156
|
+
}
|
|
19157
|
+
});
|
|
19158
|
+
} catch {
|
|
19159
|
+
}
|
|
19160
|
+
}
|
|
18860
19161
|
const reviewCoverage = {
|
|
18861
19162
|
reviewed: sentPaths,
|
|
18862
19163
|
// Declared drops from the stages that DO report themselves today. The other
|
|
@@ -18887,6 +19188,26 @@ async function runAnalyze(opts, globals) {
|
|
|
18887
19188
|
stage: "extractActionSummary",
|
|
18888
19189
|
kind: "capacity"
|
|
18889
19190
|
})),
|
|
19191
|
+
// ⚠ BASELINE SCOPING — the biggest source of false NOT A CLEAN REVIEW.
|
|
19192
|
+
//
|
|
19193
|
+
// The universe is `allChanged`, git's whole dirty tree. The reviewed set is
|
|
19194
|
+
// scoped to what THIS SESSION authored (the VRT-26 contamination cure), so
|
|
19195
|
+
// every pre-existing dirty file is in the universe, absent from `reviewed`,
|
|
19196
|
+
// and — until now — declared by nobody. It fell through to `unaccounted`,
|
|
19197
|
+
// became capacity, and produced "NOT A CLEAN REVIEW: admin.js" over a file
|
|
19198
|
+
// that was never this session's to review.
|
|
19199
|
+
//
|
|
19200
|
+
// Measured 2026-08-04: three consecutive runs over an untouched tree gave
|
|
19201
|
+
// three different answers — .claude/settings.json, then admin.js, then six
|
|
19202
|
+
// files — because each run took a different path and each path had a
|
|
19203
|
+
// different idea of the universe. POLICY: not this session's work is not a
|
|
19204
|
+
// coverage gap, it is the cure working.
|
|
19205
|
+
...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) => ({
|
|
19206
|
+
path,
|
|
19207
|
+
reason: "not-authored-this-session",
|
|
19208
|
+
stage: "baseline-scoping",
|
|
19209
|
+
kind: "policy"
|
|
19210
|
+
})),
|
|
18890
19211
|
// The extension allowlist, and it is POLICY rather than capacity: a changed
|
|
18891
19212
|
// README was never going to be reviewed, and treating that as a coverage
|
|
18892
19213
|
// gap would downgrade nearly every PASS to WARN until WARN meant nothing.
|
|
@@ -18930,6 +19251,13 @@ async function runAnalyze(opts, globals) {
|
|
|
18930
19251
|
});
|
|
18931
19252
|
}
|
|
18932
19253
|
let intentRepeatCount = 0;
|
|
19254
|
+
const priorPendingFingerprints = memorySession ? (() => {
|
|
19255
|
+
try {
|
|
19256
|
+
return foldDossier(memorySession.d).meta.recent_pending_sigs ?? [];
|
|
19257
|
+
} catch {
|
|
19258
|
+
return [];
|
|
19259
|
+
}
|
|
19260
|
+
})() : [];
|
|
18933
19261
|
if (memorySession) {
|
|
18934
19262
|
try {
|
|
18935
19263
|
recordVerdict(memorySession.d, {
|
|
@@ -18952,7 +19280,10 @@ async function runAnalyze(opts, globals) {
|
|
|
18952
19280
|
// What next turn reads as `emittedLast`. A suppressed turn did not
|
|
18953
19281
|
// speak, so it cannot be the cause of the turn after it — which is what
|
|
18954
19282
|
// keeps this from becoming a permanent gag.
|
|
18955
|
-
emitted: !silenced
|
|
19283
|
+
emitted: !silenced,
|
|
19284
|
+
// Fingerprinted for the NEXT turn's repeat check. Reviewer pending items
|
|
19285
|
+
// carry no `pattern_id`, so their content is the only available key.
|
|
19286
|
+
pendingTexts: (response.pending_items ?? []).map((p) => String(p.description ?? p.title ?? p.reason ?? "")).filter(Boolean)
|
|
18956
19287
|
});
|
|
18957
19288
|
intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
|
|
18958
19289
|
} catch {
|
|
@@ -19052,9 +19383,41 @@ async function runAnalyze(opts, globals) {
|
|
|
19052
19383
|
reverify_by: response.reverify_by
|
|
19053
19384
|
});
|
|
19054
19385
|
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
19055
|
-
|
|
19386
|
+
let capReleased = false;
|
|
19387
|
+
let effectiveDecision = decision;
|
|
19388
|
+
if (decision === "FAIL") {
|
|
19389
|
+
const blocking = (response.findings ?? []).filter((f) => {
|
|
19390
|
+
const sev = String(f.severity ?? "").toLowerCase();
|
|
19391
|
+
return sev === "critical" || sev === "high";
|
|
19392
|
+
});
|
|
19393
|
+
const fingerprint = findingsFingerprint(blocking);
|
|
19394
|
+
const prior = readIterationState(currentCommit);
|
|
19395
|
+
const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
|
|
19396
|
+
const nextIteration = sameProblem ? prior.iteration + 1 : 1;
|
|
19397
|
+
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
19398
|
+
writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
|
|
19399
|
+
iteration = nextIteration;
|
|
19400
|
+
if (nextIteration > maxIterations) {
|
|
19401
|
+
capReleased = true;
|
|
19402
|
+
effectiveDecision = "WARN";
|
|
19403
|
+
logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
|
|
19404
|
+
}
|
|
19405
|
+
}
|
|
19406
|
+
if (capReleased) {
|
|
19407
|
+
const findings = response.findings ?? [];
|
|
19408
|
+
const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
|
|
19409
|
+
emitVerdict({
|
|
19410
|
+
proposed: "WARN",
|
|
19411
|
+
changed: skipCoverageChanged,
|
|
19412
|
+
coverage: reviewCoverage,
|
|
19413
|
+
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.
|
|
19414
|
+
${lines.join("\n")}`,
|
|
19415
|
+
agentContext: null,
|
|
19416
|
+
silenced: true
|
|
19417
|
+
});
|
|
19418
|
+
}
|
|
19419
|
+
switch (effectiveDecision) {
|
|
19056
19420
|
case "FAIL": {
|
|
19057
|
-
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
19058
19421
|
const assessment = response.assessment;
|
|
19059
19422
|
const narrative = assessment?.narrative ?? "";
|
|
19060
19423
|
const findings = response.findings ?? [];
|
|
@@ -19139,7 +19502,10 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
19139
19502
|
// Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
|
|
19140
19503
|
// the findings themselves are rendered above by the blocking renderer,
|
|
19141
19504
|
// so what the cut removes is the repeated commentary, never the defect.
|
|
19142
|
-
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount)
|
|
19505
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19506
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19507
|
+
silenced: !!silenced,
|
|
19508
|
+
openElsewhere
|
|
19143
19509
|
});
|
|
19144
19510
|
break;
|
|
19145
19511
|
}
|
|
@@ -19157,7 +19523,10 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
19157
19523
|
changed: skipCoverageChanged,
|
|
19158
19524
|
coverage: reviewCoverage,
|
|
19159
19525
|
userSummary,
|
|
19160
|
-
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount)
|
|
19526
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19527
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19528
|
+
silenced: !!silenced,
|
|
19529
|
+
openElsewhere
|
|
19161
19530
|
});
|
|
19162
19531
|
break;
|
|
19163
19532
|
}
|
|
@@ -19174,7 +19543,10 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
19174
19543
|
changed: skipCoverageChanged,
|
|
19175
19544
|
coverage: reviewCoverage,
|
|
19176
19545
|
userSummary,
|
|
19177
|
-
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount)
|
|
19546
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19547
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19548
|
+
silenced: !!silenced,
|
|
19549
|
+
openElsewhere
|
|
19178
19550
|
});
|
|
19179
19551
|
break;
|
|
19180
19552
|
}
|
|
@@ -19288,8 +19660,8 @@ async function runReview(opts, globals) {
|
|
|
19288
19660
|
for (const p of specPaths) {
|
|
19289
19661
|
if (!(0, import_node_fs26.existsSync)(p)) continue;
|
|
19290
19662
|
try {
|
|
19291
|
-
const { readFileSync:
|
|
19292
|
-
const content =
|
|
19663
|
+
const { readFileSync: readFileSync16 } = await import("node:fs");
|
|
19664
|
+
const content = readFileSync16(p, "utf-8");
|
|
19293
19665
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
19294
19666
|
} catch {
|
|
19295
19667
|
}
|
|
@@ -20975,7 +21347,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20975
21347
|
}
|
|
20976
21348
|
|
|
20977
21349
|
// src/cli.ts
|
|
20978
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
21350
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.902dbd9").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
20979
21351
|
try {
|
|
20980
21352
|
await foldLegacyLocalCredential();
|
|
20981
21353
|
} catch {
|