@codacy/verity-cli 0.28.1-experimental.89ee5db → 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.
Files changed (2) hide show
  1. package/bin/verity.js +237 -17
  2. package/package.json +1 -1
package/bin/verity.js CHANGED
@@ -10386,7 +10386,7 @@ 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 = 20;
10389
+ var MAX_FILES = 40;
10390
10390
  var MAX_FILE_BYTES = 51200;
10391
10391
  var DEBOUNCE_SECONDS = 30;
10392
10392
  var MAX_SPEC_FILES = 10;
@@ -13654,6 +13654,79 @@ function shouldSkipForBareAck(input) {
13654
13654
  return input.canSeeTurnAuthorship;
13655
13655
  }
13656
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
+
13657
13730
  // src/lib/dossier.ts
13658
13731
  var import_node_fs9 = require("node:fs");
13659
13732
  var import_node_crypto4 = require("node:crypto");
@@ -13662,6 +13735,7 @@ var MAX_LINE_BYTES = 4096;
13662
13735
  var MAX_GOAL_CHARS = 2e3;
13663
13736
  var GOAL_KEEP = 8;
13664
13737
  var GOAL_TOTAL_CAP = 32;
13738
+ var RECENT_PENDING_CAP = 20;
13665
13739
  var HASH_WIDTH = 16;
13666
13740
  var AUTHORED_CAP = 300;
13667
13741
  var NOT_MINE_CAP = 300;
@@ -14030,6 +14104,10 @@ function reduce(state, events, now) {
14030
14104
  consecutiveIdle: ev.idle === false ? 0 : (state.meta.channel?.consecutiveIdle ?? 0) + 1
14031
14105
  };
14032
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
+ }
14033
14111
  if (ev.intent_sig) {
14034
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 };
14035
14113
  } else {
@@ -14886,6 +14964,13 @@ function recordVerdict(d, v) {
14886
14964
  branch: v.branch,
14887
14965
  decision: v.decision,
14888
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
+ },
14889
14974
  emitted: v.emitted === true,
14890
14975
  idle: v.idle !== false,
14891
14976
  ...v.intent?.verdict && { intent_verdict: v.intent.verdict },
@@ -15542,6 +15627,34 @@ ${addedLines}`,
15542
15627
  }
15543
15628
  return { diffs, has_baseline: true };
15544
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
+ }
15545
15658
  function changedSinceBaseline(repoRelPath, baseline) {
15546
15659
  const pre = preImage(repoRelPath, baseline);
15547
15660
  let current;
@@ -16700,7 +16813,7 @@ function resolveTaskContext(opts) {
16700
16813
  // src/lib/cli-version.ts
16701
16814
  function cliVersion() {
16702
16815
  try {
16703
- return true ? "0.28.1-experimental.89ee5db" : "dev";
16816
+ return true ? "0.28.1-experimental.902dbd9" : "dev";
16704
16817
  } catch {
16705
16818
  return "dev";
16706
16819
  }
@@ -17374,9 +17487,13 @@ function buildAgentContext(input) {
17374
17487
  }
17375
17488
  for (const p of input.pendingItems ?? []) {
17376
17489
  if (lines.length >= MAX_AGENT_ITEMS) break;
17490
+ if (p.pattern_id === "intent-misalignment") continue;
17377
17491
  const text = p.description ?? p.title ?? p.reason;
17378
17492
  if (!text) continue;
17379
- lines.push(renderItem("", text, p.pattern_id, p.file, p.line));
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
+ );
17380
17497
  }
17381
17498
  if (lines.length === 0) return null;
17382
17499
  const body = `${REPORT_PREFIX}
@@ -17637,9 +17754,11 @@ var MAX_SUMMARY_BYTES = 4096;
17637
17754
  var HOME = process.env.HOME ?? "";
17638
17755
  async function extractActionSummary(transcriptPath) {
17639
17756
  try {
17640
- const lines = readTurnLines(transcriptPath);
17641
- if (!lines || lines.length === 0) return null;
17642
- return buildSummary(lines);
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;
17643
17762
  } catch {
17644
17763
  return null;
17645
17764
  }
@@ -17653,9 +17772,11 @@ function readTurnLines(transcriptPath) {
17653
17772
  }
17654
17773
  if (size === 0) return null;
17655
17774
  let raw;
17775
+ let windowed = false;
17656
17776
  if (size <= SMALL_FILE_BYTES) {
17657
17777
  raw = (0, import_node_fs22.readFileSync)(transcriptPath, "utf-8");
17658
17778
  } else {
17779
+ windowed = true;
17659
17780
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
17660
17781
  const fd = require("node:fs").openSync(transcriptPath, "r");
17661
17782
  try {
@@ -17673,17 +17794,22 @@ function readTurnLines(transcriptPath) {
17673
17794
  const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
17674
17795
  if (allLines.length === 0) return null;
17675
17796
  let turnStart = 0;
17797
+ let boundaryFound = false;
17676
17798
  for (let i = allLines.length - 1; i >= 0; i--) {
17677
17799
  try {
17678
17800
  const parsed = JSON.parse(allLines[i]);
17679
17801
  if (parsed.type === "user" && isRealUserMessage(parsed)) {
17680
17802
  turnStart = i;
17803
+ boundaryFound = true;
17681
17804
  break;
17682
17805
  }
17683
17806
  } catch {
17684
17807
  }
17685
17808
  }
17686
- return allLines.slice(turnStart);
17809
+ return {
17810
+ lines: allLines.slice(turnStart),
17811
+ window: !windowed ? "whole" : boundaryFound ? "windowed" : "orphaned"
17812
+ };
17687
17813
  }
17688
17814
  function isRealUserMessage(parsed) {
17689
17815
  const message = parsed.message;
@@ -18241,11 +18367,12 @@ async function readStopHookStdin() {
18241
18367
  return empty;
18242
18368
  }
18243
18369
  }
18244
- function agentContextFor(response, intentRepeat = 0) {
18370
+ function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
18245
18371
  const metadata = response.metadata ?? {};
18246
18372
  const intent = response.intent_alignment ?? {};
18247
18373
  return buildAgentContext({
18248
18374
  intentRepeat,
18375
+ priorPendingFingerprints,
18249
18376
  gateDecision: String(response.gate_decision ?? ""),
18250
18377
  findings: response.findings ?? [],
18251
18378
  pendingItems: response.pending_items ?? [],
@@ -18381,13 +18508,21 @@ async function runAnalyze(opts, globals) {
18381
18508
  const conversation = await readAndClearConversationBuffer(baselineSessionId);
18382
18509
  const specs = discoverSpecs();
18383
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;
18384
18514
  const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
18385
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
+ }
18386
18524
  await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
18387
18525
  }
18388
- const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
18389
- const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
18390
- const canSeeTurnAuthorship = !!actionSummary || !!baseline;
18391
18526
  if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
18392
18527
  await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
18393
18528
  }
@@ -18437,7 +18572,11 @@ async function runAnalyze(opts, globals) {
18437
18572
  );
18438
18573
  }
18439
18574
  if (analysisMode === "skip") {
18440
- await passAndExit("Skip mode \u2014 no code work to analyze", "skip-mode");
18575
+ await passAndExit(
18576
+ "Skip mode \u2014 no code work to analyze",
18577
+ "skip-mode",
18578
+ turnAuthoredCode ? "capacity" : void 0
18579
+ );
18441
18580
  }
18442
18581
  let staticResults = {
18443
18582
  tool: "@codacy/analysis-cli",
@@ -18709,7 +18848,30 @@ async function runAnalyze(opts, globals) {
18709
18848
  hasUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
18710
18849
  isTTY: process.stdout.isTTY === true
18711
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
+ };
18712
18873
  const requestBody = {
18874
+ coverage_telemetry: coverageTelemetry,
18713
18875
  static_results: staticResults,
18714
18876
  code_delta: codeDelta,
18715
18877
  changed_files: allForReview,
@@ -18795,6 +18957,54 @@ async function runAnalyze(opts, globals) {
18795
18957
  // replaced a population floor with ≈35% power that was sub-integer for
18796
18958
  // three-quarters of the fleet.
18797
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
+ })(),
18798
19008
  // P2 — RECORDED, NOT APPLIED. What this run WOULD have reviewed if it
18799
19009
  // narrowed to the within-session increment: what changed since the last
18800
19010
  // VERDICT rather than since task start.
@@ -19041,6 +19251,13 @@ async function runAnalyze(opts, globals) {
19041
19251
  });
19042
19252
  }
19043
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
+ })() : [];
19044
19261
  if (memorySession) {
19045
19262
  try {
19046
19263
  recordVerdict(memorySession.d, {
@@ -19063,7 +19280,10 @@ async function runAnalyze(opts, globals) {
19063
19280
  // What next turn reads as `emittedLast`. A suppressed turn did not
19064
19281
  // speak, so it cannot be the cause of the turn after it — which is what
19065
19282
  // keeps this from becoming a permanent gag.
19066
- 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)
19067
19287
  });
19068
19288
  intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
19069
19289
  } catch {
@@ -19282,7 +19502,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
19282
19502
  // Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
19283
19503
  // the findings themselves are rendered above by the blocking renderer,
19284
19504
  // so what the cut removes is the repeated commentary, never the defect.
19285
- agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
19505
+ agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
19286
19506
  // The coverage note is silenced with it — half a channel is still a channel.
19287
19507
  silenced: !!silenced,
19288
19508
  openElsewhere
@@ -19303,7 +19523,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
19303
19523
  changed: skipCoverageChanged,
19304
19524
  coverage: reviewCoverage,
19305
19525
  userSummary,
19306
- agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
19526
+ agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
19307
19527
  // The coverage note is silenced with it — half a channel is still a channel.
19308
19528
  silenced: !!silenced,
19309
19529
  openElsewhere
@@ -19323,7 +19543,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
19323
19543
  changed: skipCoverageChanged,
19324
19544
  coverage: reviewCoverage,
19325
19545
  userSummary,
19326
- agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
19546
+ agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
19327
19547
  // The coverage note is silenced with it — half a channel is still a channel.
19328
19548
  silenced: !!silenced,
19329
19549
  openElsewhere
@@ -21127,7 +21347,7 @@ function registerTelemetryCommands(program2) {
21127
21347
  }
21128
21348
 
21129
21349
  // src/cli.ts
21130
- program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.89ee5db").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
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 () => {
21131
21351
  try {
21132
21352
  await foldLegacyLocalCredential();
21133
21353
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.28.1-experimental.89ee5db",
3
+ "version": "0.28.1-experimental.902dbd9",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "homepage": "https://verity.md",
6
6
  "bugs": {