@codacy/verity-cli 0.29.4-experimental.7126d67 → 0.29.4-experimental.7475da6

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 CHANGED
@@ -10502,6 +10502,7 @@ var GITHUB_APP_INSTALL_URL = `https://github.com/apps/${GITHUB_APP_SLUG}/install
10502
10502
  function githubAppInstallUrl(accountId) {
10503
10503
  return accountId != null ? `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new/permissions?target_id=${accountId}` : GITHUB_APP_INSTALL_URL;
10504
10504
  }
10505
+ var ADVISORY_EPISODE_FILE = `${VERITY_DIR}/.advisory-episode`;
10505
10506
 
10506
10507
  // src/lib/output.ts
10507
10508
  var RED = "\x1B[0;31m";
@@ -15515,6 +15516,33 @@ ${addedLines}`,
15515
15516
  }
15516
15517
  return { diffs, has_snapshots: true };
15517
15518
  }
15519
+ function ensureSnapshotGitignored() {
15520
+ let content = "";
15521
+ try {
15522
+ content = (0, import_node_fs15.readFileSync)(".gitignore", "utf-8");
15523
+ } catch {
15524
+ }
15525
+ let ignored = null;
15526
+ try {
15527
+ (0, import_node_child_process6.execSync)("git check-ignore -q -- .verity/.snapshot/__probe__", { stdio: "pipe" });
15528
+ ignored = true;
15529
+ } catch (err) {
15530
+ ignored = err.status === 1 ? false : null;
15531
+ }
15532
+ if (ignored === true) return "covered";
15533
+ if (ignored === null) {
15534
+ const lines = content.split("\n").map((l) => l.trim());
15535
+ const covering = [".verity/.snapshot/", ".verity/.snapshot", ".verity/", ".verity", ".verity/*"];
15536
+ if (lines.some((l) => covering.includes(l))) return "covered";
15537
+ }
15538
+ try {
15539
+ const block = "# Verity \u2014 snapshots of analyzed files (machine state, never commit)\n.verity/.snapshot/\n";
15540
+ (0, import_node_fs15.writeFileSync)(".gitignore", content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block);
15541
+ return "added";
15542
+ } catch {
15543
+ return "failed";
15544
+ }
15545
+ }
15518
15546
  function saveSnapshots(files) {
15519
15547
  const snapshotPaths = /* @__PURE__ */ new Set();
15520
15548
  for (const file of files) {
@@ -15559,7 +15587,7 @@ function cleanStaleSnapshots(dir, keepSet) {
15559
15587
  try {
15560
15588
  const entries = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true });
15561
15589
  for (const entry of entries) {
15562
- if (entry.name.startsWith(".")) continue;
15590
+ if (dir === SNAPSHOT_DIR && (entry.name === ".diff-old.tmp" || entry.name === ".diff-new.tmp")) continue;
15563
15591
  const fullPath = (0, import_node_path14.join)(dir, entry.name);
15564
15592
  if (entry.isDirectory()) {
15565
15593
  cleanStaleSnapshots(fullPath, keepSet);
@@ -17148,6 +17176,39 @@ async function bootstrap(run) {
17148
17176
  Object.assign(run, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
17149
17177
  }
17150
17178
 
17179
+ // src/lib/self-scope.ts
17180
+ var LEGACY_GATE_SKILLS = /* @__PURE__ */ new Set([
17181
+ "gate-setup",
17182
+ "gate-analyze",
17183
+ "gate-review",
17184
+ "gate-status",
17185
+ "gate-feedback",
17186
+ "gate-insights",
17187
+ "gate-learn",
17188
+ "gate-memory",
17189
+ "gate-reflect"
17190
+ ]);
17191
+ function isVerityOwned(path) {
17192
+ const segments = path.replace(/\\/g, "/").split("/");
17193
+ for (let i = 0; i < segments.length; i++) {
17194
+ const seg = segments[i];
17195
+ if (seg === ".verity" || seg === ".codacy") return true;
17196
+ if (i === segments.length - 1 && (seg === "VERITY.md" || seg === "GATE.md")) return true;
17197
+ if (seg === ".claude" && segments[i + 1] === "skills" && typeof segments[i + 2] === "string") {
17198
+ const skill = segments[i + 2];
17199
+ if (skill.startsWith("verity-") || LEGACY_GATE_SKILLS.has(skill)) return true;
17200
+ }
17201
+ if (seg === ".claude" && segments[i + 1] === "settings.json") return true;
17202
+ }
17203
+ return false;
17204
+ }
17205
+ function partitionVerityOwned(paths) {
17206
+ const kept = [];
17207
+ const owned = [];
17208
+ for (const p of paths) (isVerityOwned(p) ? owned : kept).push(p);
17209
+ return { kept, owned };
17210
+ }
17211
+
17151
17212
  // src/lib/channel.ts
17152
17213
  var MAX_AGENT_CONTEXT_CHARS = 1500;
17153
17214
  var MAX_AGENT_ITEMS = 5;
@@ -17157,6 +17218,29 @@ function renderItem(label2, text, patternId, file, line) {
17157
17218
  const id = patternId ? ` [${patternId}]` : "";
17158
17219
  return `- ${label2}${text}${where}${id}`;
17159
17220
  }
17221
+ function channelInputFrom(response, intentRepeat = 0, priorPendingFingerprints = []) {
17222
+ const metadata = response.metadata ?? {};
17223
+ const intent = response.intent_alignment ?? {};
17224
+ return {
17225
+ intentRepeat,
17226
+ priorPendingFingerprints,
17227
+ gateDecision: String(response.gate_decision ?? ""),
17228
+ findings: response.findings ?? [],
17229
+ pendingItems: response.pending_items ?? [],
17230
+ reviewStatus: metadata.review_status,
17231
+ coverage: metadata.coverage,
17232
+ intentVerdict: intent.verdict,
17233
+ intentGaps: intent.gaps
17234
+ };
17235
+ }
17236
+ function classifyChannelContent(input) {
17237
+ const refusal = input.reviewStatus === "not_reviewed" || input.reviewStatus === "no_authorship_evidence";
17238
+ const intentFlag = input.intentVerdict === "misaligned" || input.intentVerdict === "partial";
17239
+ const advisory = (input.findings ?? []).some((f) => f.scope !== "pre-existing") || (input.pendingItems ?? []).some(
17240
+ (p) => p.pattern_id !== "intent-misalignment" && !!(p.description ?? p.title ?? p.reason)
17241
+ );
17242
+ return { refusal, intentFlag, advisory };
17243
+ }
17160
17244
  function buildAgentContext(input) {
17161
17245
  const lines = [];
17162
17246
  if (input.reviewStatus === "not_reviewed") {
@@ -17242,7 +17326,7 @@ function channelSilence(input) {
17242
17326
  // src/lib/cli-version.ts
17243
17327
  function cliVersion() {
17244
17328
  try {
17245
- return true ? "0.29.4-experimental.7126d67" : "dev";
17329
+ return true ? "0.29.4-experimental.7475da6" : "dev";
17246
17330
  } catch {
17247
17331
  return "dev";
17248
17332
  }
@@ -17564,9 +17648,10 @@ async function scope(run) {
17564
17648
  const { assistantResponse } = run;
17565
17649
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
17566
17650
  run.changedUniverse = allChanged;
17567
- const analyzable = filterAnalyzable(allChanged);
17568
- const reviewable = filterReviewable(allChanged);
17569
- const securityFiles = filterSecurity(allChanged);
17651
+ const { kept: external } = partitionVerityOwned(allChanged);
17652
+ const analyzable = filterAnalyzable(external);
17653
+ const reviewable = filterReviewable(external);
17654
+ const securityFiles = filterSecurity(external);
17570
17655
  const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
17571
17656
  if (noFilesChanged && !assistantResponse) {
17572
17657
  await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
@@ -18059,26 +18144,50 @@ function narrowToRecent(files, sessionId) {
18059
18144
  });
18060
18145
  return recent.length > 0 ? recent : files;
18061
18146
  }
18062
- function readIterationState(currentCommit) {
18063
- if (!(0, import_node_fs22.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
18147
+ function readIteration(currentCommit, _contentHash) {
18148
+ return Math.max(1, readBlockState(currentCommit).attempts);
18149
+ }
18150
+ var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
18151
+ function readBlockState(currentCommit, opts) {
18152
+ if (opts?.newUserPrompt) return NO_BLOCKS;
18153
+ if (!(0, import_node_fs22.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
18064
18154
  try {
18065
18155
  const stored = (0, import_node_fs22.readFileSync)(ITERATION_FILE, "utf-8").trim();
18066
- const parts = stored.split(":");
18067
- const iter = parseInt(parts[0], 10);
18068
- const storedCommit = parts[1] ?? "";
18069
- const storedTimestamp = parseInt(parts[2] ?? "0", 10);
18070
- const fingerprint = parts.slice(3).join(":") || null;
18071
- if (isNaN(iter)) return { iteration: 1, fingerprint: null };
18072
- if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
18073
- if (storedTimestamp > 0) {
18074
- const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
18075
- if (elapsed > 600) return { iteration: 1, fingerprint: null };
18076
- }
18077
- return { iteration: iter, fingerprint };
18156
+ const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
18157
+ if (!parsed) return NO_BLOCKS;
18158
+ if (parsed.commit !== currentCommit) return NO_BLOCKS;
18159
+ if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
18160
+ return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
18078
18161
  } catch {
18079
- return { iteration: 1, fingerprint: null };
18162
+ return NO_BLOCKS;
18080
18163
  }
18081
18164
  }
18165
+ function parseJsonState(raw) {
18166
+ const o = JSON.parse(raw);
18167
+ const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
18168
+ if (isNaN(attempts)) return null;
18169
+ return {
18170
+ attempts,
18171
+ blocks: typeof o.blocks === "number" ? o.blocks : attempts,
18172
+ fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
18173
+ commit: typeof o.commit === "string" ? o.commit : "",
18174
+ ts: typeof o.ts === "number" ? o.ts : 0
18175
+ };
18176
+ }
18177
+ function parseLegacyState(raw) {
18178
+ const parts = raw.split(":");
18179
+ const n = parseInt(parts[0], 10);
18180
+ if (isNaN(n)) return null;
18181
+ return {
18182
+ attempts: n,
18183
+ // The old file has no separate block count; the old counter is the closest
18184
+ // honest answer, and it errs toward releasing sooner rather than later.
18185
+ blocks: n,
18186
+ fingerprint: parts.slice(3).join(":") || null,
18187
+ commit: parts[1] ?? "",
18188
+ ts: parseInt(parts[2] ?? "0", 10)
18189
+ };
18190
+ }
18082
18191
  function findingsFingerprint(findings) {
18083
18192
  const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
18084
18193
  return [...new Set(keys)].sort().join(",");
@@ -18088,11 +18197,22 @@ function isSameProblem(previous, current) {
18088
18197
  const prev = new Set(previous.split(","));
18089
18198
  return current.split(",").some((k) => prev.has(k));
18090
18199
  }
18091
- function writeIteration(iteration, commit, _contentHash, fingerprint) {
18200
+ function writeBlockState(commit, state) {
18092
18201
  (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
18093
- const ts = Math.floor(Date.now() / 1e3);
18094
- const fp = fingerprint ? `:${fingerprint}` : "";
18095
- (0, import_node_fs22.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
18202
+ (0, import_node_fs22.writeFileSync)(
18203
+ ITERATION_FILE,
18204
+ JSON.stringify({
18205
+ v: 2,
18206
+ attempts: state.attempts,
18207
+ blocks: state.blocks,
18208
+ commit,
18209
+ ts: Math.floor(Date.now() / 1e3),
18210
+ fingerprint: state.fingerprint ?? void 0
18211
+ })
18212
+ );
18213
+ }
18214
+ function resetBlockState(commit) {
18215
+ writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
18096
18216
  }
18097
18217
 
18098
18218
  // src/lib/fold.ts
@@ -18690,13 +18810,13 @@ async function evidence(run) {
18690
18810
  snapshotResult = generateSnapshotDiffs(codeDelta.files);
18691
18811
  }
18692
18812
  currentCommit = getCurrentCommit();
18693
- iteration = readIterationState(currentCommit).iteration;
18813
+ iteration = readIteration(currentCommit);
18694
18814
  }
18695
18815
  }
18696
18816
  if (analysisMode === "plan") {
18697
18817
  recordAnalysisStart();
18698
18818
  currentCommit = getCurrentCommit();
18699
- iteration = readIterationState(currentCommit).iteration;
18819
+ iteration = readIteration(currentCommit);
18700
18820
  }
18701
18821
  Object.assign(run, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
18702
18822
  }
@@ -18792,7 +18912,8 @@ function gatherContextFiles(contextPaths, deltaFiles) {
18792
18912
  // src/commands/analyze/phases/07-context-files.ts
18793
18913
  async function contextFiles(run) {
18794
18914
  const { codeDelta, contextFilePaths } = run;
18795
- const contextFiles2 = gatherContextFiles(contextFilePaths, codeDelta.files);
18915
+ const { kept: externalContext } = partitionVerityOwned(contextFilePaths ?? []);
18916
+ const contextFiles2 = gatherContextFiles(externalContext, codeDelta.files);
18796
18917
  for (const f of codeDelta.files) {
18797
18918
  f.role = "delta";
18798
18919
  }
@@ -19362,6 +19483,54 @@ async function workingMemory(run) {
19362
19483
  Object.assign(run, { incrementReport, memory, memorySession, reachability });
19363
19484
  }
19364
19485
 
19486
+ // src/lib/note-budget.ts
19487
+ var import_node_fs28 = require("node:fs");
19488
+ var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
19489
+ var EPISODE_STALE_SECONDS = 30 * 60;
19490
+ var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
19491
+ function resolveEpisode(prev, signals) {
19492
+ if (!prev) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19493
+ if (signals.humanSpoke) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19494
+ if (signals.rawFail) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19495
+ if (prev.tasksCompleted !== signals.tasksCompleted) {
19496
+ return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19497
+ }
19498
+ if (prev.ts > 0 && signals.now - prev.ts > EPISODE_STALE_SECONDS) {
19499
+ return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19500
+ }
19501
+ return prev;
19502
+ }
19503
+ function advisoryBudgetSpent(episode, rawDecision) {
19504
+ const budget = ADVISORY_BUDGET[rawDecision] ?? ADVISORY_BUDGET.WARN;
19505
+ return episode.delivered >= budget;
19506
+ }
19507
+ function readAdvisoryEpisode(sessionId) {
19508
+ const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
19509
+ if (!(0, import_node_fs28.existsSync)(file)) return null;
19510
+ try {
19511
+ const o = JSON.parse((0, import_node_fs28.readFileSync)(file, "utf-8")) ?? {};
19512
+ const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
19513
+ if (isNaN(delivered)) return null;
19514
+ return {
19515
+ delivered,
19516
+ tasksCompleted: typeof o.tasksCompleted === "number" ? o.tasksCompleted : 0,
19517
+ ts: typeof o.ts === "number" ? o.ts : 0
19518
+ };
19519
+ } catch {
19520
+ return null;
19521
+ }
19522
+ }
19523
+ function writeAdvisoryEpisode(episode, sessionId) {
19524
+ try {
19525
+ (0, import_node_fs28.mkdirSync)(VERITY_DIR, { recursive: true });
19526
+ (0, import_node_fs28.writeFileSync)(
19527
+ scopedFile(ADVISORY_EPISODE_FILE, sessionId),
19528
+ JSON.stringify({ v: 1, ...episode })
19529
+ );
19530
+ } catch {
19531
+ }
19532
+ }
19533
+
19365
19534
  // src/lib/run-mode.ts
19366
19535
  function parseAutonomousEnv(raw) {
19367
19536
  if (raw === void 0) return void 0;
@@ -19455,7 +19624,13 @@ async function buildRequest(run) {
19455
19624
  excluded_by_reason: excludedByReason,
19456
19625
  // was the transcript itself truncated? The 256 KB window means "this turn"
19457
19626
  // can quietly mean "the last 256 KB of it".
19458
- transcript_windowed: actionSummary?.transcript_windowed ?? null
19627
+ transcript_windowed: actionSummary?.transcript_windowed ?? null,
19628
+ // The advisory budget's fleet counter-metric (note-budget.ts): deliveries in
19629
+ // the episode as of the PREVIOUS turn — this runs before phase 13 updates
19630
+ // the state, so the number is one turn lagged by construction. The
19631
+ // degenerate win for the budget is a dead channel that looks like clean
19632
+ // code; this is what makes "did delivery rate collapse" a query.
19633
+ advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
19459
19634
  };
19460
19635
  const requestBody = {
19461
19636
  coverage_telemetry: coverageTelemetry,
@@ -19594,7 +19769,8 @@ async function buildRequest(run) {
19594
19769
  }
19595
19770
  const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
19596
19771
  const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
19597
- const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task;
19772
+ const planApprovalActive = foldResult?.planApproval?.activeSinceLastPrompt === true;
19773
+ const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task || planApprovalActive;
19598
19774
  if (hasIntent) {
19599
19775
  const intentContext = {};
19600
19776
  if (conversation && conversation.prompts.length > 0) {
@@ -19626,7 +19802,7 @@ async function buildRequest(run) {
19626
19802
  intentContext.user_prompt = w4Task.goal;
19627
19803
  logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
19628
19804
  }
19629
- if (foldResult?.planApproval?.activeSinceLastPrompt && intentContext.user_prompt) {
19805
+ if (planApprovalActive) {
19630
19806
  intentContext.plan_approved = true;
19631
19807
  logEvent("plan_approval_carried", { approvals: foldResult.planApproval.approvals });
19632
19808
  }
@@ -19649,14 +19825,14 @@ async function buildRequest(run) {
19649
19825
  }
19650
19826
 
19651
19827
  // src/lib/offline.ts
19652
- var import_node_fs28 = require("node:fs");
19828
+ var import_node_fs29 = require("node:fs");
19653
19829
  var import_node_crypto11 = require("node:crypto");
19654
19830
  function cacheRequest(body) {
19655
19831
  try {
19656
- (0, import_node_fs28.mkdirSync)(CACHE_DIR, { recursive: true });
19832
+ (0, import_node_fs29.mkdirSync)(CACHE_DIR, { recursive: true });
19657
19833
  const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
19658
19834
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
19659
- (0, import_node_fs28.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
19835
+ (0, import_node_fs29.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
19660
19836
  } catch {
19661
19837
  }
19662
19838
  }
@@ -19775,10 +19951,10 @@ async function transmit(run) {
19775
19951
  }
19776
19952
 
19777
19953
  // src/commands/analyze/phases/13-reconcile.ts
19778
- var import_node_fs29 = require("node:fs");
19954
+ var import_node_fs30 = require("node:fs");
19779
19955
  var import_node_path23 = require("node:path");
19780
19956
  async function reconcile(run) {
19781
- const { actionSummary, allChanged, analyzable, baseline, codeDelta, contentHash, conversation, decision, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
19957
+ const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
19782
19958
  const sentPaths = codeDelta.files.map((f) => f.path);
19783
19959
  let openElsewhere = [];
19784
19960
  if (memorySession) {
@@ -19786,7 +19962,7 @@ async function reconcile(run) {
19786
19962
  const st = foldDossier(memorySession.d);
19787
19963
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
19788
19964
  try {
19789
- const src = (0, import_node_fs29.readFileSync)((0, import_node_path23.join)(repoRoot(), file), "utf8").split("\n");
19965
+ const src = (0, import_node_fs30.readFileSync)((0, import_node_path23.join)(repoRoot(), file), "utf8").split("\n");
19790
19966
  const at = src[line - 1];
19791
19967
  return at === void 0 ? null : lineSha(at);
19792
19968
  } catch {
@@ -19796,6 +19972,7 @@ async function reconcile(run) {
19796
19972
  } catch {
19797
19973
  }
19798
19974
  }
19975
+ const { kept: externalChanged, owned: verityOwned } = partitionVerityOwned(allChanged);
19799
19976
  const reviewCoverage = {
19800
19977
  reviewed: sentPaths,
19801
19978
  // Declared drops from the stages that DO report themselves today. The other
@@ -19837,11 +20014,20 @@ async function reconcile(run) {
19837
20014
  stage: "baseline-scoping",
19838
20015
  kind: "policy"
19839
20016
  })),
20017
+ // ⚠ VERITY'S OWN FILES, named as such — not laundered into the
20018
+ // extension bucket below, where "we do not review our own installer's
20019
+ // dirt" would read as "a changed README". See self-scope.ts.
20020
+ ...verityOwned.map((path) => ({
20021
+ path,
20022
+ reason: "verity-owned",
20023
+ stage: "self-scope",
20024
+ kind: "policy"
20025
+ })),
19840
20026
  // The extension allowlist. POLICY: a changed README was never going to be
19841
20027
  // reviewed, and calling that a coverage gap would downgrade nearly every
19842
20028
  // PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
19843
20029
  // so "what did Verity ignore entirely" is answerable.
19844
- ...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
20030
+ ...externalChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
19845
20031
  path,
19846
20032
  reason: "not-a-reviewed-file-type",
19847
20033
  stage: "extension-allowlist",
@@ -19878,6 +20064,29 @@ async function reconcile(run) {
19878
20064
  decision
19879
20065
  });
19880
20066
  }
20067
+ const episodeSignals = {
20068
+ humanSpoke: (conversation?.prompts?.length ?? 0) > 0,
20069
+ rawFail: decision === "FAIL",
20070
+ tasksCompleted: (foldResult?.tasks ?? []).filter((t) => t.status === "completed").length,
20071
+ now: Math.floor(Date.now() / 1e3)
20072
+ };
20073
+ let episode = resolveEpisode(readAdvisoryEpisode(baselineSessionId), episodeSignals);
20074
+ const contentClass = classifyChannelContent(channelInputFrom(response));
20075
+ const wouldCarryAdvisory = contentClass.advisory || openElsewhere.length > 0;
20076
+ if (decision !== "FAIL" && !silenced && wouldCarryAdvisory && !contentClass.refusal && !contentClass.intentFlag && advisoryBudgetSpent(episode, decision)) {
20077
+ silenced = "note-budget";
20078
+ logEvent("channel_silenced", {
20079
+ reason: silenced,
20080
+ run_id: response.run_id ?? turnId,
20081
+ decision,
20082
+ episode_delivered: episode.delivered
20083
+ });
20084
+ }
20085
+ const deliveringAdvisory = decision !== "FAIL" && !silenced && wouldCarryAdvisory;
20086
+ writeAdvisoryEpisode(
20087
+ { ...episode, delivered: episode.delivered + (deliveringAdvisory ? 1 : 0), ts: episodeSignals.now },
20088
+ baselineSessionId
20089
+ );
19881
20090
  let intentRepeatCount = 0;
19882
20091
  const priorPendingFingerprints = memorySession ? (() => {
19883
20092
  try {
@@ -19964,6 +20173,39 @@ ${YELLOW2}${note}${NC2}
19964
20173
  return exit(0);
19965
20174
  }
19966
20175
 
20176
+ // src/lib/may-block.ts
20177
+ var HARD_BLOCK_CEILING = 5;
20178
+ function mayBlock(input) {
20179
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
20180
+ if (input.reviewedFileCount === 0 && input.staticFindingCount === 0) {
20181
+ return { block: false, release: "no-code-reviewed" };
20182
+ }
20183
+ if (input.cycleCutFired) {
20184
+ return { block: false, release: "nothing-moved" };
20185
+ }
20186
+ if (input.attempts > input.maxIterations) {
20187
+ return { block: false, release: "same-problem-cap" };
20188
+ }
20189
+ if (input.blocks > ceiling) {
20190
+ return { block: false, release: "block-ceiling" };
20191
+ }
20192
+ return { block: true, release: null };
20193
+ }
20194
+ function describeRelease(release, input) {
20195
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
20196
+ const open = input.findingCount > 0 ? `${input.findingCount} finding(s) remain OPEN and were NOT fixed. Human review required before deploying.` : "Human review required before deploying.";
20197
+ switch (release) {
20198
+ case "no-code-reviewed":
20199
+ return `Verity: WARN \u2014 NOT BLOCKING: no code was reviewed on this turn, so there is nothing here to fix. Reported as advice instead. ${open}`;
20200
+ case "nothing-moved":
20201
+ return `Verity: WARN \u2014 NOT BLOCKING: nothing has changed since the last verdict, so re-raising it cannot move anything forward. ${open}`;
20202
+ case "same-problem-cap":
20203
+ return `Verity: WARN \u2014 self-healing limit (${input.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${open}`;
20204
+ case "block-ceiling":
20205
+ return `Verity: WARN \u2014 ${ceiling} consecutive blocking verdicts reached; releasing the block so this cannot loop. ${open}`;
20206
+ }
20207
+ }
20208
+
19967
20209
  // src/lib/remediation-guard.ts
19968
20210
  var TOOL_CONFIG_PATTERNS = [
19969
20211
  /(^|\/)\.codacy\//,
@@ -20006,19 +20248,7 @@ function screenRemediation(fix, findingFile) {
20006
20248
 
20007
20249
  // src/commands/analyze/phases/14-render.ts
20008
20250
  function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
20009
- const metadata = response.metadata ?? {};
20010
- const intent = response.intent_alignment ?? {};
20011
- return buildAgentContext({
20012
- intentRepeat,
20013
- priorPendingFingerprints,
20014
- gateDecision: String(response.gate_decision ?? ""),
20015
- findings: response.findings ?? [],
20016
- pendingItems: response.pending_items ?? [],
20017
- reviewStatus: metadata.review_status,
20018
- coverage: metadata.coverage,
20019
- intentVerdict: intent.verdict,
20020
- intentGaps: intent.gaps
20021
- });
20251
+ return buildAgentContext(channelInputFrom(response, intentRepeat, priorPendingFingerprints));
20022
20252
  }
20023
20253
  async function render(run) {
20024
20254
  const { opts, globals } = run;
@@ -20112,38 +20342,63 @@ async function render(run) {
20112
20342
  reverify_by: response.reverify_by
20113
20343
  });
20114
20344
  const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
20115
- let capReleased = false;
20345
+ let release = null;
20116
20346
  let effectiveDecision = decision;
20117
20347
  if (decision === "FAIL") {
20118
- const blocking = (response.findings ?? []).filter((f) => {
20348
+ const findings = response.findings ?? [];
20349
+ const blocking = findings.filter((f) => {
20119
20350
  const sev = String(f.severity ?? "").toLowerCase();
20120
20351
  return sev === "critical" || sev === "high";
20121
20352
  });
20122
20353
  const fingerprint = findingsFingerprint(blocking);
20123
- const prior = readIterationState(currentCommit);
20354
+ const prior = readBlockState(currentCommit, {
20355
+ newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
20356
+ });
20124
20357
  const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
20125
- const nextIteration = sameProblem ? prior.iteration + 1 : 1;
20126
20358
  const maxIterations = parseInt(opts.maxIterations, 10);
20127
- writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
20128
- iteration = nextIteration;
20129
- if (nextIteration > maxIterations) {
20130
- capReleased = true;
20359
+ const attempts = sameProblem ? prior.attempts + 1 : 1;
20360
+ const blocks = prior.blocks + 1;
20361
+ const decisionNow = mayBlock({
20362
+ reviewedFileCount: codeDelta.files.length,
20363
+ staticFindingCount: run.staticResults?.findings?.length ?? 0,
20364
+ cycleCutFired: silenced !== null,
20365
+ attempts,
20366
+ blocks,
20367
+ maxIterations
20368
+ });
20369
+ if (decisionNow.block) {
20370
+ writeBlockState(currentCommit, { attempts, blocks, fingerprint });
20371
+ iteration = attempts;
20372
+ } else {
20373
+ release = decisionNow.release;
20131
20374
  effectiveDecision = "WARN";
20132
- logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
20375
+ logEvent("block_released", {
20376
+ reason: release,
20377
+ attempts,
20378
+ blocks,
20379
+ reviewed_files: codeDelta.files.length,
20380
+ cycle_cut: silenced,
20381
+ fingerprint
20382
+ });
20133
20383
  }
20134
20384
  }
20135
- if (capReleased) {
20385
+ if (release) {
20136
20386
  const findings = response.findings ?? [];
20137
20387
  const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
20388
+ const summary = describeRelease(release, {
20389
+ findingCount: findings.length,
20390
+ maxIterations: parseInt(opts.maxIterations, 10)
20391
+ });
20138
20392
  emitVerdict({
20139
20393
  proposed: "WARN",
20140
20394
  changed: run.changedUniverse,
20141
20395
  coverage: reviewCoverage,
20142
- 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.
20143
- ${lines.join("\n")}`,
20396
+ userSummary: lines.length > 0 ? `${summary}
20397
+ ${lines.join("\n")}` : summary,
20144
20398
  agentContext: null,
20145
20399
  silenced: true
20146
20400
  });
20401
+ return;
20147
20402
  }
20148
20403
  switch (effectiveDecision) {
20149
20404
  case "FAIL": {
@@ -20242,7 +20497,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20242
20497
  break;
20243
20498
  }
20244
20499
  case "PASS": {
20245
- writeIteration(1, currentCommit, contentHash ?? void 0);
20500
+ resetBlockState(currentCommit);
20246
20501
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20247
20502
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20248
20503
  let userSummary = response.user_summary ?? "Verity: PASS";
@@ -20263,6 +20518,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20263
20518
  break;
20264
20519
  }
20265
20520
  case "WARN": {
20521
+ if (decision !== "FAIL") resetBlockState(currentCommit);
20266
20522
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20267
20523
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20268
20524
  let userSummary = response.user_summary ?? "Verity: WARN";
@@ -20361,7 +20617,7 @@ async function runAnalyze(opts, globals) {
20361
20617
  }
20362
20618
 
20363
20619
  // src/commands/baseline.ts
20364
- var import_node_fs30 = require("node:fs");
20620
+ var import_node_fs31 = require("node:fs");
20365
20621
  function registerBaselineCommands(program2) {
20366
20622
  const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
20367
20623
  baseline.command("capture").description("Snapshot the working tree at task start (used by SessionStart hook)").option("--session-id <id>", "Session id (overrides any value from stdin)").option("--source <source>", "Lifecycle hint: startup|resume|clear|compact").action(async (opts) => {
@@ -20370,7 +20626,7 @@ function registerBaselineCommands(program2) {
20370
20626
  process.chdir(repoRoot());
20371
20627
  } catch {
20372
20628
  }
20373
- if (!(0, import_node_fs30.existsSync)(VERITY_DIR)) {
20629
+ if (!(0, import_node_fs31.existsSync)(VERITY_DIR)) {
20374
20630
  process.exit(0);
20375
20631
  }
20376
20632
  let sessionId = opts.sessionId;
@@ -20410,7 +20666,7 @@ async function readStdin() {
20410
20666
  }
20411
20667
 
20412
20668
  // src/commands/review.ts
20413
- var import_node_fs31 = require("node:fs");
20669
+ var import_node_fs32 = require("node:fs");
20414
20670
  function registerReviewCommand(program2) {
20415
20671
  program2.command("review").description("Run on-demand Verity analysis (advisory, never blocks)").requiredOption("--files <paths>", "Comma-separated file list").option("--changed <paths>", "Subset of --files that were modified").option("--intent <text>", "User intent description (max 2000 chars)").option("--specs <paths>", "Comma-separated spec file paths").option("--json", "Output raw JSON response").action(async (opts) => {
20416
20672
  const globals = program2.opts();
@@ -20429,7 +20685,7 @@ async function runReview(opts, globals) {
20429
20685
  const securityFiles = filterSecurity(allFiles);
20430
20686
  let staticResults;
20431
20687
  if (isCodacyAvailable()) {
20432
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs31.existsSync)(f) || resolveFile(f) !== null);
20688
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs32.existsSync)(f) || resolveFile(f) !== null);
20433
20689
  staticResults = runCodacyAnalysis(scannable);
20434
20690
  } else {
20435
20691
  staticResults = {
@@ -20455,10 +20711,10 @@ async function runReview(opts, globals) {
20455
20711
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
20456
20712
  specs = [];
20457
20713
  for (const p of specPaths) {
20458
- if (!(0, import_node_fs31.existsSync)(p)) continue;
20714
+ if (!(0, import_node_fs32.existsSync)(p)) continue;
20459
20715
  try {
20460
- const { readFileSync: readFileSync18 } = await import("node:fs");
20461
- const content = readFileSync18(p, "utf-8");
20716
+ const { readFileSync: readFileSync19 } = await import("node:fs");
20717
+ const content = readFileSync19(p, "utf-8");
20462
20718
  specs.push({ path: p, content: content.slice(0, 10240) });
20463
20719
  } catch {
20464
20720
  }
@@ -20515,7 +20771,7 @@ async function runReview(opts, globals) {
20515
20771
  }
20516
20772
 
20517
20773
  // src/commands/guard.ts
20518
- var import_node_fs32 = require("node:fs");
20774
+ var import_node_fs33 = require("node:fs");
20519
20775
  var import_node_path24 = require("node:path");
20520
20776
  var GUARD_BLOCK_CAP = 2;
20521
20777
  var GUARD_ITER_FILE = (0, import_node_path24.join)(VERITY_DIR, ".guard-iteration");
@@ -20582,7 +20838,7 @@ function classifyCommand2(command, on) {
20582
20838
  }
20583
20839
  function readIterMap() {
20584
20840
  try {
20585
- const raw = JSON.parse((0, import_node_fs32.readFileSync)(GUARD_ITER_FILE, "utf-8"));
20841
+ const raw = JSON.parse((0, import_node_fs33.readFileSync)(GUARD_ITER_FILE, "utf-8"));
20586
20842
  if (raw && typeof raw === "object") {
20587
20843
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
20588
20844
  return { [raw.moment]: raw.count };
@@ -20602,10 +20858,10 @@ function readIter(moment) {
20602
20858
  }
20603
20859
  function writeIter(moment, count) {
20604
20860
  try {
20605
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20861
+ (0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
20606
20862
  const map = readIterMap();
20607
20863
  map[moment] = count;
20608
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20864
+ (0, import_node_fs33.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20609
20865
  } catch {
20610
20866
  }
20611
20867
  }
@@ -20615,10 +20871,10 @@ function resetIter(moment) {
20615
20871
  if (!(moment in map)) return;
20616
20872
  delete map[moment];
20617
20873
  if (Object.keys(map).length === 0) {
20618
- if ((0, import_node_fs32.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs32.unlinkSync)(GUARD_ITER_FILE);
20874
+ if ((0, import_node_fs33.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs33.unlinkSync)(GUARD_ITER_FILE);
20619
20875
  } else {
20620
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20621
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20876
+ (0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
20877
+ (0, import_node_fs33.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20622
20878
  }
20623
20879
  } catch {
20624
20880
  }
@@ -20682,7 +20938,7 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
20682
20938
  const securityFiles = filterSecurity(files);
20683
20939
  let staticResults;
20684
20940
  if (isCodacyAvailable()) {
20685
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs32.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
20941
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs33.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
20686
20942
  staticResults = runCodacyAnalysis(scannable);
20687
20943
  } else {
20688
20944
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -20727,7 +20983,7 @@ function emitAllowNotice(userMsg, agentMsg) {
20727
20983
  async function runGuard(opts, globals) {
20728
20984
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
20729
20985
  const { command, cwd, sessionId } = await readPreToolUseStdin();
20730
- if (cwd && (0, import_node_fs32.existsSync)(cwd)) {
20986
+ if (cwd && (0, import_node_fs33.existsSync)(cwd)) {
20731
20987
  try {
20732
20988
  process.chdir(cwd);
20733
20989
  } catch {
@@ -20833,14 +21089,14 @@ function writeBlockMessage(moment, response) {
20833
21089
  }
20834
21090
 
20835
21091
  // src/commands/init.ts
20836
- var import_node_fs34 = require("node:fs");
21092
+ var import_node_fs35 = require("node:fs");
20837
21093
  var import_promises13 = require("node:fs/promises");
20838
21094
  var import_node_path26 = require("node:path");
20839
21095
  var import_node_child_process10 = require("node:child_process");
20840
21096
  var readline2 = __toESM(require("node:readline/promises"));
20841
21097
 
20842
21098
  // src/commands/migrate.ts
20843
- var import_node_fs33 = require("node:fs");
21099
+ var import_node_fs34 = require("node:fs");
20844
21100
  var import_node_path25 = require("node:path");
20845
21101
  var import_node_child_process9 = require("node:child_process");
20846
21102
 
@@ -20972,10 +21228,10 @@ async function runMigration(opts = {}) {
20972
21228
  function migrateProjectDir(root, actions) {
20973
21229
  const gateDir = (0, import_node_path25.join)(root, ".gate");
20974
21230
  const verityDir = (0, import_node_path25.join)(root, ".verity");
20975
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) {
21231
+ if ((0, import_node_fs34.existsSync)(gateDir) && !(0, import_node_fs34.existsSync)(verityDir)) {
20976
21232
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
20977
21233
  }
20978
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21234
+ if ((0, import_node_fs34.existsSync)(gateDir) && (0, import_node_fs34.existsSync)(verityDir)) {
20979
21235
  return migrateProjectDirCarry(gateDir, verityDir, actions);
20980
21236
  }
20981
21237
  return false;
@@ -20996,13 +21252,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
20996
21252
  }
20997
21253
  }
20998
21254
  if (moved) {
20999
- if ((0, import_node_fs33.existsSync)(gateDir)) {
21255
+ if ((0, import_node_fs34.existsSync)(gateDir)) {
21000
21256
  const carried = carryLegacyContents(gateDir, verityDir);
21001
21257
  if (carried > 0) {
21002
21258
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
21003
21259
  }
21004
21260
  try {
21005
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21261
+ (0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
21006
21262
  } catch {
21007
21263
  }
21008
21264
  }
@@ -21018,7 +21274,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
21018
21274
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
21019
21275
  }
21020
21276
  try {
21021
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21277
+ (0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
21022
21278
  } catch {
21023
21279
  }
21024
21280
  return carried > 0;
@@ -21027,9 +21283,9 @@ function migrateGlobalCredentials(home, actions) {
21027
21283
  if (!home) return;
21028
21284
  const gateCreds = (0, import_node_path25.join)(home, ".gate", "credentials");
21029
21285
  const verityCreds = (0, import_node_path25.join)(home, ".verity", "credentials");
21030
- if (!(0, import_node_fs33.existsSync)(gateCreds)) return;
21031
- if (!(0, import_node_fs33.existsSync)(verityCreds)) {
21032
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.join)(home, ".verity"), { recursive: true });
21286
+ if (!(0, import_node_fs34.existsSync)(gateCreds)) return;
21287
+ if (!(0, import_node_fs34.existsSync)(verityCreds)) {
21288
+ (0, import_node_fs34.mkdirSync)((0, import_node_path25.join)(home, ".verity"), { recursive: true });
21033
21289
  moveFile(gateCreds, verityCreds);
21034
21290
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
21035
21291
  return;
@@ -21052,7 +21308,7 @@ async function migrateLegacyHooks(root, actions) {
21052
21308
  }
21053
21309
  async function migrateClaudeMd(root, actions) {
21054
21310
  const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
21055
- const hadLegacyBlock = (0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21311
+ const hadLegacyBlock = (0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21056
21312
  if (!hadLegacyBlock) return;
21057
21313
  try {
21058
21314
  await ensureClaudeMdPointer(root);
@@ -21064,7 +21320,7 @@ async function migrateClaudeMd(root, actions) {
21064
21320
  function migrateStandardFile(root, actions) {
21065
21321
  const gateMd = (0, import_node_path25.join)(root, "GATE.md");
21066
21322
  const verityMd = (0, import_node_path25.join)(root, "VERITY.md");
21067
- if (!(0, import_node_fs33.existsSync)(gateMd) || (0, import_node_fs33.existsSync)(verityMd)) return;
21323
+ if (!(0, import_node_fs34.existsSync)(gateMd) || (0, import_node_fs34.existsSync)(verityMd)) return;
21068
21324
  let moved = false;
21069
21325
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
21070
21326
  try {
@@ -21076,12 +21332,12 @@ function migrateStandardFile(root, actions) {
21076
21332
  if (!moved) moveFile(gateMd, verityMd);
21077
21333
  const content = readFileSyncSafe(verityMd);
21078
21334
  const refreshed = content.split("GATE.md").join("VERITY.md");
21079
- if (refreshed !== content) (0, import_node_fs33.writeFileSync)(verityMd, refreshed);
21335
+ if (refreshed !== content) (0, import_node_fs34.writeFileSync)(verityMd, refreshed);
21080
21336
  actions.push("Renamed GATE.md \u2192 VERITY.md");
21081
21337
  }
21082
21338
  async function migrateTelemetryHeaders(root, actions) {
21083
21339
  const file = (0, import_node_path25.join)(root, ".claude", "settings.local.json");
21084
- if (!(0, import_node_fs33.existsSync)(file)) return;
21340
+ if (!(0, import_node_fs34.existsSync)(file)) return;
21085
21341
  let settings;
21086
21342
  try {
21087
21343
  settings = JSON.parse(readFileSyncSafe(file) || "{}");
@@ -21129,14 +21385,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
21129
21385
  }
21130
21386
  if (toAppend.length > 0) {
21131
21387
  const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
21132
- (0, import_node_fs33.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
21388
+ (0, import_node_fs34.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
21133
21389
  }
21134
- (0, import_node_fs33.rmSync)(gateCreds, { force: true });
21390
+ (0, import_node_fs34.rmSync)(gateCreds, { force: true });
21135
21391
  return toAppend.length;
21136
21392
  }
21137
21393
  function readFileSyncSafe(path) {
21138
21394
  try {
21139
- return (0, import_node_fs33.readFileSync)(path, "utf-8");
21395
+ return (0, import_node_fs34.readFileSync)(path, "utf-8");
21140
21396
  } catch {
21141
21397
  return "";
21142
21398
  }
@@ -21151,35 +21407,35 @@ function hasStagedChanges(root) {
21151
21407
  }
21152
21408
  function moveDir(from, to) {
21153
21409
  try {
21154
- (0, import_node_fs33.renameSync)(from, to);
21410
+ (0, import_node_fs34.renameSync)(from, to);
21155
21411
  } catch (err) {
21156
21412
  if (err.code !== "EXDEV") throw err;
21157
- (0, import_node_fs33.cpSync)(from, to, { recursive: true });
21158
- (0, import_node_fs33.rmSync)(from, { recursive: true, force: true });
21413
+ (0, import_node_fs34.cpSync)(from, to, { recursive: true });
21414
+ (0, import_node_fs34.rmSync)(from, { recursive: true, force: true });
21159
21415
  }
21160
21416
  }
21161
21417
  function moveFile(from, to) {
21162
21418
  try {
21163
- (0, import_node_fs33.renameSync)(from, to);
21419
+ (0, import_node_fs34.renameSync)(from, to);
21164
21420
  } catch (err) {
21165
21421
  if (err.code !== "EXDEV") throw err;
21166
- (0, import_node_fs33.cpSync)(from, to);
21167
- (0, import_node_fs33.rmSync)(from, { force: true });
21422
+ (0, import_node_fs34.cpSync)(from, to);
21423
+ (0, import_node_fs34.rmSync)(from, { force: true });
21168
21424
  }
21169
21425
  }
21170
21426
  function carryLegacyContents(gateDir, verityDir) {
21171
21427
  let copied = 0;
21172
21428
  const walk = (relDir) => {
21173
21429
  const srcDir = (0, import_node_path25.join)(gateDir, relDir);
21174
- for (const entry of (0, import_node_fs33.readdirSync)(srcDir)) {
21430
+ for (const entry of (0, import_node_fs34.readdirSync)(srcDir)) {
21175
21431
  const rel = relDir ? (0, import_node_path25.join)(relDir, entry) : entry;
21176
21432
  const src = (0, import_node_path25.join)(gateDir, rel);
21177
21433
  const dest = (0, import_node_path25.join)(verityDir, rel);
21178
- if ((0, import_node_fs33.statSync)(src).isDirectory()) {
21434
+ if ((0, import_node_fs34.statSync)(src).isDirectory()) {
21179
21435
  walk(rel);
21180
- } else if (!(0, import_node_fs33.existsSync)(dest)) {
21181
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
21182
- (0, import_node_fs33.cpSync)(src, dest);
21436
+ } else if (!(0, import_node_fs34.existsSync)(dest)) {
21437
+ (0, import_node_fs34.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
21438
+ (0, import_node_fs34.cpSync)(src, dest);
21183
21439
  copied++;
21184
21440
  }
21185
21441
  }
@@ -21190,20 +21446,20 @@ function carryLegacyContents(gateDir, verityDir) {
21190
21446
  async function needsMigration(root = repoRoot()) {
21191
21447
  const gateDir = (0, import_node_path25.join)(root, ".gate");
21192
21448
  const verityDir = (0, import_node_path25.join)(root, ".verity");
21193
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) return true;
21194
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21195
- if ((0, import_node_fs33.existsSync)((0, import_node_path25.join)(gateDir, "credentials")) && !(0, import_node_fs33.existsSync)((0, import_node_path25.join)(verityDir, "credentials"))) {
21449
+ if ((0, import_node_fs34.existsSync)(gateDir) && !(0, import_node_fs34.existsSync)(verityDir)) return true;
21450
+ if ((0, import_node_fs34.existsSync)(gateDir) && (0, import_node_fs34.existsSync)(verityDir)) {
21451
+ if ((0, import_node_fs34.existsSync)((0, import_node_path25.join)(gateDir, "credentials")) && !(0, import_node_fs34.existsSync)((0, import_node_path25.join)(verityDir, "credentials"))) {
21196
21452
  return true;
21197
21453
  }
21198
- if ((0, import_node_fs33.existsSync)((0, import_node_path25.join)(gateDir, "memory")) && !(0, import_node_fs33.existsSync)((0, import_node_path25.join)(verityDir, "memory"))) {
21454
+ if ((0, import_node_fs34.existsSync)((0, import_node_path25.join)(gateDir, "memory")) && !(0, import_node_fs34.existsSync)((0, import_node_path25.join)(verityDir, "memory"))) {
21199
21455
  return true;
21200
21456
  }
21201
21457
  }
21202
21458
  const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
21203
- if ((0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21459
+ if ((0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21204
21460
  return true;
21205
21461
  }
21206
- if ((0, import_node_fs33.existsSync)((0, import_node_path25.join)(root, "GATE.md")) && !(0, import_node_fs33.existsSync)((0, import_node_path25.join)(root, "VERITY.md"))) {
21462
+ if ((0, import_node_fs34.existsSync)((0, import_node_path25.join)(root, "GATE.md")) && !(0, import_node_fs34.existsSync)((0, import_node_path25.join)(root, "VERITY.md"))) {
21207
21463
  return true;
21208
21464
  }
21209
21465
  if (await hasLegacyHooksAt(root)) return true;
@@ -21345,7 +21601,7 @@ function resolveDataDir() {
21345
21601
  // local dev: running from repo root
21346
21602
  ];
21347
21603
  for (const candidate of candidates) {
21348
- if ((0, import_node_fs34.existsSync)((0, import_node_path26.join)(candidate, "skills"))) {
21604
+ if ((0, import_node_fs35.existsSync)((0, import_node_path26.join)(candidate, "skills"))) {
21349
21605
  return candidate;
21350
21606
  }
21351
21607
  }
@@ -21361,7 +21617,7 @@ function registerInitCommand(program2) {
21361
21617
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
21362
21618
  const force = opts.force ?? false;
21363
21619
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
21364
- const isProject = projectMarkers.some((m) => (0, import_node_fs34.existsSync)(m));
21620
+ const isProject = projectMarkers.some((m) => (0, import_node_fs35.existsSync)(m));
21365
21621
  if (!isProject) {
21366
21622
  printError("No project detected in the current directory.");
21367
21623
  printInfo('Run "verity init" from your project root.');
@@ -21431,14 +21687,14 @@ function registerInitCommand(program2) {
21431
21687
  for (const skill of skills) {
21432
21688
  const src = (0, import_node_path26.join)(skillsSource, skill);
21433
21689
  const dest = (0, import_node_path26.join)(skillsDest, skill);
21434
- if (!(0, import_node_fs34.existsSync)(src)) {
21690
+ if (!(0, import_node_fs35.existsSync)(src)) {
21435
21691
  printWarn(` Skill data not found: ${skill}`);
21436
21692
  continue;
21437
21693
  }
21438
- if ((0, import_node_fs34.existsSync)(dest) && !force) {
21694
+ if ((0, import_node_fs35.existsSync)(dest) && !force) {
21439
21695
  const srcSkill = (0, import_node_path26.join)(src, "SKILL.md");
21440
21696
  const destSkill = (0, import_node_path26.join)(dest, "SKILL.md");
21441
- if ((0, import_node_fs34.existsSync)(destSkill)) {
21697
+ if ((0, import_node_fs35.existsSync)(destSkill)) {
21442
21698
  try {
21443
21699
  const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
21444
21700
  const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
@@ -21469,6 +21725,12 @@ function registerInitCommand(program2) {
21469
21725
  }
21470
21726
  await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
21471
21727
  await ensureMemoryDir();
21728
+ const ignoreResult = ensureSnapshotGitignored();
21729
+ if (ignoreResult === "failed") {
21730
+ printWarn(" .gitignore: could not add .verity/.snapshot/ \u2014 add it manually (it holds copies of analyzed files)");
21731
+ } else {
21732
+ printInfo(` .gitignore: .verity/.snapshot/ ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
21733
+ }
21472
21734
  try {
21473
21735
  await ensureClaudeMdPointer();
21474
21736
  printInfo(" CLAUDE.md memory pointer \u2713");
@@ -21510,7 +21772,7 @@ function registerInitCommand(program2) {
21510
21772
  }
21511
21773
 
21512
21774
  // src/commands/uninstall.ts
21513
- var import_node_fs35 = require("node:fs");
21775
+ var import_node_fs36 = require("node:fs");
21514
21776
  var import_node_path27 = require("node:path");
21515
21777
  var SKILL_NAMES = [
21516
21778
  "verity-setup",
@@ -21531,10 +21793,10 @@ function registerUninstallCommand(program2) {
21531
21793
  const skillsRoot = projectPath(".claude/skills");
21532
21794
  for (const name of SKILL_NAMES) {
21533
21795
  const dir = (0, import_node_path27.join)(skillsRoot, name);
21534
- if ((0, import_node_fs35.existsSync)(dir)) {
21796
+ if ((0, import_node_fs36.existsSync)(dir)) {
21535
21797
  actions.push({
21536
21798
  label: `Remove .claude/skills/${name}/`,
21537
- apply: () => (0, import_node_fs35.rmSync)(dir, { recursive: true, force: true })
21799
+ apply: () => (0, import_node_fs36.rmSync)(dir, { recursive: true, force: true })
21538
21800
  });
21539
21801
  }
21540
21802
  }
@@ -21548,24 +21810,24 @@ function registerUninstallCommand(program2) {
21548
21810
  });
21549
21811
  }
21550
21812
  const verityDir = projectPath(VERITY_DIR);
21551
- if ((0, import_node_fs35.existsSync)(verityDir)) {
21813
+ if ((0, import_node_fs36.existsSync)(verityDir)) {
21552
21814
  actions.push({
21553
21815
  label: `Remove ${VERITY_DIR}/`,
21554
- apply: () => (0, import_node_fs35.rmSync)(verityDir, { recursive: true, force: true })
21816
+ apply: () => (0, import_node_fs36.rmSync)(verityDir, { recursive: true, force: true })
21555
21817
  });
21556
21818
  }
21557
21819
  if (!keepVerityMd) {
21558
21820
  const verityMd = projectPath(VERITY_MD_FILE);
21559
- if ((0, import_node_fs35.existsSync)(verityMd)) {
21821
+ if ((0, import_node_fs36.existsSync)(verityMd)) {
21560
21822
  actions.push({
21561
21823
  label: `Remove ${VERITY_MD_FILE}`,
21562
- apply: () => (0, import_node_fs35.rmSync)(verityMd, { force: true })
21824
+ apply: () => (0, import_node_fs36.rmSync)(verityMd, { force: true })
21563
21825
  });
21564
21826
  }
21565
21827
  }
21566
21828
  const cleanupEmptyDir = (path) => {
21567
- if ((0, import_node_fs35.existsSync)(path) && (0, import_node_fs35.statSync)(path).isDirectory() && (0, import_node_fs35.readdirSync)(path).length === 0) {
21568
- (0, import_node_fs35.rmdirSync)(path);
21829
+ if ((0, import_node_fs36.existsSync)(path) && (0, import_node_fs36.statSync)(path).isDirectory() && (0, import_node_fs36.readdirSync)(path).length === 0) {
21830
+ (0, import_node_fs36.rmdirSync)(path);
21569
21831
  }
21570
21832
  };
21571
21833
  actions.push({
@@ -21577,10 +21839,10 @@ function registerUninstallCommand(program2) {
21577
21839
  });
21578
21840
  const home = process.env.HOME ?? "";
21579
21841
  const globalVerityDir = (0, import_node_path27.join)(home, ".verity");
21580
- if (purgeGlobal && (0, import_node_fs35.existsSync)(globalVerityDir)) {
21842
+ if (purgeGlobal && (0, import_node_fs36.existsSync)(globalVerityDir)) {
21581
21843
  actions.push({
21582
21844
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
21583
- apply: () => (0, import_node_fs35.rmSync)(globalVerityDir, { recursive: true, force: true })
21845
+ apply: () => (0, import_node_fs36.rmSync)(globalVerityDir, { recursive: true, force: true })
21584
21846
  });
21585
21847
  }
21586
21848
  if (actions.length === 0) {
@@ -21774,7 +22036,7 @@ function registerTaskCommands(program2) {
21774
22036
  }
21775
22037
 
21776
22038
  // src/commands/reset.ts
21777
- var import_node_fs36 = require("node:fs");
22039
+ var import_node_fs37 = require("node:fs");
21778
22040
  var import_node_path28 = require("node:path");
21779
22041
  function registerResetCommand(program2) {
21780
22042
  program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.verity/.logs/)").action(async (opts) => {
@@ -21812,11 +22074,11 @@ function registerResetCommand(program2) {
21812
22074
  }
21813
22075
  const cacheDir = projectPath(CACHE_DIR);
21814
22076
  let purged = 0;
21815
- if ((0, import_node_fs36.existsSync)(cacheDir)) {
21816
- for (const entry of (0, import_node_fs36.readdirSync)(cacheDir)) {
22077
+ if ((0, import_node_fs37.existsSync)(cacheDir)) {
22078
+ for (const entry of (0, import_node_fs37.readdirSync)(cacheDir)) {
21817
22079
  if (entry.startsWith("pending-")) {
21818
22080
  try {
21819
- (0, import_node_fs36.unlinkSync)((0, import_node_path28.join)(cacheDir, entry));
22081
+ (0, import_node_fs37.unlinkSync)((0, import_node_path28.join)(cacheDir, entry));
21820
22082
  purged++;
21821
22083
  } catch {
21822
22084
  }
@@ -21831,19 +22093,19 @@ function registerResetCommand(program2) {
21831
22093
  projectPath(`${VERITY_DIR}/.last-analysis`)
21832
22094
  ];
21833
22095
  for (const file of filesToClear) {
21834
- if ((0, import_node_fs36.existsSync)(file)) {
22096
+ if ((0, import_node_fs37.existsSync)(file)) {
21835
22097
  try {
21836
- (0, import_node_fs36.writeFileSync)(file, "");
22098
+ (0, import_node_fs37.writeFileSync)(file, "");
21837
22099
  } catch {
21838
22100
  }
21839
22101
  }
21840
22102
  }
21841
22103
  if (opts.all) {
21842
22104
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
21843
- if ((0, import_node_fs36.existsSync)(logsDir)) {
21844
- for (const entry of (0, import_node_fs36.readdirSync)(logsDir)) {
22105
+ if ((0, import_node_fs37.existsSync)(logsDir)) {
22106
+ for (const entry of (0, import_node_fs37.readdirSync)(logsDir)) {
21845
22107
  try {
21846
- (0, import_node_fs36.unlinkSync)((0, import_node_path28.join)(logsDir, entry));
22108
+ (0, import_node_fs37.unlinkSync)((0, import_node_path28.join)(logsDir, entry));
21847
22109
  } catch {
21848
22110
  }
21849
22111
  }
@@ -22151,8 +22413,8 @@ function registerTelemetryCommands(program2) {
22151
22413
  }
22152
22414
 
22153
22415
  // src/cli.ts
22154
- program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.7126d67").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
22155
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.7126d67");
22416
+ program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.7475da6").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
22417
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.7475da6");
22156
22418
  setUserNamedServiceUrl(program.opts().serviceUrl);
22157
22419
  try {
22158
22420
  await foldLegacyLocalCredential();
@@ -711,18 +711,31 @@ to-be-pushed diff before it lands.
711
711
  Add these entries to `.gitignore` (create it if it doesn't exist, append if it does):
712
712
 
713
713
  ```
714
- # Verity
715
- .verity/.cache/
716
- .verity/.logs/
717
- .verity/.last-analysis
718
- .verity/.iteration-count
719
- .verity/.last-pass-hash
720
- .verity/.last-intent
721
- .verity/.memory-sync-state.json
714
+ # Verity — machine-local state. Everything in .verity/ is ignored EXCEPT the
715
+ # shared standard and the knowledge graph, which are meant to be committed.
716
+ .verity/*
717
+ !.verity/standard.yaml
718
+ !.verity/memory/
722
719
  .verity/memory/log.md
723
720
  .claude/settings.local.json
724
721
  ```
725
722
 
723
+ This is a whitelist on purpose: `.verity/` accumulates state files over time
724
+ (`.snapshot/` holds byte-for-byte copies of analyzed files — including any
725
+ secret the gate just flagged — plus `.cache/`, `.logs/`, `.baseline`,
726
+ `.task-context`, session-suffixed `.last-analysis.*` files, and whatever comes
727
+ next). Enumerating them one by one is how `.snapshot/` ended up committed in a
728
+ real repo; ignoring everything and re-including the two shared artifacts means
729
+ a future state file can never repeat that.
730
+
731
+ **If the repo previously committed Verity state** (check with
732
+ `git ls-files .verity`), untrack everything except the shared artifacts once:
733
+
734
+ ```bash
735
+ git rm -r --cached .verity
736
+ git add .verity/standard.yaml .verity/memory
737
+ ```
738
+
726
739
  `.claude/settings.local.json` is machine-local Claude Code config (telemetry endpoint + an
727
740
  `otelHeadersHelper` reference — no token). `verity telemetry install` adds this entry
728
741
  automatically.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.29.4-experimental.7126d67",
3
+ "version": "0.29.4-experimental.7475da6",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",