@codacy/verity-cli 0.29.4-experimental.e387745 → 0.29.4-experimental.e818a8a

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";
@@ -15060,8 +15061,10 @@ function recordVerdict(d, v) {
15060
15061
  if (summary) appendEvent(d, { k: "goal_delivered", summary });
15061
15062
  }
15062
15063
  const lines = /* @__PURE__ */ new Map();
15064
+ const sent = new Set(v.sentPaths);
15063
15065
  for (const f of v.findings) {
15064
15066
  if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
15067
+ if (!sent.has(f.file)) continue;
15065
15068
  if (!lines.has(f.file)) {
15066
15069
  try {
15067
15070
  const abs = (0, import_node_path12.join)(root, f.file);
@@ -15513,6 +15516,33 @@ ${addedLines}`,
15513
15516
  }
15514
15517
  return { diffs, has_snapshots: true };
15515
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
+ }
15516
15546
  function saveSnapshots(files) {
15517
15547
  const snapshotPaths = /* @__PURE__ */ new Set();
15518
15548
  for (const file of files) {
@@ -15557,7 +15587,7 @@ function cleanStaleSnapshots(dir, keepSet) {
15557
15587
  try {
15558
15588
  const entries = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true });
15559
15589
  for (const entry of entries) {
15560
- if (entry.name.startsWith(".")) continue;
15590
+ if (dir === SNAPSHOT_DIR && (entry.name === ".diff-old.tmp" || entry.name === ".diff-new.tmp")) continue;
15561
15591
  const fullPath = (0, import_node_path14.join)(dir, entry.name);
15562
15592
  if (entry.isDirectory()) {
15563
15593
  cleanStaleSnapshots(fullPath, keepSet);
@@ -16717,7 +16747,7 @@ function formatRunEvidence(run, startedAt) {
16717
16747
  const md = run.modeDecision;
16718
16748
  if (md) {
16719
16749
  const how = md.forced ? "forced by --mode" : `predicted=${md.predicted ?? "none"} \u2192 ${md.resolved}`;
16720
- out += row("mode", `${md.resolved} \xB7 ${how} \xB7 authored=${md.authored ? "yes" : "no"} \xB7 investigated=${md.investigated ? "yes" : "no"}`);
16750
+ out += row("mode", `${md.resolved} \xB7 ${how}` + (md.flip ? ` (flipped to plan: no delta at ${md.flip})` : "") + ` \xB7 authored=${md.authored ? "yes" : "no"} \xB7 investigated=${md.investigated ? "yes" : "no"}`);
16721
16751
  } else {
16722
16752
  out += row("mode", `? (this run stopped in ${run.phaseReached || "no phase"}, before the mode was decided)`);
16723
16753
  }
@@ -17146,6 +17176,39 @@ async function bootstrap(run) {
17146
17176
  Object.assign(run, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
17147
17177
  }
17148
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
+
17149
17212
  // src/lib/channel.ts
17150
17213
  var MAX_AGENT_CONTEXT_CHARS = 1500;
17151
17214
  var MAX_AGENT_ITEMS = 5;
@@ -17155,6 +17218,29 @@ function renderItem(label2, text, patternId, file, line) {
17155
17218
  const id = patternId ? ` [${patternId}]` : "";
17156
17219
  return `- ${label2}${text}${where}${id}`;
17157
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
+ }
17158
17244
  function buildAgentContext(input) {
17159
17245
  const lines = [];
17160
17246
  if (input.reviewStatus === "not_reviewed") {
@@ -17240,7 +17326,7 @@ function channelSilence(input) {
17240
17326
  // src/lib/cli-version.ts
17241
17327
  function cliVersion() {
17242
17328
  try {
17243
- return true ? "0.29.4-experimental.e387745" : "dev";
17329
+ return true ? "0.29.4-experimental.e818a8a" : "dev";
17244
17330
  } catch {
17245
17331
  return "dev";
17246
17332
  }
@@ -17562,9 +17648,10 @@ async function scope(run) {
17562
17648
  const { assistantResponse } = run;
17563
17649
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
17564
17650
  run.changedUniverse = allChanged;
17565
- const analyzable = filterAnalyzable(allChanged);
17566
- const reviewable = filterReviewable(allChanged);
17567
- 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);
17568
17655
  const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
17569
17656
  if (noFilesChanged && !assistantResponse) {
17570
17657
  await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
@@ -18057,26 +18144,50 @@ function narrowToRecent(files, sessionId) {
18057
18144
  });
18058
18145
  return recent.length > 0 ? recent : files;
18059
18146
  }
18060
- function readIterationState(currentCommit) {
18061
- 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;
18062
18154
  try {
18063
18155
  const stored = (0, import_node_fs22.readFileSync)(ITERATION_FILE, "utf-8").trim();
18064
- const parts = stored.split(":");
18065
- const iter = parseInt(parts[0], 10);
18066
- const storedCommit = parts[1] ?? "";
18067
- const storedTimestamp = parseInt(parts[2] ?? "0", 10);
18068
- const fingerprint = parts.slice(3).join(":") || null;
18069
- if (isNaN(iter)) return { iteration: 1, fingerprint: null };
18070
- if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
18071
- if (storedTimestamp > 0) {
18072
- const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
18073
- if (elapsed > 600) return { iteration: 1, fingerprint: null };
18074
- }
18075
- 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 };
18076
18161
  } catch {
18077
- return { iteration: 1, fingerprint: null };
18162
+ return NO_BLOCKS;
18078
18163
  }
18079
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
+ }
18080
18191
  function findingsFingerprint(findings) {
18081
18192
  const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
18082
18193
  return [...new Set(keys)].sort().join(",");
@@ -18086,11 +18197,22 @@ function isSameProblem(previous, current) {
18086
18197
  const prev = new Set(previous.split(","));
18087
18198
  return current.split(",").some((k) => prev.has(k));
18088
18199
  }
18089
- function writeIteration(iteration, commit, _contentHash, fingerprint) {
18200
+ function writeBlockState(commit, state) {
18090
18201
  (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
18091
- const ts = Math.floor(Date.now() / 1e3);
18092
- const fp = fingerprint ? `:${fingerprint}` : "";
18093
- (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 });
18094
18216
  }
18095
18217
 
18096
18218
  // src/lib/fold.ts
@@ -18282,8 +18404,10 @@ function fold(transcriptPath, opts = {}) {
18282
18404
  subagentSkipped: 0,
18283
18405
  compactions: 0,
18284
18406
  complete: false
18285
- }
18407
+ },
18408
+ planApproval: { approvals: 0, activeSinceLastPrompt: false }
18286
18409
  };
18410
+ const flow = { seq: 0, lastPrompt: -1, lastApproval: -1, approvals: 0 };
18287
18411
  const byPath = /* @__PURE__ */ new Map();
18288
18412
  const commandStats = /* @__PURE__ */ new Map();
18289
18413
  const pendingByToolUse = /* @__PURE__ */ new Map();
@@ -18309,8 +18433,12 @@ function fold(transcriptPath, opts = {}) {
18309
18433
  if (type === "system" && record.subtype === "compact_boundary") {
18310
18434
  result.coverage.compactions++;
18311
18435
  }
18312
- if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
18313
- collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, opts.repoRoot, result.coverage);
18436
+ if (type === "user" && hasUserText(record)) {
18437
+ result.coverage.userMessages++;
18438
+ if (owner === "agent") flow.lastPrompt = flow.seq;
18439
+ }
18440
+ if (owner === "agent") flow.seq++;
18441
+ collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, opts.repoRoot, result.coverage, flow);
18314
18442
  }
18315
18443
  };
18316
18444
  try {
@@ -18382,6 +18510,10 @@ function fold(transcriptPath, opts = {}) {
18382
18510
  if (!p || authoredPaths.has(p)) continue;
18383
18511
  result.unobserved.push({ p, cause: classifyUnobserved(raw) });
18384
18512
  }
18513
+ result.planApproval = {
18514
+ approvals: flow.approvals,
18515
+ activeSinceLastPrompt: flow.lastApproval >= 0 && flow.lastApproval > flow.lastPrompt
18516
+ };
18385
18517
  return result;
18386
18518
  }
18387
18519
  function classifyUnobserved(path) {
@@ -18394,7 +18526,7 @@ function classifyUnobserved(path) {
18394
18526
  if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
18395
18527
  return "no_edit_record";
18396
18528
  }
18397
- function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally) {
18529
+ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally, flow) {
18398
18530
  const message = record.message;
18399
18531
  const content = message?.content ?? record.content;
18400
18532
  const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
@@ -18478,6 +18610,13 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18478
18610
  }
18479
18611
  }
18480
18612
  const toolName = id ? pendingToolName.get(id) : void 0;
18613
+ if (toolName === "ExitPlanMode" && flow && block.is_error !== true) {
18614
+ const body = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => typeof c.text === "string" ? c.text : "").join(" ") : "";
18615
+ if (/approved your plan/i.test(body)) {
18616
+ flow.lastApproval = flow.seq;
18617
+ flow.approvals += 1;
18618
+ }
18619
+ }
18481
18620
  if (toolName) {
18482
18621
  pendingToolName.delete(id);
18483
18622
  const prevTool = toolStats.get(toolName);
@@ -18535,8 +18674,13 @@ function checkConservation(changedFiles, result, repoRoot2) {
18535
18674
  // src/commands/analyze/phases/06-evidence.ts
18536
18675
  async function evidence(run) {
18537
18676
  const { opts } = run;
18538
- const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath } = run;
18677
+ const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath, turnAuthoredCode } = run;
18539
18678
  let { analysisMode, earlyFold } = run;
18679
+ const recordFlip = (stage) => {
18680
+ if (run.modeDecision) run.modeDecision = { ...run.modeDecision, resolved: "plan", flip: stage };
18681
+ logEvent("mode_flipped", { stage, to: "plan" });
18682
+ };
18683
+ const planWorthy = !!assistantResponse && !turnAuthoredCode;
18540
18684
  let staticResults = {
18541
18685
  tool: "@codacy/analysis-cli",
18542
18686
  findings: [],
@@ -18556,8 +18700,9 @@ async function evidence(run) {
18556
18700
  const debounceSeconds = parseInt(opts.debounce, 10);
18557
18701
  const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
18558
18702
  if (debounceSkip) {
18559
- if (assistantResponse) {
18703
+ if (planWorthy) {
18560
18704
  analysisMode = "plan";
18705
+ recordFlip("debounce");
18561
18706
  } else {
18562
18707
  await passAndExit(run, debounceSkip, "debounce");
18563
18708
  }
@@ -18566,8 +18711,9 @@ async function evidence(run) {
18566
18711
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18567
18712
  const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
18568
18713
  if (mtimeSkip) {
18569
- if (assistantResponse) {
18714
+ if (planWorthy) {
18570
18715
  analysisMode = "plan";
18716
+ recordFlip("mtime");
18571
18717
  } else {
18572
18718
  await passAndExit(run, mtimeSkip, "no-delta-since-last-review");
18573
18719
  }
@@ -18578,8 +18724,9 @@ async function evidence(run) {
18578
18724
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18579
18725
  const hashResult = checkContentHash(allCheckable, baselineSessionId);
18580
18726
  if (hashResult.skip) {
18581
- if (assistantResponse) {
18727
+ if (planWorthy) {
18582
18728
  analysisMode = "plan";
18729
+ recordFlip("content-hash");
18583
18730
  } else {
18584
18731
  await passAndExit(run, hashResult.skip, "no-delta-since-last-review");
18585
18732
  }
@@ -18641,8 +18788,9 @@ async function evidence(run) {
18641
18788
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
18642
18789
  });
18643
18790
  if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
18644
- if (assistantResponse) {
18791
+ if (planWorthy) {
18645
18792
  analysisMode = "plan";
18793
+ recordFlip("empty-after-scoping");
18646
18794
  } else {
18647
18795
  await passAndExit(
18648
18796
  run,
@@ -18662,13 +18810,13 @@ async function evidence(run) {
18662
18810
  snapshotResult = generateSnapshotDiffs(codeDelta.files);
18663
18811
  }
18664
18812
  currentCommit = getCurrentCommit();
18665
- iteration = readIterationState(currentCommit).iteration;
18813
+ iteration = readIteration(currentCommit);
18666
18814
  }
18667
18815
  }
18668
18816
  if (analysisMode === "plan") {
18669
18817
  recordAnalysisStart();
18670
18818
  currentCommit = getCurrentCommit();
18671
- iteration = readIterationState(currentCommit).iteration;
18819
+ iteration = readIteration(currentCommit);
18672
18820
  }
18673
18821
  Object.assign(run, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
18674
18822
  }
@@ -18764,7 +18912,8 @@ function gatherContextFiles(contextPaths, deltaFiles) {
18764
18912
  // src/commands/analyze/phases/07-context-files.ts
18765
18913
  async function contextFiles(run) {
18766
18914
  const { codeDelta, contextFilePaths } = run;
18767
- const contextFiles2 = gatherContextFiles(contextFilePaths, codeDelta.files);
18915
+ const { kept: externalContext } = partitionVerityOwned(contextFilePaths ?? []);
18916
+ const contextFiles2 = gatherContextFiles(externalContext, codeDelta.files);
18768
18917
  for (const f of codeDelta.files) {
18769
18918
  f.role = "delta";
18770
18919
  }
@@ -19334,6 +19483,54 @@ async function workingMemory(run) {
19334
19483
  Object.assign(run, { incrementReport, memory, memorySession, reachability });
19335
19484
  }
19336
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
+
19337
19534
  // src/lib/run-mode.ts
19338
19535
  function parseAutonomousEnv(raw) {
19339
19536
  if (raw === void 0) return void 0;
@@ -19427,7 +19624,13 @@ async function buildRequest(run) {
19427
19624
  excluded_by_reason: excludedByReason,
19428
19625
  // was the transcript itself truncated? The 256 KB window means "this turn"
19429
19626
  // can quietly mean "the last 256 KB of it".
19430
- 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
19431
19634
  };
19432
19635
  const requestBody = {
19433
19636
  coverage_telemetry: coverageTelemetry,
@@ -19566,7 +19769,8 @@ async function buildRequest(run) {
19566
19769
  }
19567
19770
  const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
19568
19771
  const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
19569
- 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;
19570
19774
  if (hasIntent) {
19571
19775
  const intentContext = {};
19572
19776
  if (conversation && conversation.prompts.length > 0) {
@@ -19598,6 +19802,10 @@ async function buildRequest(run) {
19598
19802
  intentContext.user_prompt = w4Task.goal;
19599
19803
  logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
19600
19804
  }
19805
+ if (planApprovalActive) {
19806
+ intentContext.plan_approved = true;
19807
+ logEvent("plan_approval_carried", { approvals: foldResult.planApproval.approvals });
19808
+ }
19601
19809
  if (assistantResponse) {
19602
19810
  const cap = analysisMode === "plan" ? MAX_ASSISTANT_RESPONSE_CHARS_PLAN : MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT;
19603
19811
  intentContext.assistant_response = assistantResponse.length > cap ? assistantResponse.slice(0, cap) : assistantResponse;
@@ -19617,14 +19825,14 @@ async function buildRequest(run) {
19617
19825
  }
19618
19826
 
19619
19827
  // src/lib/offline.ts
19620
- var import_node_fs28 = require("node:fs");
19828
+ var import_node_fs29 = require("node:fs");
19621
19829
  var import_node_crypto11 = require("node:crypto");
19622
19830
  function cacheRequest(body) {
19623
19831
  try {
19624
- (0, import_node_fs28.mkdirSync)(CACHE_DIR, { recursive: true });
19832
+ (0, import_node_fs29.mkdirSync)(CACHE_DIR, { recursive: true });
19625
19833
  const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
19626
19834
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
19627
- (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)));
19628
19836
  } catch {
19629
19837
  }
19630
19838
  }
@@ -19743,10 +19951,10 @@ async function transmit(run) {
19743
19951
  }
19744
19952
 
19745
19953
  // src/commands/analyze/phases/13-reconcile.ts
19746
- var import_node_fs29 = require("node:fs");
19954
+ var import_node_fs30 = require("node:fs");
19747
19955
  var import_node_path23 = require("node:path");
19748
19956
  async function reconcile(run) {
19749
- 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;
19750
19958
  const sentPaths = codeDelta.files.map((f) => f.path);
19751
19959
  let openElsewhere = [];
19752
19960
  if (memorySession) {
@@ -19754,7 +19962,7 @@ async function reconcile(run) {
19754
19962
  const st = foldDossier(memorySession.d);
19755
19963
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
19756
19964
  try {
19757
- 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");
19758
19966
  const at = src[line - 1];
19759
19967
  return at === void 0 ? null : lineSha(at);
19760
19968
  } catch {
@@ -19764,6 +19972,7 @@ async function reconcile(run) {
19764
19972
  } catch {
19765
19973
  }
19766
19974
  }
19975
+ const { kept: externalChanged, owned: verityOwned } = partitionVerityOwned(allChanged);
19767
19976
  const reviewCoverage = {
19768
19977
  reviewed: sentPaths,
19769
19978
  // Declared drops from the stages that DO report themselves today. The other
@@ -19805,11 +20014,20 @@ async function reconcile(run) {
19805
20014
  stage: "baseline-scoping",
19806
20015
  kind: "policy"
19807
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
+ })),
19808
20026
  // The extension allowlist. POLICY: a changed README was never going to be
19809
20027
  // reviewed, and calling that a coverage gap would downgrade nearly every
19810
20028
  // PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
19811
20029
  // so "what did Verity ignore entirely" is answerable.
19812
- ...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) => ({
19813
20031
  path,
19814
20032
  reason: "not-a-reviewed-file-type",
19815
20033
  stage: "extension-allowlist",
@@ -19846,6 +20064,29 @@ async function reconcile(run) {
19846
20064
  decision
19847
20065
  });
19848
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
+ );
19849
20090
  let intentRepeatCount = 0;
19850
20091
  const priorPendingFingerprints = memorySession ? (() => {
19851
20092
  try {
@@ -19861,6 +20102,11 @@ async function reconcile(run) {
19861
20102
  decision,
19862
20103
  branch: getCurrentBranch(),
19863
20104
  watermarkSha: watermarkIsPartial ? null : watermarkHash,
20105
+ // The byte witness — the same "only honest definition of reviewed" the
20106
+ // coverage column uses. A finding on a path outside this set records no
20107
+ // statement (plan-mode prose anchored to unsent files must not become
20108
+ // "STILL OPEN … the tree is not clean").
20109
+ sentPaths,
19864
20110
  findings: response.findings?.map((f) => ({
19865
20111
  file: f.file,
19866
20112
  line: f.line,
@@ -19927,6 +20173,39 @@ ${YELLOW2}${note}${NC2}
19927
20173
  return exit(0);
19928
20174
  }
19929
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
+
19930
20209
  // src/lib/remediation-guard.ts
19931
20210
  var TOOL_CONFIG_PATTERNS = [
19932
20211
  /(^|\/)\.codacy\//,
@@ -19969,19 +20248,7 @@ function screenRemediation(fix, findingFile) {
19969
20248
 
19970
20249
  // src/commands/analyze/phases/14-render.ts
19971
20250
  function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
19972
- const metadata = response.metadata ?? {};
19973
- const intent = response.intent_alignment ?? {};
19974
- return buildAgentContext({
19975
- intentRepeat,
19976
- priorPendingFingerprints,
19977
- gateDecision: String(response.gate_decision ?? ""),
19978
- findings: response.findings ?? [],
19979
- pendingItems: response.pending_items ?? [],
19980
- reviewStatus: metadata.review_status,
19981
- coverage: metadata.coverage,
19982
- intentVerdict: intent.verdict,
19983
- intentGaps: intent.gaps
19984
- });
20251
+ return buildAgentContext(channelInputFrom(response, intentRepeat, priorPendingFingerprints));
19985
20252
  }
19986
20253
  async function render(run) {
19987
20254
  const { opts, globals } = run;
@@ -20075,38 +20342,63 @@ async function render(run) {
20075
20342
  reverify_by: response.reverify_by
20076
20343
  });
20077
20344
  const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
20078
- let capReleased = false;
20345
+ let release = null;
20079
20346
  let effectiveDecision = decision;
20080
20347
  if (decision === "FAIL") {
20081
- const blocking = (response.findings ?? []).filter((f) => {
20348
+ const findings = response.findings ?? [];
20349
+ const blocking = findings.filter((f) => {
20082
20350
  const sev = String(f.severity ?? "").toLowerCase();
20083
20351
  return sev === "critical" || sev === "high";
20084
20352
  });
20085
20353
  const fingerprint = findingsFingerprint(blocking);
20086
- const prior = readIterationState(currentCommit);
20354
+ const prior = readBlockState(currentCommit, {
20355
+ newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
20356
+ });
20087
20357
  const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
20088
- const nextIteration = sameProblem ? prior.iteration + 1 : 1;
20089
20358
  const maxIterations = parseInt(opts.maxIterations, 10);
20090
- writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
20091
- iteration = nextIteration;
20092
- if (nextIteration > maxIterations) {
20093
- 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;
20094
20374
  effectiveDecision = "WARN";
20095
- 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
+ });
20096
20383
  }
20097
20384
  }
20098
- if (capReleased) {
20385
+ if (release) {
20099
20386
  const findings = response.findings ?? [];
20100
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
+ });
20101
20392
  emitVerdict({
20102
20393
  proposed: "WARN",
20103
20394
  changed: run.changedUniverse,
20104
20395
  coverage: reviewCoverage,
20105
- 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.
20106
- ${lines.join("\n")}`,
20396
+ userSummary: lines.length > 0 ? `${summary}
20397
+ ${lines.join("\n")}` : summary,
20107
20398
  agentContext: null,
20108
20399
  silenced: true
20109
20400
  });
20401
+ return;
20110
20402
  }
20111
20403
  switch (effectiveDecision) {
20112
20404
  case "FAIL": {
@@ -20205,7 +20497,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20205
20497
  break;
20206
20498
  }
20207
20499
  case "PASS": {
20208
- writeIteration(1, currentCommit, contentHash ?? void 0);
20500
+ resetBlockState(currentCommit);
20209
20501
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20210
20502
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20211
20503
  let userSummary = response.user_summary ?? "Verity: PASS";
@@ -20226,6 +20518,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20226
20518
  break;
20227
20519
  }
20228
20520
  case "WARN": {
20521
+ if (decision !== "FAIL") resetBlockState(currentCommit);
20229
20522
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20230
20523
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20231
20524
  let userSummary = response.user_summary ?? "Verity: WARN";
@@ -20324,7 +20617,7 @@ async function runAnalyze(opts, globals) {
20324
20617
  }
20325
20618
 
20326
20619
  // src/commands/baseline.ts
20327
- var import_node_fs30 = require("node:fs");
20620
+ var import_node_fs31 = require("node:fs");
20328
20621
  function registerBaselineCommands(program2) {
20329
20622
  const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
20330
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) => {
@@ -20333,7 +20626,7 @@ function registerBaselineCommands(program2) {
20333
20626
  process.chdir(repoRoot());
20334
20627
  } catch {
20335
20628
  }
20336
- if (!(0, import_node_fs30.existsSync)(VERITY_DIR)) {
20629
+ if (!(0, import_node_fs31.existsSync)(VERITY_DIR)) {
20337
20630
  process.exit(0);
20338
20631
  }
20339
20632
  let sessionId = opts.sessionId;
@@ -20373,7 +20666,7 @@ async function readStdin() {
20373
20666
  }
20374
20667
 
20375
20668
  // src/commands/review.ts
20376
- var import_node_fs31 = require("node:fs");
20669
+ var import_node_fs32 = require("node:fs");
20377
20670
  function registerReviewCommand(program2) {
20378
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) => {
20379
20672
  const globals = program2.opts();
@@ -20392,7 +20685,7 @@ async function runReview(opts, globals) {
20392
20685
  const securityFiles = filterSecurity(allFiles);
20393
20686
  let staticResults;
20394
20687
  if (isCodacyAvailable()) {
20395
- 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);
20396
20689
  staticResults = runCodacyAnalysis(scannable);
20397
20690
  } else {
20398
20691
  staticResults = {
@@ -20418,10 +20711,10 @@ async function runReview(opts, globals) {
20418
20711
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
20419
20712
  specs = [];
20420
20713
  for (const p of specPaths) {
20421
- if (!(0, import_node_fs31.existsSync)(p)) continue;
20714
+ if (!(0, import_node_fs32.existsSync)(p)) continue;
20422
20715
  try {
20423
- const { readFileSync: readFileSync18 } = await import("node:fs");
20424
- const content = readFileSync18(p, "utf-8");
20716
+ const { readFileSync: readFileSync19 } = await import("node:fs");
20717
+ const content = readFileSync19(p, "utf-8");
20425
20718
  specs.push({ path: p, content: content.slice(0, 10240) });
20426
20719
  } catch {
20427
20720
  }
@@ -20478,7 +20771,7 @@ async function runReview(opts, globals) {
20478
20771
  }
20479
20772
 
20480
20773
  // src/commands/guard.ts
20481
- var import_node_fs32 = require("node:fs");
20774
+ var import_node_fs33 = require("node:fs");
20482
20775
  var import_node_path24 = require("node:path");
20483
20776
  var GUARD_BLOCK_CAP = 2;
20484
20777
  var GUARD_ITER_FILE = (0, import_node_path24.join)(VERITY_DIR, ".guard-iteration");
@@ -20545,7 +20838,7 @@ function classifyCommand2(command, on) {
20545
20838
  }
20546
20839
  function readIterMap() {
20547
20840
  try {
20548
- 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"));
20549
20842
  if (raw && typeof raw === "object") {
20550
20843
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
20551
20844
  return { [raw.moment]: raw.count };
@@ -20565,10 +20858,10 @@ function readIter(moment) {
20565
20858
  }
20566
20859
  function writeIter(moment, count) {
20567
20860
  try {
20568
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20861
+ (0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
20569
20862
  const map = readIterMap();
20570
20863
  map[moment] = count;
20571
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20864
+ (0, import_node_fs33.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20572
20865
  } catch {
20573
20866
  }
20574
20867
  }
@@ -20578,10 +20871,10 @@ function resetIter(moment) {
20578
20871
  if (!(moment in map)) return;
20579
20872
  delete map[moment];
20580
20873
  if (Object.keys(map).length === 0) {
20581
- 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);
20582
20875
  } else {
20583
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20584
- (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));
20585
20878
  }
20586
20879
  } catch {
20587
20880
  }
@@ -20645,7 +20938,7 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
20645
20938
  const securityFiles = filterSecurity(files);
20646
20939
  let staticResults;
20647
20940
  if (isCodacyAvailable()) {
20648
- 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);
20649
20942
  staticResults = runCodacyAnalysis(scannable);
20650
20943
  } else {
20651
20944
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -20690,7 +20983,7 @@ function emitAllowNotice(userMsg, agentMsg) {
20690
20983
  async function runGuard(opts, globals) {
20691
20984
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
20692
20985
  const { command, cwd, sessionId } = await readPreToolUseStdin();
20693
- if (cwd && (0, import_node_fs32.existsSync)(cwd)) {
20986
+ if (cwd && (0, import_node_fs33.existsSync)(cwd)) {
20694
20987
  try {
20695
20988
  process.chdir(cwd);
20696
20989
  } catch {
@@ -20796,14 +21089,14 @@ function writeBlockMessage(moment, response) {
20796
21089
  }
20797
21090
 
20798
21091
  // src/commands/init.ts
20799
- var import_node_fs34 = require("node:fs");
21092
+ var import_node_fs35 = require("node:fs");
20800
21093
  var import_promises13 = require("node:fs/promises");
20801
21094
  var import_node_path26 = require("node:path");
20802
21095
  var import_node_child_process10 = require("node:child_process");
20803
21096
  var readline2 = __toESM(require("node:readline/promises"));
20804
21097
 
20805
21098
  // src/commands/migrate.ts
20806
- var import_node_fs33 = require("node:fs");
21099
+ var import_node_fs34 = require("node:fs");
20807
21100
  var import_node_path25 = require("node:path");
20808
21101
  var import_node_child_process9 = require("node:child_process");
20809
21102
 
@@ -20935,10 +21228,10 @@ async function runMigration(opts = {}) {
20935
21228
  function migrateProjectDir(root, actions) {
20936
21229
  const gateDir = (0, import_node_path25.join)(root, ".gate");
20937
21230
  const verityDir = (0, import_node_path25.join)(root, ".verity");
20938
- 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)) {
20939
21232
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
20940
21233
  }
20941
- 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)) {
20942
21235
  return migrateProjectDirCarry(gateDir, verityDir, actions);
20943
21236
  }
20944
21237
  return false;
@@ -20959,13 +21252,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
20959
21252
  }
20960
21253
  }
20961
21254
  if (moved) {
20962
- if ((0, import_node_fs33.existsSync)(gateDir)) {
21255
+ if ((0, import_node_fs34.existsSync)(gateDir)) {
20963
21256
  const carried = carryLegacyContents(gateDir, verityDir);
20964
21257
  if (carried > 0) {
20965
21258
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
20966
21259
  }
20967
21260
  try {
20968
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21261
+ (0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
20969
21262
  } catch {
20970
21263
  }
20971
21264
  }
@@ -20981,7 +21274,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
20981
21274
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
20982
21275
  }
20983
21276
  try {
20984
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21277
+ (0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
20985
21278
  } catch {
20986
21279
  }
20987
21280
  return carried > 0;
@@ -20990,9 +21283,9 @@ function migrateGlobalCredentials(home, actions) {
20990
21283
  if (!home) return;
20991
21284
  const gateCreds = (0, import_node_path25.join)(home, ".gate", "credentials");
20992
21285
  const verityCreds = (0, import_node_path25.join)(home, ".verity", "credentials");
20993
- if (!(0, import_node_fs33.existsSync)(gateCreds)) return;
20994
- if (!(0, import_node_fs33.existsSync)(verityCreds)) {
20995
- (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 });
20996
21289
  moveFile(gateCreds, verityCreds);
20997
21290
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
20998
21291
  return;
@@ -21015,7 +21308,7 @@ async function migrateLegacyHooks(root, actions) {
21015
21308
  }
21016
21309
  async function migrateClaudeMd(root, actions) {
21017
21310
  const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
21018
- const hadLegacyBlock = (0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21311
+ const hadLegacyBlock = (0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21019
21312
  if (!hadLegacyBlock) return;
21020
21313
  try {
21021
21314
  await ensureClaudeMdPointer(root);
@@ -21027,7 +21320,7 @@ async function migrateClaudeMd(root, actions) {
21027
21320
  function migrateStandardFile(root, actions) {
21028
21321
  const gateMd = (0, import_node_path25.join)(root, "GATE.md");
21029
21322
  const verityMd = (0, import_node_path25.join)(root, "VERITY.md");
21030
- 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;
21031
21324
  let moved = false;
21032
21325
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
21033
21326
  try {
@@ -21039,12 +21332,12 @@ function migrateStandardFile(root, actions) {
21039
21332
  if (!moved) moveFile(gateMd, verityMd);
21040
21333
  const content = readFileSyncSafe(verityMd);
21041
21334
  const refreshed = content.split("GATE.md").join("VERITY.md");
21042
- if (refreshed !== content) (0, import_node_fs33.writeFileSync)(verityMd, refreshed);
21335
+ if (refreshed !== content) (0, import_node_fs34.writeFileSync)(verityMd, refreshed);
21043
21336
  actions.push("Renamed GATE.md \u2192 VERITY.md");
21044
21337
  }
21045
21338
  async function migrateTelemetryHeaders(root, actions) {
21046
21339
  const file = (0, import_node_path25.join)(root, ".claude", "settings.local.json");
21047
- if (!(0, import_node_fs33.existsSync)(file)) return;
21340
+ if (!(0, import_node_fs34.existsSync)(file)) return;
21048
21341
  let settings;
21049
21342
  try {
21050
21343
  settings = JSON.parse(readFileSyncSafe(file) || "{}");
@@ -21092,14 +21385,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
21092
21385
  }
21093
21386
  if (toAppend.length > 0) {
21094
21387
  const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
21095
- (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");
21096
21389
  }
21097
- (0, import_node_fs33.rmSync)(gateCreds, { force: true });
21390
+ (0, import_node_fs34.rmSync)(gateCreds, { force: true });
21098
21391
  return toAppend.length;
21099
21392
  }
21100
21393
  function readFileSyncSafe(path) {
21101
21394
  try {
21102
- return (0, import_node_fs33.readFileSync)(path, "utf-8");
21395
+ return (0, import_node_fs34.readFileSync)(path, "utf-8");
21103
21396
  } catch {
21104
21397
  return "";
21105
21398
  }
@@ -21114,35 +21407,35 @@ function hasStagedChanges(root) {
21114
21407
  }
21115
21408
  function moveDir(from, to) {
21116
21409
  try {
21117
- (0, import_node_fs33.renameSync)(from, to);
21410
+ (0, import_node_fs34.renameSync)(from, to);
21118
21411
  } catch (err) {
21119
21412
  if (err.code !== "EXDEV") throw err;
21120
- (0, import_node_fs33.cpSync)(from, to, { recursive: true });
21121
- (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 });
21122
21415
  }
21123
21416
  }
21124
21417
  function moveFile(from, to) {
21125
21418
  try {
21126
- (0, import_node_fs33.renameSync)(from, to);
21419
+ (0, import_node_fs34.renameSync)(from, to);
21127
21420
  } catch (err) {
21128
21421
  if (err.code !== "EXDEV") throw err;
21129
- (0, import_node_fs33.cpSync)(from, to);
21130
- (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 });
21131
21424
  }
21132
21425
  }
21133
21426
  function carryLegacyContents(gateDir, verityDir) {
21134
21427
  let copied = 0;
21135
21428
  const walk = (relDir) => {
21136
21429
  const srcDir = (0, import_node_path25.join)(gateDir, relDir);
21137
- for (const entry of (0, import_node_fs33.readdirSync)(srcDir)) {
21430
+ for (const entry of (0, import_node_fs34.readdirSync)(srcDir)) {
21138
21431
  const rel = relDir ? (0, import_node_path25.join)(relDir, entry) : entry;
21139
21432
  const src = (0, import_node_path25.join)(gateDir, rel);
21140
21433
  const dest = (0, import_node_path25.join)(verityDir, rel);
21141
- if ((0, import_node_fs33.statSync)(src).isDirectory()) {
21434
+ if ((0, import_node_fs34.statSync)(src).isDirectory()) {
21142
21435
  walk(rel);
21143
- } else if (!(0, import_node_fs33.existsSync)(dest)) {
21144
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
21145
- (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);
21146
21439
  copied++;
21147
21440
  }
21148
21441
  }
@@ -21153,20 +21446,20 @@ function carryLegacyContents(gateDir, verityDir) {
21153
21446
  async function needsMigration(root = repoRoot()) {
21154
21447
  const gateDir = (0, import_node_path25.join)(root, ".gate");
21155
21448
  const verityDir = (0, import_node_path25.join)(root, ".verity");
21156
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) return true;
21157
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21158
- 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"))) {
21159
21452
  return true;
21160
21453
  }
21161
- 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"))) {
21162
21455
  return true;
21163
21456
  }
21164
21457
  }
21165
21458
  const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
21166
- if ((0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21459
+ if ((0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21167
21460
  return true;
21168
21461
  }
21169
- 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"))) {
21170
21463
  return true;
21171
21464
  }
21172
21465
  if (await hasLegacyHooksAt(root)) return true;
@@ -21308,7 +21601,7 @@ function resolveDataDir() {
21308
21601
  // local dev: running from repo root
21309
21602
  ];
21310
21603
  for (const candidate of candidates) {
21311
- 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"))) {
21312
21605
  return candidate;
21313
21606
  }
21314
21607
  }
@@ -21324,7 +21617,7 @@ function registerInitCommand(program2) {
21324
21617
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
21325
21618
  const force = opts.force ?? false;
21326
21619
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
21327
- const isProject = projectMarkers.some((m) => (0, import_node_fs34.existsSync)(m));
21620
+ const isProject = projectMarkers.some((m) => (0, import_node_fs35.existsSync)(m));
21328
21621
  if (!isProject) {
21329
21622
  printError("No project detected in the current directory.");
21330
21623
  printInfo('Run "verity init" from your project root.');
@@ -21394,14 +21687,14 @@ function registerInitCommand(program2) {
21394
21687
  for (const skill of skills) {
21395
21688
  const src = (0, import_node_path26.join)(skillsSource, skill);
21396
21689
  const dest = (0, import_node_path26.join)(skillsDest, skill);
21397
- if (!(0, import_node_fs34.existsSync)(src)) {
21690
+ if (!(0, import_node_fs35.existsSync)(src)) {
21398
21691
  printWarn(` Skill data not found: ${skill}`);
21399
21692
  continue;
21400
21693
  }
21401
- if ((0, import_node_fs34.existsSync)(dest) && !force) {
21694
+ if ((0, import_node_fs35.existsSync)(dest) && !force) {
21402
21695
  const srcSkill = (0, import_node_path26.join)(src, "SKILL.md");
21403
21696
  const destSkill = (0, import_node_path26.join)(dest, "SKILL.md");
21404
- if ((0, import_node_fs34.existsSync)(destSkill)) {
21697
+ if ((0, import_node_fs35.existsSync)(destSkill)) {
21405
21698
  try {
21406
21699
  const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
21407
21700
  const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
@@ -21432,6 +21725,12 @@ function registerInitCommand(program2) {
21432
21725
  }
21433
21726
  await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
21434
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
+ }
21435
21734
  try {
21436
21735
  await ensureClaudeMdPointer();
21437
21736
  printInfo(" CLAUDE.md memory pointer \u2713");
@@ -21473,7 +21772,7 @@ function registerInitCommand(program2) {
21473
21772
  }
21474
21773
 
21475
21774
  // src/commands/uninstall.ts
21476
- var import_node_fs35 = require("node:fs");
21775
+ var import_node_fs36 = require("node:fs");
21477
21776
  var import_node_path27 = require("node:path");
21478
21777
  var SKILL_NAMES = [
21479
21778
  "verity-setup",
@@ -21494,10 +21793,10 @@ function registerUninstallCommand(program2) {
21494
21793
  const skillsRoot = projectPath(".claude/skills");
21495
21794
  for (const name of SKILL_NAMES) {
21496
21795
  const dir = (0, import_node_path27.join)(skillsRoot, name);
21497
- if ((0, import_node_fs35.existsSync)(dir)) {
21796
+ if ((0, import_node_fs36.existsSync)(dir)) {
21498
21797
  actions.push({
21499
21798
  label: `Remove .claude/skills/${name}/`,
21500
- apply: () => (0, import_node_fs35.rmSync)(dir, { recursive: true, force: true })
21799
+ apply: () => (0, import_node_fs36.rmSync)(dir, { recursive: true, force: true })
21501
21800
  });
21502
21801
  }
21503
21802
  }
@@ -21511,24 +21810,24 @@ function registerUninstallCommand(program2) {
21511
21810
  });
21512
21811
  }
21513
21812
  const verityDir = projectPath(VERITY_DIR);
21514
- if ((0, import_node_fs35.existsSync)(verityDir)) {
21813
+ if ((0, import_node_fs36.existsSync)(verityDir)) {
21515
21814
  actions.push({
21516
21815
  label: `Remove ${VERITY_DIR}/`,
21517
- apply: () => (0, import_node_fs35.rmSync)(verityDir, { recursive: true, force: true })
21816
+ apply: () => (0, import_node_fs36.rmSync)(verityDir, { recursive: true, force: true })
21518
21817
  });
21519
21818
  }
21520
21819
  if (!keepVerityMd) {
21521
21820
  const verityMd = projectPath(VERITY_MD_FILE);
21522
- if ((0, import_node_fs35.existsSync)(verityMd)) {
21821
+ if ((0, import_node_fs36.existsSync)(verityMd)) {
21523
21822
  actions.push({
21524
21823
  label: `Remove ${VERITY_MD_FILE}`,
21525
- apply: () => (0, import_node_fs35.rmSync)(verityMd, { force: true })
21824
+ apply: () => (0, import_node_fs36.rmSync)(verityMd, { force: true })
21526
21825
  });
21527
21826
  }
21528
21827
  }
21529
21828
  const cleanupEmptyDir = (path) => {
21530
- if ((0, import_node_fs35.existsSync)(path) && (0, import_node_fs35.statSync)(path).isDirectory() && (0, import_node_fs35.readdirSync)(path).length === 0) {
21531
- (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);
21532
21831
  }
21533
21832
  };
21534
21833
  actions.push({
@@ -21540,10 +21839,10 @@ function registerUninstallCommand(program2) {
21540
21839
  });
21541
21840
  const home = process.env.HOME ?? "";
21542
21841
  const globalVerityDir = (0, import_node_path27.join)(home, ".verity");
21543
- if (purgeGlobal && (0, import_node_fs35.existsSync)(globalVerityDir)) {
21842
+ if (purgeGlobal && (0, import_node_fs36.existsSync)(globalVerityDir)) {
21544
21843
  actions.push({
21545
21844
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
21546
- apply: () => (0, import_node_fs35.rmSync)(globalVerityDir, { recursive: true, force: true })
21845
+ apply: () => (0, import_node_fs36.rmSync)(globalVerityDir, { recursive: true, force: true })
21547
21846
  });
21548
21847
  }
21549
21848
  if (actions.length === 0) {
@@ -21737,7 +22036,7 @@ function registerTaskCommands(program2) {
21737
22036
  }
21738
22037
 
21739
22038
  // src/commands/reset.ts
21740
- var import_node_fs36 = require("node:fs");
22039
+ var import_node_fs37 = require("node:fs");
21741
22040
  var import_node_path28 = require("node:path");
21742
22041
  function registerResetCommand(program2) {
21743
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) => {
@@ -21775,11 +22074,11 @@ function registerResetCommand(program2) {
21775
22074
  }
21776
22075
  const cacheDir = projectPath(CACHE_DIR);
21777
22076
  let purged = 0;
21778
- if ((0, import_node_fs36.existsSync)(cacheDir)) {
21779
- 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)) {
21780
22079
  if (entry.startsWith("pending-")) {
21781
22080
  try {
21782
- (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));
21783
22082
  purged++;
21784
22083
  } catch {
21785
22084
  }
@@ -21794,19 +22093,19 @@ function registerResetCommand(program2) {
21794
22093
  projectPath(`${VERITY_DIR}/.last-analysis`)
21795
22094
  ];
21796
22095
  for (const file of filesToClear) {
21797
- if ((0, import_node_fs36.existsSync)(file)) {
22096
+ if ((0, import_node_fs37.existsSync)(file)) {
21798
22097
  try {
21799
- (0, import_node_fs36.writeFileSync)(file, "");
22098
+ (0, import_node_fs37.writeFileSync)(file, "");
21800
22099
  } catch {
21801
22100
  }
21802
22101
  }
21803
22102
  }
21804
22103
  if (opts.all) {
21805
22104
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
21806
- if ((0, import_node_fs36.existsSync)(logsDir)) {
21807
- 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)) {
21808
22107
  try {
21809
- (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));
21810
22109
  } catch {
21811
22110
  }
21812
22111
  }
@@ -22114,8 +22413,8 @@ function registerTelemetryCommands(program2) {
22114
22413
  }
22115
22414
 
22116
22415
  // src/cli.ts
22117
- program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.e387745").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) => {
22118
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.e387745");
22416
+ program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.e818a8a").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.e818a8a");
22119
22418
  setUserNamedServiceUrl(program.opts().serviceUrl);
22120
22419
  try {
22121
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.e387745",
3
+ "version": "0.29.4-experimental.e818a8a",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",