@codacy/verity-cli 0.29.4-experimental.5fcba03 → 0.29.4-experimental.6f2ede0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/verity.js +543 -159
  2. package/package.json +1 -1
package/bin/verity.js CHANGED
@@ -10390,11 +10390,11 @@ var MAX_DELTA_BYTES = 194560;
10390
10390
  var MAX_FILES = 40;
10391
10391
  var MAX_FILE_BYTES = 51200;
10392
10392
  var DEBOUNCE_SECONDS = 30;
10393
- var MAX_SPEC_FILES = 10;
10394
- var MAX_SPEC_FILE_BYTES = 10240;
10395
- var MAX_TOTAL_SPEC_BYTES = 30720;
10393
+ var MAX_SPEC_FILES = 6;
10394
+ var MAX_SPEC_FILE_BYTES = 512e3;
10395
+ var MAX_TOTAL_SPEC_BYTES = 512e3;
10396
10396
  var MAX_PLAN_FILES = 3;
10397
- var MAX_PLAN_FILE_BYTES = 10240;
10397
+ var MAX_PLAN_FILE_BYTES = 512e3;
10398
10398
  var MAX_INTENT_CHARS = 2e3;
10399
10399
  var SNAPSHOT_DIR = `${VERITY_DIR}/.snapshot`;
10400
10400
  var BASELINE_DIR = `${VERITY_DIR}/.baseline`;
@@ -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);
@@ -16717,7 +16720,7 @@ function formatRunEvidence(run, startedAt) {
16717
16720
  const md = run.modeDecision;
16718
16721
  if (md) {
16719
16722
  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"}`);
16723
+ 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
16724
  } else {
16722
16725
  out += row("mode", `? (this run stopped in ${run.phaseReached || "no phase"}, before the mode was decided)`);
16723
16726
  }
@@ -16765,6 +16768,30 @@ function formatRunEvidence(run, startedAt) {
16765
16768
  out += row("", `${cov.outsideRepo} authored path(s) refused as outside the repo`);
16766
16769
  }
16767
16770
  }
16771
+ if (run.foldResult?.tools?.length) {
16772
+ const shown = run.foldResult.tools.slice(0, 6).map((t) => {
16773
+ const outcome = t.failed > 0 ? `${t.failed} failed` : t.last_status === 0 ? "ok" : "?";
16774
+ const where = t.targets.length > 0 ? ` \u2192 ${t.targets.slice(0, 2).join(", ")}` : "";
16775
+ return `${t.runs}\xD7 ${t.name} (${outcome})${where}`;
16776
+ });
16777
+ const more = run.foldResult.tools.length > 6 ? ` \u2026 +${run.foldResult.tools.length - 6} more` : "";
16778
+ out += row("tools", shown.join(" \xB7 ") + more);
16779
+ if (run.foldResult.coverage.toolNamesDropped > 0) {
16780
+ out += row("", `\u26A0 ${run.foldResult.coverage.toolNamesDropped} tool name(s) refused by the cap`);
16781
+ }
16782
+ }
16783
+ if (run.foldResult?.tasks?.length) {
16784
+ const t = run.foldResult.tasks;
16785
+ const done2 = t.filter((x) => x.status === "completed").length;
16786
+ out += row("tasks", `${t.length} \xB7 ${done2} completed \xB7 ` + list(t.slice(0, 4).map((x) => `#${x.id} ${x.name} [${x.status}]`), 4));
16787
+ }
16788
+ if (run.specs?.length) {
16789
+ const readThisSession = new Set(run.actionSummary?.files_read ?? []);
16790
+ const labelled = run.specs.map(
16791
+ (s) => `${s.path}${readThisSession.has(s.path) ? " (read)" : " (positional)"}`
16792
+ );
16793
+ out += row("specs", `${run.specs.length} \xB7 ${list(labelled, 5)}`);
16794
+ }
16768
16795
  if (run.staticResults.findings.length > 0) {
16769
16796
  out += row("static", `${run.staticResults.findings.length} finding(s) from ${run.staticResults.summary.tools_run.join(", ") || "no tools"}`);
16770
16797
  }
@@ -17122,6 +17149,39 @@ async function bootstrap(run) {
17122
17149
  Object.assign(run, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
17123
17150
  }
17124
17151
 
17152
+ // src/lib/self-scope.ts
17153
+ var LEGACY_GATE_SKILLS = /* @__PURE__ */ new Set([
17154
+ "gate-setup",
17155
+ "gate-analyze",
17156
+ "gate-review",
17157
+ "gate-status",
17158
+ "gate-feedback",
17159
+ "gate-insights",
17160
+ "gate-learn",
17161
+ "gate-memory",
17162
+ "gate-reflect"
17163
+ ]);
17164
+ function isVerityOwned(path) {
17165
+ const segments = path.replace(/\\/g, "/").split("/");
17166
+ for (let i = 0; i < segments.length; i++) {
17167
+ const seg = segments[i];
17168
+ if (seg === ".verity" || seg === ".codacy") return true;
17169
+ if (i === segments.length - 1 && (seg === "VERITY.md" || seg === "GATE.md")) return true;
17170
+ if (seg === ".claude" && segments[i + 1] === "skills" && typeof segments[i + 2] === "string") {
17171
+ const skill = segments[i + 2];
17172
+ if (skill.startsWith("verity-") || LEGACY_GATE_SKILLS.has(skill)) return true;
17173
+ }
17174
+ if (seg === ".claude" && segments[i + 1] === "settings.json") return true;
17175
+ }
17176
+ return false;
17177
+ }
17178
+ function partitionVerityOwned(paths) {
17179
+ const kept = [];
17180
+ const owned = [];
17181
+ for (const p of paths) (isVerityOwned(p) ? owned : kept).push(p);
17182
+ return { kept, owned };
17183
+ }
17184
+
17125
17185
  // src/lib/channel.ts
17126
17186
  var MAX_AGENT_CONTEXT_CHARS = 1500;
17127
17187
  var MAX_AGENT_ITEMS = 5;
@@ -17131,6 +17191,29 @@ function renderItem(label2, text, patternId, file, line) {
17131
17191
  const id = patternId ? ` [${patternId}]` : "";
17132
17192
  return `- ${label2}${text}${where}${id}`;
17133
17193
  }
17194
+ function channelInputFrom(response, intentRepeat = 0, priorPendingFingerprints = []) {
17195
+ const metadata = response.metadata ?? {};
17196
+ const intent = response.intent_alignment ?? {};
17197
+ return {
17198
+ intentRepeat,
17199
+ priorPendingFingerprints,
17200
+ gateDecision: String(response.gate_decision ?? ""),
17201
+ findings: response.findings ?? [],
17202
+ pendingItems: response.pending_items ?? [],
17203
+ reviewStatus: metadata.review_status,
17204
+ coverage: metadata.coverage,
17205
+ intentVerdict: intent.verdict,
17206
+ intentGaps: intent.gaps
17207
+ };
17208
+ }
17209
+ function classifyChannelContent(input) {
17210
+ const refusal = input.reviewStatus === "not_reviewed" || input.reviewStatus === "no_authorship_evidence";
17211
+ const intentFlag = input.intentVerdict === "misaligned" || input.intentVerdict === "partial";
17212
+ const advisory = (input.findings ?? []).some((f) => f.scope !== "pre-existing") || (input.pendingItems ?? []).some(
17213
+ (p) => p.pattern_id !== "intent-misalignment" && !!(p.description ?? p.title ?? p.reason)
17214
+ );
17215
+ return { refusal, intentFlag, advisory };
17216
+ }
17134
17217
  function buildAgentContext(input) {
17135
17218
  const lines = [];
17136
17219
  if (input.reviewStatus === "not_reviewed") {
@@ -17216,7 +17299,7 @@ function channelSilence(input) {
17216
17299
  // src/lib/cli-version.ts
17217
17300
  function cliVersion() {
17218
17301
  try {
17219
- return true ? "0.29.4-experimental.5fcba03" : "dev";
17302
+ return true ? "0.29.4-experimental.6f2ede0" : "dev";
17220
17303
  } catch {
17221
17304
  return "dev";
17222
17305
  }
@@ -17538,9 +17621,10 @@ async function scope(run) {
17538
17621
  const { assistantResponse } = run;
17539
17622
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
17540
17623
  run.changedUniverse = allChanged;
17541
- const analyzable = filterAnalyzable(allChanged);
17542
- const reviewable = filterReviewable(allChanged);
17543
- const securityFiles = filterSecurity(allChanged);
17624
+ const { kept: external } = partitionVerityOwned(allChanged);
17625
+ const analyzable = filterAnalyzable(external);
17626
+ const reviewable = filterReviewable(external);
17627
+ const securityFiles = filterSecurity(external);
17544
17628
  const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
17545
17629
  if (noFilesChanged && !assistantResponse) {
17546
17630
  await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
@@ -17567,18 +17651,26 @@ var SPEC_CANDIDATES = [
17567
17651
  "docs/API.md",
17568
17652
  "spec/ARCHITECTURE.md"
17569
17653
  ];
17570
- function discoverSpecs() {
17654
+ var DOC_EXT = /\.(md|mdx|ya?ml|txt|rst|adoc)$/i;
17655
+ var UNCONSULTED_FILE_BYTES = 10240;
17656
+ var UNCONSULTED_TOTAL_BYTES = 30720;
17657
+ function discoverSpecs(consulted = []) {
17571
17658
  const result = [];
17572
17659
  const seen = /* @__PURE__ */ new Set();
17573
17660
  let totalBytes = 0;
17574
- const addSpec = (specPath) => {
17661
+ const consultedDocs = new Set(
17662
+ consulted.filter((p) => DOC_EXT.test(p) && !p.startsWith("/") && !p.includes(".."))
17663
+ );
17664
+ const addSpec = (specPath, relevant = false) => {
17575
17665
  if (result.length >= MAX_SPEC_FILES) return false;
17576
- if (totalBytes >= MAX_TOTAL_SPEC_BYTES) return false;
17666
+ const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
17667
+ if (totalBytes >= totalCap) return false;
17577
17668
  if (seen.has(specPath)) return true;
17578
17669
  if (!(0, import_node_fs21.existsSync)(specPath)) return true;
17579
17670
  seen.add(specPath);
17580
- const remaining = MAX_TOTAL_SPEC_BYTES - totalBytes;
17581
- const readBytes = Math.min(MAX_SPEC_FILE_BYTES, remaining);
17671
+ const remaining = totalCap - totalBytes;
17672
+ const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
17673
+ const readBytes = Math.min(fileCap, remaining);
17582
17674
  try {
17583
17675
  const buf = Buffer.alloc(readBytes);
17584
17676
  const fd = (0, import_node_fs21.openSync)(specPath, "r");
@@ -17592,6 +17684,9 @@ function discoverSpecs() {
17592
17684
  }
17593
17685
  return true;
17594
17686
  };
17687
+ for (const doc of consultedDocs) {
17688
+ if (!addSpec(doc, true)) break;
17689
+ }
17595
17690
  for (const candidate of SPEC_CANDIDATES) {
17596
17691
  if (!addSpec(candidate)) break;
17597
17692
  }
@@ -17662,7 +17757,7 @@ function discoverPlans() {
17662
17757
  async function intentInputs(run) {
17663
17758
  const { actionSummary, allForReview, assistantResponse, baseline, baselineSessionId } = run;
17664
17759
  const conversation = await readAndClearConversationBuffer(baselineSessionId);
17665
- const specs = discoverSpecs();
17760
+ const specs = discoverSpecs(actionSummary?.files_read ?? []);
17666
17761
  const plans = discoverPlans();
17667
17762
  const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
17668
17763
  const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
@@ -18022,26 +18117,50 @@ function narrowToRecent(files, sessionId) {
18022
18117
  });
18023
18118
  return recent.length > 0 ? recent : files;
18024
18119
  }
18025
- function readIterationState(currentCommit) {
18026
- if (!(0, import_node_fs22.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
18120
+ function readIteration(currentCommit, _contentHash) {
18121
+ return Math.max(1, readBlockState(currentCommit).attempts);
18122
+ }
18123
+ var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
18124
+ function readBlockState(currentCommit, opts) {
18125
+ if (opts?.newUserPrompt) return NO_BLOCKS;
18126
+ if (!(0, import_node_fs22.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
18027
18127
  try {
18028
18128
  const stored = (0, import_node_fs22.readFileSync)(ITERATION_FILE, "utf-8").trim();
18029
- const parts = stored.split(":");
18030
- const iter = parseInt(parts[0], 10);
18031
- const storedCommit = parts[1] ?? "";
18032
- const storedTimestamp = parseInt(parts[2] ?? "0", 10);
18033
- const fingerprint = parts.slice(3).join(":") || null;
18034
- if (isNaN(iter)) return { iteration: 1, fingerprint: null };
18035
- if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
18036
- if (storedTimestamp > 0) {
18037
- const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
18038
- if (elapsed > 600) return { iteration: 1, fingerprint: null };
18039
- }
18040
- return { iteration: iter, fingerprint };
18129
+ const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
18130
+ if (!parsed) return NO_BLOCKS;
18131
+ if (parsed.commit !== currentCommit) return NO_BLOCKS;
18132
+ if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
18133
+ return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
18041
18134
  } catch {
18042
- return { iteration: 1, fingerprint: null };
18135
+ return NO_BLOCKS;
18043
18136
  }
18044
18137
  }
18138
+ function parseJsonState(raw) {
18139
+ const o = JSON.parse(raw);
18140
+ const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
18141
+ if (isNaN(attempts)) return null;
18142
+ return {
18143
+ attempts,
18144
+ blocks: typeof o.blocks === "number" ? o.blocks : attempts,
18145
+ fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
18146
+ commit: typeof o.commit === "string" ? o.commit : "",
18147
+ ts: typeof o.ts === "number" ? o.ts : 0
18148
+ };
18149
+ }
18150
+ function parseLegacyState(raw) {
18151
+ const parts = raw.split(":");
18152
+ const n = parseInt(parts[0], 10);
18153
+ if (isNaN(n)) return null;
18154
+ return {
18155
+ attempts: n,
18156
+ // The old file has no separate block count; the old counter is the closest
18157
+ // honest answer, and it errs toward releasing sooner rather than later.
18158
+ blocks: n,
18159
+ fingerprint: parts.slice(3).join(":") || null,
18160
+ commit: parts[1] ?? "",
18161
+ ts: parseInt(parts[2] ?? "0", 10)
18162
+ };
18163
+ }
18045
18164
  function findingsFingerprint(findings) {
18046
18165
  const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
18047
18166
  return [...new Set(keys)].sort().join(",");
@@ -18051,11 +18170,22 @@ function isSameProblem(previous, current) {
18051
18170
  const prev = new Set(previous.split(","));
18052
18171
  return current.split(",").some((k) => prev.has(k));
18053
18172
  }
18054
- function writeIteration(iteration, commit, _contentHash, fingerprint) {
18173
+ function writeBlockState(commit, state) {
18055
18174
  (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
18056
- const ts = Math.floor(Date.now() / 1e3);
18057
- const fp = fingerprint ? `:${fingerprint}` : "";
18058
- (0, import_node_fs22.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
18175
+ (0, import_node_fs22.writeFileSync)(
18176
+ ITERATION_FILE,
18177
+ JSON.stringify({
18178
+ v: 2,
18179
+ attempts: state.attempts,
18180
+ blocks: state.blocks,
18181
+ commit,
18182
+ ts: Math.floor(Date.now() / 1e3),
18183
+ fingerprint: state.fingerprint ?? void 0
18184
+ })
18185
+ );
18186
+ }
18187
+ function resetBlockState(commit) {
18188
+ writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
18059
18189
  }
18060
18190
 
18061
18191
  // src/lib/fold.ts
@@ -18178,6 +18308,18 @@ function commandShape(cmd) {
18178
18308
  return out.join(" ").slice(0, COMMAND_HEAD_CHARS);
18179
18309
  }
18180
18310
  var COMMAND_HEAD_CHARS = 80;
18311
+ var MAX_TOOL_NAMES = 64;
18312
+ var MAX_TOOL_TARGETS = 3;
18313
+ var MAX_TASKS = 64;
18314
+ var TASK_NAME_CHARS = 120;
18315
+ function toolTarget(name, input) {
18316
+ if (name === "Bash") return null;
18317
+ for (const key of ["file_path", "notebook_path", "path", "filePath"]) {
18318
+ const v = input[key];
18319
+ if (typeof v === "string" && v) return v.slice(0, 120);
18320
+ }
18321
+ return null;
18322
+ }
18181
18323
  var rootCandidateCache = /* @__PURE__ */ new Map();
18182
18324
  function candidateRoots(repoRoot2) {
18183
18325
  const cached2 = rootCandidateCache.get(repoRoot2);
@@ -18208,15 +18350,20 @@ function toRepoRelative(path, repoRoot2) {
18208
18350
  }
18209
18351
  function isInsideRepo(path, repoRoot2) {
18210
18352
  const p = path.replace(/\\/g, "/");
18211
- if (!p.startsWith("/")) return true;
18353
+ if (!isAbsolutePath(p)) return true;
18212
18354
  if (!repoRoot2) return true;
18213
18355
  return candidateRoots(repoRoot2).some((root) => p === root || p.startsWith(root + "/"));
18214
18356
  }
18357
+ function isAbsolutePath(p) {
18358
+ return p.startsWith("/") || /^[A-Za-z]:\//.test(p);
18359
+ }
18215
18360
  function fold(transcriptPath, opts = {}) {
18216
18361
  const result = {
18217
18362
  authored: [],
18218
18363
  unobserved: [],
18219
18364
  commands: [],
18365
+ tools: [],
18366
+ tasks: [],
18220
18367
  unknownTypes: [],
18221
18368
  coverage: {
18222
18369
  recordCounts: {},
@@ -18224,16 +18371,23 @@ function fold(transcriptPath, opts = {}) {
18224
18371
  malformed: 0,
18225
18372
  subagentFiles: 0,
18226
18373
  outsideRepo: 0,
18374
+ toolNamesDropped: 0,
18227
18375
  dispatched: 0,
18228
18376
  userMessages: 0,
18229
18377
  subagentSkipped: 0,
18230
18378
  compactions: 0,
18231
18379
  complete: false
18232
- }
18380
+ },
18381
+ planApproval: { approvals: 0, activeSinceLastPrompt: false }
18233
18382
  };
18383
+ const flow = { seq: 0, lastPrompt: -1, lastApproval: -1, approvals: 0 };
18234
18384
  const byPath = /* @__PURE__ */ new Map();
18235
18385
  const commandStats = /* @__PURE__ */ new Map();
18236
18386
  const pendingByToolUse = /* @__PURE__ */ new Map();
18387
+ const toolStats = /* @__PURE__ */ new Map();
18388
+ const pendingToolName = /* @__PURE__ */ new Map();
18389
+ const taskById = /* @__PURE__ */ new Map();
18390
+ const pendingTaskName = /* @__PURE__ */ new Map();
18237
18391
  const unknown = /* @__PURE__ */ new Set();
18238
18392
  const ingest = (raw, owner) => {
18239
18393
  for (const line of raw.split("\n")) {
@@ -18252,8 +18406,12 @@ function fold(transcriptPath, opts = {}) {
18252
18406
  if (type === "system" && record.subtype === "compact_boundary") {
18253
18407
  result.coverage.compactions++;
18254
18408
  }
18255
- if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
18256
- collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot, result.coverage);
18409
+ if (type === "user" && hasUserText(record)) {
18410
+ result.coverage.userMessages++;
18411
+ if (owner === "agent") flow.lastPrompt = flow.seq;
18412
+ }
18413
+ if (owner === "agent") flow.seq++;
18414
+ collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, opts.repoRoot, result.coverage, flow);
18257
18415
  }
18258
18416
  };
18259
18417
  try {
@@ -18311,12 +18469,24 @@ function fold(transcriptPath, opts = {}) {
18311
18469
  result.authored = [...byPath.values()].sort((a, b) => a.p.localeCompare(b.p));
18312
18470
  result.unknownTypes = [...unknown].sort();
18313
18471
  result.commands = [...commandStats.entries()].map(([cls, s]) => ({ class: cls, ...s })).sort((a, b) => a.class.localeCompare(b.class));
18472
+ result.tools = [...toolStats.entries()].map(([name, s]) => ({
18473
+ name,
18474
+ runs: s.runs,
18475
+ failed: s.failed,
18476
+ last_status: s.last_status,
18477
+ targets: [...s.targets].map((t) => toRepoRelative(t, opts.repoRoot)).sort()
18478
+ })).sort((a, b) => b.runs - a.runs || a.name.localeCompare(b.name));
18479
+ result.tasks = [...taskById.entries()].map(([id, t]) => ({ id, name: t.name, status: t.status })).sort((a, b) => (Number(a.id) || 0) - (Number(b.id) || 0));
18314
18480
  const authoredPaths = new Set(result.authored.map((a) => a.p));
18315
18481
  for (const raw of opts.changedFiles ?? []) {
18316
18482
  const p = toRepoRelative(raw, opts.repoRoot);
18317
18483
  if (!p || authoredPaths.has(p)) continue;
18318
18484
  result.unobserved.push({ p, cause: classifyUnobserved(raw) });
18319
18485
  }
18486
+ result.planApproval = {
18487
+ approvals: flow.approvals,
18488
+ activeSinceLastPrompt: flow.lastApproval >= 0 && flow.lastApproval > flow.lastPrompt
18489
+ };
18320
18490
  return result;
18321
18491
  }
18322
18492
  function classifyUnobserved(path) {
@@ -18329,7 +18499,7 @@ function classifyUnobserved(path) {
18329
18499
  if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
18330
18500
  return "no_edit_record";
18331
18501
  }
18332
- function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2, tally) {
18502
+ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally, flow) {
18333
18503
  const message = record.message;
18334
18504
  const content = message?.content ?? record.content;
18335
18505
  const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
@@ -18338,6 +18508,32 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18338
18508
  if (blockType === "tool_use") {
18339
18509
  const name = String(block.name ?? "");
18340
18510
  const input = block.input ?? {};
18511
+ if (name && toolStats.size < MAX_TOOL_NAMES) {
18512
+ const prev = toolStats.get(name) ?? { runs: 0, failed: 0, last_status: null, targets: /* @__PURE__ */ new Set() };
18513
+ prev.targets = prev.targets ?? /* @__PURE__ */ new Set();
18514
+ if (prev.targets.size < MAX_TOOL_TARGETS) {
18515
+ const target = toolTarget(name, input);
18516
+ if (target) prev.targets.add(target);
18517
+ }
18518
+ toolStats.set(name, { runs: prev.runs + 1, failed: prev.failed, last_status: null, targets: prev.targets });
18519
+ const toolId = typeof block.id === "string" ? block.id : null;
18520
+ if (toolId) pendingToolName.set(toolId, name);
18521
+ } else if (name && tally) {
18522
+ tally.toolNamesDropped += 1;
18523
+ }
18524
+ if (name === "TaskCreate") {
18525
+ const subject = typeof input.subject === "string" ? input.subject : "";
18526
+ const taskId = typeof block.id === "string" ? block.id : null;
18527
+ if (subject && taskId) pendingTaskName.set(taskId, subject.slice(0, TASK_NAME_CHARS));
18528
+ }
18529
+ if (name === "TaskUpdate") {
18530
+ const id = typeof input.taskId === "string" ? input.taskId : "";
18531
+ const status = typeof input.status === "string" ? input.status : "";
18532
+ if (id && status) {
18533
+ const entry = taskById.get(id);
18534
+ taskById.set(id, { name: entry?.name ?? `#${id}`, status });
18535
+ }
18536
+ }
18341
18537
  if (EDIT_TOOLS.has(name)) {
18342
18538
  const rawPath = typeof input.notebook_path === "string" ? input.notebook_path : typeof input.file_path === "string" ? input.file_path : null;
18343
18539
  if (rawPath && !isInsideRepo(rawPath, repoRoot2)) {
@@ -18377,6 +18573,38 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18377
18573
  }
18378
18574
  if (blockType === "tool_result") {
18379
18575
  const id = typeof block.tool_use_id === "string" ? block.tool_use_id : null;
18576
+ const taskName = id ? pendingTaskName.get(id) : void 0;
18577
+ if (taskName) {
18578
+ pendingTaskName.delete(id);
18579
+ const body = typeof block.content === "string" ? block.content : "";
18580
+ const created = /Task #(\d+)/.exec(body);
18581
+ if (created && taskById.size < MAX_TASKS) {
18582
+ taskById.set(created[1], { name: taskName, status: taskById.get(created[1])?.status ?? "created" });
18583
+ }
18584
+ }
18585
+ const toolName = id ? pendingToolName.get(id) : void 0;
18586
+ if (toolName === "ExitPlanMode" && flow && block.is_error !== true) {
18587
+ const body = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => typeof c.text === "string" ? c.text : "").join(" ") : "";
18588
+ if (/approved your plan/i.test(body)) {
18589
+ flow.lastApproval = flow.seq;
18590
+ flow.approvals += 1;
18591
+ }
18592
+ }
18593
+ if (toolName) {
18594
+ pendingToolName.delete(id);
18595
+ const prevTool = toolStats.get(toolName);
18596
+ if (prevTool) {
18597
+ const failed = block.is_error === true;
18598
+ toolStats.set(toolName, {
18599
+ runs: prevTool.runs,
18600
+ failed: prevTool.failed + (failed ? 1 : 0),
18601
+ last_status: block.is_error === true ? 1 : block.is_error === false ? 0 : null,
18602
+ // Carried, not rebuilt: the targets were collected on the `tool_use`
18603
+ // side and dropping them here would empty the ledger on every result.
18604
+ targets: prevTool.targets
18605
+ });
18606
+ }
18607
+ }
18380
18608
  const cls = id ? pendingByToolUse.get(id) : void 0;
18381
18609
  if (!cls) continue;
18382
18610
  pendingByToolUse.delete(id);
@@ -18419,8 +18647,13 @@ function checkConservation(changedFiles, result, repoRoot2) {
18419
18647
  // src/commands/analyze/phases/06-evidence.ts
18420
18648
  async function evidence(run) {
18421
18649
  const { opts } = run;
18422
- const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath } = run;
18650
+ const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath, turnAuthoredCode } = run;
18423
18651
  let { analysisMode, earlyFold } = run;
18652
+ const recordFlip = (stage) => {
18653
+ if (run.modeDecision) run.modeDecision = { ...run.modeDecision, resolved: "plan", flip: stage };
18654
+ logEvent("mode_flipped", { stage, to: "plan" });
18655
+ };
18656
+ const planWorthy = !!assistantResponse && !turnAuthoredCode;
18424
18657
  let staticResults = {
18425
18658
  tool: "@codacy/analysis-cli",
18426
18659
  findings: [],
@@ -18440,8 +18673,9 @@ async function evidence(run) {
18440
18673
  const debounceSeconds = parseInt(opts.debounce, 10);
18441
18674
  const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
18442
18675
  if (debounceSkip) {
18443
- if (assistantResponse) {
18676
+ if (planWorthy) {
18444
18677
  analysisMode = "plan";
18678
+ recordFlip("debounce");
18445
18679
  } else {
18446
18680
  await passAndExit(run, debounceSkip, "debounce");
18447
18681
  }
@@ -18450,8 +18684,9 @@ async function evidence(run) {
18450
18684
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18451
18685
  const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
18452
18686
  if (mtimeSkip) {
18453
- if (assistantResponse) {
18687
+ if (planWorthy) {
18454
18688
  analysisMode = "plan";
18689
+ recordFlip("mtime");
18455
18690
  } else {
18456
18691
  await passAndExit(run, mtimeSkip, "no-delta-since-last-review");
18457
18692
  }
@@ -18462,8 +18697,9 @@ async function evidence(run) {
18462
18697
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18463
18698
  const hashResult = checkContentHash(allCheckable, baselineSessionId);
18464
18699
  if (hashResult.skip) {
18465
- if (assistantResponse) {
18700
+ if (planWorthy) {
18466
18701
  analysisMode = "plan";
18702
+ recordFlip("content-hash");
18467
18703
  } else {
18468
18704
  await passAndExit(run, hashResult.skip, "no-delta-since-last-review");
18469
18705
  }
@@ -18525,8 +18761,9 @@ async function evidence(run) {
18525
18761
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
18526
18762
  });
18527
18763
  if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
18528
- if (assistantResponse) {
18764
+ if (planWorthy) {
18529
18765
  analysisMode = "plan";
18766
+ recordFlip("empty-after-scoping");
18530
18767
  } else {
18531
18768
  await passAndExit(
18532
18769
  run,
@@ -18546,13 +18783,13 @@ async function evidence(run) {
18546
18783
  snapshotResult = generateSnapshotDiffs(codeDelta.files);
18547
18784
  }
18548
18785
  currentCommit = getCurrentCommit();
18549
- iteration = readIterationState(currentCommit).iteration;
18786
+ iteration = readIteration(currentCommit);
18550
18787
  }
18551
18788
  }
18552
18789
  if (analysisMode === "plan") {
18553
18790
  recordAnalysisStart();
18554
18791
  currentCommit = getCurrentCommit();
18555
- iteration = readIterationState(currentCommit).iteration;
18792
+ iteration = readIteration(currentCommit);
18556
18793
  }
18557
18794
  Object.assign(run, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
18558
18795
  }
@@ -18648,7 +18885,8 @@ function gatherContextFiles(contextPaths, deltaFiles) {
18648
18885
  // src/commands/analyze/phases/07-context-files.ts
18649
18886
  async function contextFiles(run) {
18650
18887
  const { codeDelta, contextFilePaths } = run;
18651
- const contextFiles2 = gatherContextFiles(contextFilePaths, codeDelta.files);
18888
+ const { kept: externalContext } = partitionVerityOwned(contextFilePaths ?? []);
18889
+ const contextFiles2 = gatherContextFiles(externalContext, codeDelta.files);
18652
18890
  for (const f of codeDelta.files) {
18653
18891
  f.role = "delta";
18654
18892
  }
@@ -19218,6 +19456,54 @@ async function workingMemory(run) {
19218
19456
  Object.assign(run, { incrementReport, memory, memorySession, reachability });
19219
19457
  }
19220
19458
 
19459
+ // src/lib/note-budget.ts
19460
+ var import_node_fs28 = require("node:fs");
19461
+ var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
19462
+ var EPISODE_STALE_SECONDS = 30 * 60;
19463
+ var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
19464
+ function resolveEpisode(prev, signals) {
19465
+ if (!prev) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19466
+ if (signals.humanSpoke) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19467
+ if (signals.rawFail) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19468
+ if (prev.tasksCompleted !== signals.tasksCompleted) {
19469
+ return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19470
+ }
19471
+ if (prev.ts > 0 && signals.now - prev.ts > EPISODE_STALE_SECONDS) {
19472
+ return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19473
+ }
19474
+ return prev;
19475
+ }
19476
+ function advisoryBudgetSpent(episode, rawDecision) {
19477
+ const budget = ADVISORY_BUDGET[rawDecision] ?? ADVISORY_BUDGET.WARN;
19478
+ return episode.delivered >= budget;
19479
+ }
19480
+ function readAdvisoryEpisode(sessionId) {
19481
+ const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
19482
+ if (!(0, import_node_fs28.existsSync)(file)) return null;
19483
+ try {
19484
+ const o = JSON.parse((0, import_node_fs28.readFileSync)(file, "utf-8")) ?? {};
19485
+ const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
19486
+ if (isNaN(delivered)) return null;
19487
+ return {
19488
+ delivered,
19489
+ tasksCompleted: typeof o.tasksCompleted === "number" ? o.tasksCompleted : 0,
19490
+ ts: typeof o.ts === "number" ? o.ts : 0
19491
+ };
19492
+ } catch {
19493
+ return null;
19494
+ }
19495
+ }
19496
+ function writeAdvisoryEpisode(episode, sessionId) {
19497
+ try {
19498
+ (0, import_node_fs28.mkdirSync)(VERITY_DIR, { recursive: true });
19499
+ (0, import_node_fs28.writeFileSync)(
19500
+ scopedFile(ADVISORY_EPISODE_FILE, sessionId),
19501
+ JSON.stringify({ v: 1, ...episode })
19502
+ );
19503
+ } catch {
19504
+ }
19505
+ }
19506
+
19221
19507
  // src/lib/run-mode.ts
19222
19508
  function parseAutonomousEnv(raw) {
19223
19509
  if (raw === void 0) return void 0;
@@ -19311,7 +19597,13 @@ async function buildRequest(run) {
19311
19597
  excluded_by_reason: excludedByReason,
19312
19598
  // was the transcript itself truncated? The 256 KB window means "this turn"
19313
19599
  // can quietly mean "the last 256 KB of it".
19314
- transcript_windowed: actionSummary?.transcript_windowed ?? null
19600
+ transcript_windowed: actionSummary?.transcript_windowed ?? null,
19601
+ // The advisory budget's fleet counter-metric (note-budget.ts): deliveries in
19602
+ // the episode as of the PREVIOUS turn — this runs before phase 13 updates
19603
+ // the state, so the number is one turn lagged by construction. The
19604
+ // degenerate win for the budget is a dead channel that looks like clean
19605
+ // code; this is what makes "did delivery rate collapse" a query.
19606
+ advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
19315
19607
  };
19316
19608
  const requestBody = {
19317
19609
  coverage_telemetry: coverageTelemetry,
@@ -19370,6 +19662,8 @@ async function buildRequest(run) {
19370
19662
  authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
19371
19663
  unobserved: foldResult?.unobserved ?? [],
19372
19664
  commands: foldResult?.commands ?? [],
19665
+ tools: foldResult?.tools ?? [],
19666
+ tasks: foldResult?.tasks ?? [],
19373
19667
  unknown_types: foldResult?.unknownTypes ?? [],
19374
19668
  coverage: foldResult?.coverage ?? { recordCounts: {}, totalRecords: 0, malformed: 0, subagentFiles: 0, subagentSkipped: 0, complete: false },
19375
19669
  // INV-19, computed client-side and sent so a conservation failure is
@@ -19448,7 +19742,8 @@ async function buildRequest(run) {
19448
19742
  }
19449
19743
  const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
19450
19744
  const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
19451
- const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task;
19745
+ const planApprovalActive = foldResult?.planApproval?.activeSinceLastPrompt === true;
19746
+ const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task || planApprovalActive;
19452
19747
  if (hasIntent) {
19453
19748
  const intentContext = {};
19454
19749
  if (conversation && conversation.prompts.length > 0) {
@@ -19480,6 +19775,10 @@ async function buildRequest(run) {
19480
19775
  intentContext.user_prompt = w4Task.goal;
19481
19776
  logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
19482
19777
  }
19778
+ if (planApprovalActive) {
19779
+ intentContext.plan_approved = true;
19780
+ logEvent("plan_approval_carried", { approvals: foldResult.planApproval.approvals });
19781
+ }
19483
19782
  if (assistantResponse) {
19484
19783
  const cap = analysisMode === "plan" ? MAX_ASSISTANT_RESPONSE_CHARS_PLAN : MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT;
19485
19784
  intentContext.assistant_response = assistantResponse.length > cap ? assistantResponse.slice(0, cap) : assistantResponse;
@@ -19499,14 +19798,14 @@ async function buildRequest(run) {
19499
19798
  }
19500
19799
 
19501
19800
  // src/lib/offline.ts
19502
- var import_node_fs28 = require("node:fs");
19801
+ var import_node_fs29 = require("node:fs");
19503
19802
  var import_node_crypto11 = require("node:crypto");
19504
19803
  function cacheRequest(body) {
19505
19804
  try {
19506
- (0, import_node_fs28.mkdirSync)(CACHE_DIR, { recursive: true });
19805
+ (0, import_node_fs29.mkdirSync)(CACHE_DIR, { recursive: true });
19507
19806
  const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
19508
19807
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
19509
- (0, import_node_fs28.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
19808
+ (0, import_node_fs29.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
19510
19809
  } catch {
19511
19810
  }
19512
19811
  }
@@ -19625,10 +19924,10 @@ async function transmit(run) {
19625
19924
  }
19626
19925
 
19627
19926
  // src/commands/analyze/phases/13-reconcile.ts
19628
- var import_node_fs29 = require("node:fs");
19927
+ var import_node_fs30 = require("node:fs");
19629
19928
  var import_node_path23 = require("node:path");
19630
19929
  async function reconcile(run) {
19631
- const { actionSummary, allChanged, analyzable, baseline, codeDelta, contentHash, conversation, decision, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
19930
+ const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
19632
19931
  const sentPaths = codeDelta.files.map((f) => f.path);
19633
19932
  let openElsewhere = [];
19634
19933
  if (memorySession) {
@@ -19636,7 +19935,7 @@ async function reconcile(run) {
19636
19935
  const st = foldDossier(memorySession.d);
19637
19936
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
19638
19937
  try {
19639
- const src = (0, import_node_fs29.readFileSync)((0, import_node_path23.join)(repoRoot(), file), "utf8").split("\n");
19938
+ const src = (0, import_node_fs30.readFileSync)((0, import_node_path23.join)(repoRoot(), file), "utf8").split("\n");
19640
19939
  const at = src[line - 1];
19641
19940
  return at === void 0 ? null : lineSha(at);
19642
19941
  } catch {
@@ -19646,6 +19945,7 @@ async function reconcile(run) {
19646
19945
  } catch {
19647
19946
  }
19648
19947
  }
19948
+ const { kept: externalChanged, owned: verityOwned } = partitionVerityOwned(allChanged);
19649
19949
  const reviewCoverage = {
19650
19950
  reviewed: sentPaths,
19651
19951
  // Declared drops from the stages that DO report themselves today. The other
@@ -19687,11 +19987,20 @@ async function reconcile(run) {
19687
19987
  stage: "baseline-scoping",
19688
19988
  kind: "policy"
19689
19989
  })),
19990
+ // ⚠ VERITY'S OWN FILES, named as such — not laundered into the
19991
+ // extension bucket below, where "we do not review our own installer's
19992
+ // dirt" would read as "a changed README". See self-scope.ts.
19993
+ ...verityOwned.map((path) => ({
19994
+ path,
19995
+ reason: "verity-owned",
19996
+ stage: "self-scope",
19997
+ kind: "policy"
19998
+ })),
19690
19999
  // The extension allowlist. POLICY: a changed README was never going to be
19691
20000
  // reviewed, and calling that a coverage gap would downgrade nearly every
19692
20001
  // PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
19693
20002
  // so "what did Verity ignore entirely" is answerable.
19694
- ...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
20003
+ ...externalChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
19695
20004
  path,
19696
20005
  reason: "not-a-reviewed-file-type",
19697
20006
  stage: "extension-allowlist",
@@ -19728,6 +20037,29 @@ async function reconcile(run) {
19728
20037
  decision
19729
20038
  });
19730
20039
  }
20040
+ const episodeSignals = {
20041
+ humanSpoke: (conversation?.prompts?.length ?? 0) > 0,
20042
+ rawFail: decision === "FAIL",
20043
+ tasksCompleted: (foldResult?.tasks ?? []).filter((t) => t.status === "completed").length,
20044
+ now: Math.floor(Date.now() / 1e3)
20045
+ };
20046
+ let episode = resolveEpisode(readAdvisoryEpisode(baselineSessionId), episodeSignals);
20047
+ const contentClass = classifyChannelContent(channelInputFrom(response));
20048
+ const wouldCarryAdvisory = contentClass.advisory || openElsewhere.length > 0;
20049
+ if (decision !== "FAIL" && !silenced && wouldCarryAdvisory && !contentClass.refusal && !contentClass.intentFlag && advisoryBudgetSpent(episode, decision)) {
20050
+ silenced = "note-budget";
20051
+ logEvent("channel_silenced", {
20052
+ reason: silenced,
20053
+ run_id: response.run_id ?? turnId,
20054
+ decision,
20055
+ episode_delivered: episode.delivered
20056
+ });
20057
+ }
20058
+ const deliveringAdvisory = decision !== "FAIL" && !silenced && wouldCarryAdvisory;
20059
+ writeAdvisoryEpisode(
20060
+ { ...episode, delivered: episode.delivered + (deliveringAdvisory ? 1 : 0), ts: episodeSignals.now },
20061
+ baselineSessionId
20062
+ );
19731
20063
  let intentRepeatCount = 0;
19732
20064
  const priorPendingFingerprints = memorySession ? (() => {
19733
20065
  try {
@@ -19743,6 +20075,11 @@ async function reconcile(run) {
19743
20075
  decision,
19744
20076
  branch: getCurrentBranch(),
19745
20077
  watermarkSha: watermarkIsPartial ? null : watermarkHash,
20078
+ // The byte witness — the same "only honest definition of reviewed" the
20079
+ // coverage column uses. A finding on a path outside this set records no
20080
+ // statement (plan-mode prose anchored to unsent files must not become
20081
+ // "STILL OPEN … the tree is not clean").
20082
+ sentPaths,
19746
20083
  findings: response.findings?.map((f) => ({
19747
20084
  file: f.file,
19748
20085
  line: f.line,
@@ -19809,6 +20146,39 @@ ${YELLOW2}${note}${NC2}
19809
20146
  return exit(0);
19810
20147
  }
19811
20148
 
20149
+ // src/lib/may-block.ts
20150
+ var HARD_BLOCK_CEILING = 5;
20151
+ function mayBlock(input) {
20152
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
20153
+ if (input.reviewedFileCount === 0 && input.staticFindingCount === 0) {
20154
+ return { block: false, release: "no-code-reviewed" };
20155
+ }
20156
+ if (input.cycleCutFired) {
20157
+ return { block: false, release: "nothing-moved" };
20158
+ }
20159
+ if (input.attempts > input.maxIterations) {
20160
+ return { block: false, release: "same-problem-cap" };
20161
+ }
20162
+ if (input.blocks > ceiling) {
20163
+ return { block: false, release: "block-ceiling" };
20164
+ }
20165
+ return { block: true, release: null };
20166
+ }
20167
+ function describeRelease(release, input) {
20168
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
20169
+ 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.";
20170
+ switch (release) {
20171
+ case "no-code-reviewed":
20172
+ 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}`;
20173
+ case "nothing-moved":
20174
+ return `Verity: WARN \u2014 NOT BLOCKING: nothing has changed since the last verdict, so re-raising it cannot move anything forward. ${open}`;
20175
+ case "same-problem-cap":
20176
+ return `Verity: WARN \u2014 self-healing limit (${input.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${open}`;
20177
+ case "block-ceiling":
20178
+ return `Verity: WARN \u2014 ${ceiling} consecutive blocking verdicts reached; releasing the block so this cannot loop. ${open}`;
20179
+ }
20180
+ }
20181
+
19812
20182
  // src/lib/remediation-guard.ts
19813
20183
  var TOOL_CONFIG_PATTERNS = [
19814
20184
  /(^|\/)\.codacy\//,
@@ -19851,19 +20221,7 @@ function screenRemediation(fix, findingFile) {
19851
20221
 
19852
20222
  // src/commands/analyze/phases/14-render.ts
19853
20223
  function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
19854
- const metadata = response.metadata ?? {};
19855
- const intent = response.intent_alignment ?? {};
19856
- return buildAgentContext({
19857
- intentRepeat,
19858
- priorPendingFingerprints,
19859
- gateDecision: String(response.gate_decision ?? ""),
19860
- findings: response.findings ?? [],
19861
- pendingItems: response.pending_items ?? [],
19862
- reviewStatus: metadata.review_status,
19863
- coverage: metadata.coverage,
19864
- intentVerdict: intent.verdict,
19865
- intentGaps: intent.gaps
19866
- });
20224
+ return buildAgentContext(channelInputFrom(response, intentRepeat, priorPendingFingerprints));
19867
20225
  }
19868
20226
  async function render(run) {
19869
20227
  const { opts, globals } = run;
@@ -19957,38 +20315,63 @@ async function render(run) {
19957
20315
  reverify_by: response.reverify_by
19958
20316
  });
19959
20317
  const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
19960
- let capReleased = false;
20318
+ let release = null;
19961
20319
  let effectiveDecision = decision;
19962
20320
  if (decision === "FAIL") {
19963
- const blocking = (response.findings ?? []).filter((f) => {
20321
+ const findings = response.findings ?? [];
20322
+ const blocking = findings.filter((f) => {
19964
20323
  const sev = String(f.severity ?? "").toLowerCase();
19965
20324
  return sev === "critical" || sev === "high";
19966
20325
  });
19967
20326
  const fingerprint = findingsFingerprint(blocking);
19968
- const prior = readIterationState(currentCommit);
20327
+ const prior = readBlockState(currentCommit, {
20328
+ newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
20329
+ });
19969
20330
  const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
19970
- const nextIteration = sameProblem ? prior.iteration + 1 : 1;
19971
20331
  const maxIterations = parseInt(opts.maxIterations, 10);
19972
- writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
19973
- iteration = nextIteration;
19974
- if (nextIteration > maxIterations) {
19975
- capReleased = true;
20332
+ const attempts = sameProblem ? prior.attempts + 1 : 1;
20333
+ const blocks = prior.blocks + 1;
20334
+ const decisionNow = mayBlock({
20335
+ reviewedFileCount: codeDelta.files.length,
20336
+ staticFindingCount: run.staticResults?.findings?.length ?? 0,
20337
+ cycleCutFired: silenced !== null,
20338
+ attempts,
20339
+ blocks,
20340
+ maxIterations
20341
+ });
20342
+ if (decisionNow.block) {
20343
+ writeBlockState(currentCommit, { attempts, blocks, fingerprint });
20344
+ iteration = attempts;
20345
+ } else {
20346
+ release = decisionNow.release;
19976
20347
  effectiveDecision = "WARN";
19977
- logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
20348
+ logEvent("block_released", {
20349
+ reason: release,
20350
+ attempts,
20351
+ blocks,
20352
+ reviewed_files: codeDelta.files.length,
20353
+ cycle_cut: silenced,
20354
+ fingerprint
20355
+ });
19978
20356
  }
19979
20357
  }
19980
- if (capReleased) {
20358
+ if (release) {
19981
20359
  const findings = response.findings ?? [];
19982
20360
  const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
20361
+ const summary = describeRelease(release, {
20362
+ findingCount: findings.length,
20363
+ maxIterations: parseInt(opts.maxIterations, 10)
20364
+ });
19983
20365
  emitVerdict({
19984
20366
  proposed: "WARN",
19985
20367
  changed: run.changedUniverse,
19986
20368
  coverage: reviewCoverage,
19987
- 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.
19988
- ${lines.join("\n")}`,
20369
+ userSummary: lines.length > 0 ? `${summary}
20370
+ ${lines.join("\n")}` : summary,
19989
20371
  agentContext: null,
19990
20372
  silenced: true
19991
20373
  });
20374
+ return;
19992
20375
  }
19993
20376
  switch (effectiveDecision) {
19994
20377
  case "FAIL": {
@@ -20087,7 +20470,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20087
20470
  break;
20088
20471
  }
20089
20472
  case "PASS": {
20090
- writeIteration(1, currentCommit, contentHash ?? void 0);
20473
+ resetBlockState(currentCommit);
20091
20474
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20092
20475
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20093
20476
  let userSummary = response.user_summary ?? "Verity: PASS";
@@ -20108,6 +20491,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20108
20491
  break;
20109
20492
  }
20110
20493
  case "WARN": {
20494
+ if (decision !== "FAIL") resetBlockState(currentCommit);
20111
20495
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20112
20496
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20113
20497
  let userSummary = response.user_summary ?? "Verity: WARN";
@@ -20206,7 +20590,7 @@ async function runAnalyze(opts, globals) {
20206
20590
  }
20207
20591
 
20208
20592
  // src/commands/baseline.ts
20209
- var import_node_fs30 = require("node:fs");
20593
+ var import_node_fs31 = require("node:fs");
20210
20594
  function registerBaselineCommands(program2) {
20211
20595
  const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
20212
20596
  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) => {
@@ -20215,7 +20599,7 @@ function registerBaselineCommands(program2) {
20215
20599
  process.chdir(repoRoot());
20216
20600
  } catch {
20217
20601
  }
20218
- if (!(0, import_node_fs30.existsSync)(VERITY_DIR)) {
20602
+ if (!(0, import_node_fs31.existsSync)(VERITY_DIR)) {
20219
20603
  process.exit(0);
20220
20604
  }
20221
20605
  let sessionId = opts.sessionId;
@@ -20255,7 +20639,7 @@ async function readStdin() {
20255
20639
  }
20256
20640
 
20257
20641
  // src/commands/review.ts
20258
- var import_node_fs31 = require("node:fs");
20642
+ var import_node_fs32 = require("node:fs");
20259
20643
  function registerReviewCommand(program2) {
20260
20644
  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) => {
20261
20645
  const globals = program2.opts();
@@ -20274,7 +20658,7 @@ async function runReview(opts, globals) {
20274
20658
  const securityFiles = filterSecurity(allFiles);
20275
20659
  let staticResults;
20276
20660
  if (isCodacyAvailable()) {
20277
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs31.existsSync)(f) || resolveFile(f) !== null);
20661
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs32.existsSync)(f) || resolveFile(f) !== null);
20278
20662
  staticResults = runCodacyAnalysis(scannable);
20279
20663
  } else {
20280
20664
  staticResults = {
@@ -20300,10 +20684,10 @@ async function runReview(opts, globals) {
20300
20684
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
20301
20685
  specs = [];
20302
20686
  for (const p of specPaths) {
20303
- if (!(0, import_node_fs31.existsSync)(p)) continue;
20687
+ if (!(0, import_node_fs32.existsSync)(p)) continue;
20304
20688
  try {
20305
- const { readFileSync: readFileSync18 } = await import("node:fs");
20306
- const content = readFileSync18(p, "utf-8");
20689
+ const { readFileSync: readFileSync19 } = await import("node:fs");
20690
+ const content = readFileSync19(p, "utf-8");
20307
20691
  specs.push({ path: p, content: content.slice(0, 10240) });
20308
20692
  } catch {
20309
20693
  }
@@ -20360,7 +20744,7 @@ async function runReview(opts, globals) {
20360
20744
  }
20361
20745
 
20362
20746
  // src/commands/guard.ts
20363
- var import_node_fs32 = require("node:fs");
20747
+ var import_node_fs33 = require("node:fs");
20364
20748
  var import_node_path24 = require("node:path");
20365
20749
  var GUARD_BLOCK_CAP = 2;
20366
20750
  var GUARD_ITER_FILE = (0, import_node_path24.join)(VERITY_DIR, ".guard-iteration");
@@ -20427,7 +20811,7 @@ function classifyCommand2(command, on) {
20427
20811
  }
20428
20812
  function readIterMap() {
20429
20813
  try {
20430
- const raw = JSON.parse((0, import_node_fs32.readFileSync)(GUARD_ITER_FILE, "utf-8"));
20814
+ const raw = JSON.parse((0, import_node_fs33.readFileSync)(GUARD_ITER_FILE, "utf-8"));
20431
20815
  if (raw && typeof raw === "object") {
20432
20816
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
20433
20817
  return { [raw.moment]: raw.count };
@@ -20447,10 +20831,10 @@ function readIter(moment) {
20447
20831
  }
20448
20832
  function writeIter(moment, count) {
20449
20833
  try {
20450
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20834
+ (0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
20451
20835
  const map = readIterMap();
20452
20836
  map[moment] = count;
20453
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20837
+ (0, import_node_fs33.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20454
20838
  } catch {
20455
20839
  }
20456
20840
  }
@@ -20460,10 +20844,10 @@ function resetIter(moment) {
20460
20844
  if (!(moment in map)) return;
20461
20845
  delete map[moment];
20462
20846
  if (Object.keys(map).length === 0) {
20463
- if ((0, import_node_fs32.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs32.unlinkSync)(GUARD_ITER_FILE);
20847
+ if ((0, import_node_fs33.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs33.unlinkSync)(GUARD_ITER_FILE);
20464
20848
  } else {
20465
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20466
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20849
+ (0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
20850
+ (0, import_node_fs33.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20467
20851
  }
20468
20852
  } catch {
20469
20853
  }
@@ -20527,7 +20911,7 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
20527
20911
  const securityFiles = filterSecurity(files);
20528
20912
  let staticResults;
20529
20913
  if (isCodacyAvailable()) {
20530
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs32.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
20914
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs33.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
20531
20915
  staticResults = runCodacyAnalysis(scannable);
20532
20916
  } else {
20533
20917
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -20572,7 +20956,7 @@ function emitAllowNotice(userMsg, agentMsg) {
20572
20956
  async function runGuard(opts, globals) {
20573
20957
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
20574
20958
  const { command, cwd, sessionId } = await readPreToolUseStdin();
20575
- if (cwd && (0, import_node_fs32.existsSync)(cwd)) {
20959
+ if (cwd && (0, import_node_fs33.existsSync)(cwd)) {
20576
20960
  try {
20577
20961
  process.chdir(cwd);
20578
20962
  } catch {
@@ -20678,14 +21062,14 @@ function writeBlockMessage(moment, response) {
20678
21062
  }
20679
21063
 
20680
21064
  // src/commands/init.ts
20681
- var import_node_fs34 = require("node:fs");
21065
+ var import_node_fs35 = require("node:fs");
20682
21066
  var import_promises13 = require("node:fs/promises");
20683
21067
  var import_node_path26 = require("node:path");
20684
21068
  var import_node_child_process10 = require("node:child_process");
20685
21069
  var readline2 = __toESM(require("node:readline/promises"));
20686
21070
 
20687
21071
  // src/commands/migrate.ts
20688
- var import_node_fs33 = require("node:fs");
21072
+ var import_node_fs34 = require("node:fs");
20689
21073
  var import_node_path25 = require("node:path");
20690
21074
  var import_node_child_process9 = require("node:child_process");
20691
21075
 
@@ -20817,10 +21201,10 @@ async function runMigration(opts = {}) {
20817
21201
  function migrateProjectDir(root, actions) {
20818
21202
  const gateDir = (0, import_node_path25.join)(root, ".gate");
20819
21203
  const verityDir = (0, import_node_path25.join)(root, ".verity");
20820
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) {
21204
+ if ((0, import_node_fs34.existsSync)(gateDir) && !(0, import_node_fs34.existsSync)(verityDir)) {
20821
21205
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
20822
21206
  }
20823
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21207
+ if ((0, import_node_fs34.existsSync)(gateDir) && (0, import_node_fs34.existsSync)(verityDir)) {
20824
21208
  return migrateProjectDirCarry(gateDir, verityDir, actions);
20825
21209
  }
20826
21210
  return false;
@@ -20841,13 +21225,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
20841
21225
  }
20842
21226
  }
20843
21227
  if (moved) {
20844
- if ((0, import_node_fs33.existsSync)(gateDir)) {
21228
+ if ((0, import_node_fs34.existsSync)(gateDir)) {
20845
21229
  const carried = carryLegacyContents(gateDir, verityDir);
20846
21230
  if (carried > 0) {
20847
21231
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
20848
21232
  }
20849
21233
  try {
20850
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21234
+ (0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
20851
21235
  } catch {
20852
21236
  }
20853
21237
  }
@@ -20863,7 +21247,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
20863
21247
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
20864
21248
  }
20865
21249
  try {
20866
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21250
+ (0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
20867
21251
  } catch {
20868
21252
  }
20869
21253
  return carried > 0;
@@ -20872,9 +21256,9 @@ function migrateGlobalCredentials(home, actions) {
20872
21256
  if (!home) return;
20873
21257
  const gateCreds = (0, import_node_path25.join)(home, ".gate", "credentials");
20874
21258
  const verityCreds = (0, import_node_path25.join)(home, ".verity", "credentials");
20875
- if (!(0, import_node_fs33.existsSync)(gateCreds)) return;
20876
- if (!(0, import_node_fs33.existsSync)(verityCreds)) {
20877
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.join)(home, ".verity"), { recursive: true });
21259
+ if (!(0, import_node_fs34.existsSync)(gateCreds)) return;
21260
+ if (!(0, import_node_fs34.existsSync)(verityCreds)) {
21261
+ (0, import_node_fs34.mkdirSync)((0, import_node_path25.join)(home, ".verity"), { recursive: true });
20878
21262
  moveFile(gateCreds, verityCreds);
20879
21263
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
20880
21264
  return;
@@ -20897,7 +21281,7 @@ async function migrateLegacyHooks(root, actions) {
20897
21281
  }
20898
21282
  async function migrateClaudeMd(root, actions) {
20899
21283
  const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
20900
- const hadLegacyBlock = (0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21284
+ const hadLegacyBlock = (0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
20901
21285
  if (!hadLegacyBlock) return;
20902
21286
  try {
20903
21287
  await ensureClaudeMdPointer(root);
@@ -20909,7 +21293,7 @@ async function migrateClaudeMd(root, actions) {
20909
21293
  function migrateStandardFile(root, actions) {
20910
21294
  const gateMd = (0, import_node_path25.join)(root, "GATE.md");
20911
21295
  const verityMd = (0, import_node_path25.join)(root, "VERITY.md");
20912
- if (!(0, import_node_fs33.existsSync)(gateMd) || (0, import_node_fs33.existsSync)(verityMd)) return;
21296
+ if (!(0, import_node_fs34.existsSync)(gateMd) || (0, import_node_fs34.existsSync)(verityMd)) return;
20913
21297
  let moved = false;
20914
21298
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
20915
21299
  try {
@@ -20921,12 +21305,12 @@ function migrateStandardFile(root, actions) {
20921
21305
  if (!moved) moveFile(gateMd, verityMd);
20922
21306
  const content = readFileSyncSafe(verityMd);
20923
21307
  const refreshed = content.split("GATE.md").join("VERITY.md");
20924
- if (refreshed !== content) (0, import_node_fs33.writeFileSync)(verityMd, refreshed);
21308
+ if (refreshed !== content) (0, import_node_fs34.writeFileSync)(verityMd, refreshed);
20925
21309
  actions.push("Renamed GATE.md \u2192 VERITY.md");
20926
21310
  }
20927
21311
  async function migrateTelemetryHeaders(root, actions) {
20928
21312
  const file = (0, import_node_path25.join)(root, ".claude", "settings.local.json");
20929
- if (!(0, import_node_fs33.existsSync)(file)) return;
21313
+ if (!(0, import_node_fs34.existsSync)(file)) return;
20930
21314
  let settings;
20931
21315
  try {
20932
21316
  settings = JSON.parse(readFileSyncSafe(file) || "{}");
@@ -20974,14 +21358,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
20974
21358
  }
20975
21359
  if (toAppend.length > 0) {
20976
21360
  const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
20977
- (0, import_node_fs33.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
21361
+ (0, import_node_fs34.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
20978
21362
  }
20979
- (0, import_node_fs33.rmSync)(gateCreds, { force: true });
21363
+ (0, import_node_fs34.rmSync)(gateCreds, { force: true });
20980
21364
  return toAppend.length;
20981
21365
  }
20982
21366
  function readFileSyncSafe(path) {
20983
21367
  try {
20984
- return (0, import_node_fs33.readFileSync)(path, "utf-8");
21368
+ return (0, import_node_fs34.readFileSync)(path, "utf-8");
20985
21369
  } catch {
20986
21370
  return "";
20987
21371
  }
@@ -20996,35 +21380,35 @@ function hasStagedChanges(root) {
20996
21380
  }
20997
21381
  function moveDir(from, to) {
20998
21382
  try {
20999
- (0, import_node_fs33.renameSync)(from, to);
21383
+ (0, import_node_fs34.renameSync)(from, to);
21000
21384
  } catch (err) {
21001
21385
  if (err.code !== "EXDEV") throw err;
21002
- (0, import_node_fs33.cpSync)(from, to, { recursive: true });
21003
- (0, import_node_fs33.rmSync)(from, { recursive: true, force: true });
21386
+ (0, import_node_fs34.cpSync)(from, to, { recursive: true });
21387
+ (0, import_node_fs34.rmSync)(from, { recursive: true, force: true });
21004
21388
  }
21005
21389
  }
21006
21390
  function moveFile(from, to) {
21007
21391
  try {
21008
- (0, import_node_fs33.renameSync)(from, to);
21392
+ (0, import_node_fs34.renameSync)(from, to);
21009
21393
  } catch (err) {
21010
21394
  if (err.code !== "EXDEV") throw err;
21011
- (0, import_node_fs33.cpSync)(from, to);
21012
- (0, import_node_fs33.rmSync)(from, { force: true });
21395
+ (0, import_node_fs34.cpSync)(from, to);
21396
+ (0, import_node_fs34.rmSync)(from, { force: true });
21013
21397
  }
21014
21398
  }
21015
21399
  function carryLegacyContents(gateDir, verityDir) {
21016
21400
  let copied = 0;
21017
21401
  const walk = (relDir) => {
21018
21402
  const srcDir = (0, import_node_path25.join)(gateDir, relDir);
21019
- for (const entry of (0, import_node_fs33.readdirSync)(srcDir)) {
21403
+ for (const entry of (0, import_node_fs34.readdirSync)(srcDir)) {
21020
21404
  const rel = relDir ? (0, import_node_path25.join)(relDir, entry) : entry;
21021
21405
  const src = (0, import_node_path25.join)(gateDir, rel);
21022
21406
  const dest = (0, import_node_path25.join)(verityDir, rel);
21023
- if ((0, import_node_fs33.statSync)(src).isDirectory()) {
21407
+ if ((0, import_node_fs34.statSync)(src).isDirectory()) {
21024
21408
  walk(rel);
21025
- } else if (!(0, import_node_fs33.existsSync)(dest)) {
21026
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
21027
- (0, import_node_fs33.cpSync)(src, dest);
21409
+ } else if (!(0, import_node_fs34.existsSync)(dest)) {
21410
+ (0, import_node_fs34.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
21411
+ (0, import_node_fs34.cpSync)(src, dest);
21028
21412
  copied++;
21029
21413
  }
21030
21414
  }
@@ -21035,20 +21419,20 @@ function carryLegacyContents(gateDir, verityDir) {
21035
21419
  async function needsMigration(root = repoRoot()) {
21036
21420
  const gateDir = (0, import_node_path25.join)(root, ".gate");
21037
21421
  const verityDir = (0, import_node_path25.join)(root, ".verity");
21038
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) return true;
21039
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21040
- 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"))) {
21422
+ if ((0, import_node_fs34.existsSync)(gateDir) && !(0, import_node_fs34.existsSync)(verityDir)) return true;
21423
+ if ((0, import_node_fs34.existsSync)(gateDir) && (0, import_node_fs34.existsSync)(verityDir)) {
21424
+ 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"))) {
21041
21425
  return true;
21042
21426
  }
21043
- 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"))) {
21427
+ 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"))) {
21044
21428
  return true;
21045
21429
  }
21046
21430
  }
21047
21431
  const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
21048
- if ((0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21432
+ if ((0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21049
21433
  return true;
21050
21434
  }
21051
- 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"))) {
21435
+ 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"))) {
21052
21436
  return true;
21053
21437
  }
21054
21438
  if (await hasLegacyHooksAt(root)) return true;
@@ -21190,7 +21574,7 @@ function resolveDataDir() {
21190
21574
  // local dev: running from repo root
21191
21575
  ];
21192
21576
  for (const candidate of candidates) {
21193
- if ((0, import_node_fs34.existsSync)((0, import_node_path26.join)(candidate, "skills"))) {
21577
+ if ((0, import_node_fs35.existsSync)((0, import_node_path26.join)(candidate, "skills"))) {
21194
21578
  return candidate;
21195
21579
  }
21196
21580
  }
@@ -21206,7 +21590,7 @@ function registerInitCommand(program2) {
21206
21590
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
21207
21591
  const force = opts.force ?? false;
21208
21592
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
21209
- const isProject = projectMarkers.some((m) => (0, import_node_fs34.existsSync)(m));
21593
+ const isProject = projectMarkers.some((m) => (0, import_node_fs35.existsSync)(m));
21210
21594
  if (!isProject) {
21211
21595
  printError("No project detected in the current directory.");
21212
21596
  printInfo('Run "verity init" from your project root.');
@@ -21276,14 +21660,14 @@ function registerInitCommand(program2) {
21276
21660
  for (const skill of skills) {
21277
21661
  const src = (0, import_node_path26.join)(skillsSource, skill);
21278
21662
  const dest = (0, import_node_path26.join)(skillsDest, skill);
21279
- if (!(0, import_node_fs34.existsSync)(src)) {
21663
+ if (!(0, import_node_fs35.existsSync)(src)) {
21280
21664
  printWarn(` Skill data not found: ${skill}`);
21281
21665
  continue;
21282
21666
  }
21283
- if ((0, import_node_fs34.existsSync)(dest) && !force) {
21667
+ if ((0, import_node_fs35.existsSync)(dest) && !force) {
21284
21668
  const srcSkill = (0, import_node_path26.join)(src, "SKILL.md");
21285
21669
  const destSkill = (0, import_node_path26.join)(dest, "SKILL.md");
21286
- if ((0, import_node_fs34.existsSync)(destSkill)) {
21670
+ if ((0, import_node_fs35.existsSync)(destSkill)) {
21287
21671
  try {
21288
21672
  const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
21289
21673
  const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
@@ -21355,7 +21739,7 @@ function registerInitCommand(program2) {
21355
21739
  }
21356
21740
 
21357
21741
  // src/commands/uninstall.ts
21358
- var import_node_fs35 = require("node:fs");
21742
+ var import_node_fs36 = require("node:fs");
21359
21743
  var import_node_path27 = require("node:path");
21360
21744
  var SKILL_NAMES = [
21361
21745
  "verity-setup",
@@ -21376,10 +21760,10 @@ function registerUninstallCommand(program2) {
21376
21760
  const skillsRoot = projectPath(".claude/skills");
21377
21761
  for (const name of SKILL_NAMES) {
21378
21762
  const dir = (0, import_node_path27.join)(skillsRoot, name);
21379
- if ((0, import_node_fs35.existsSync)(dir)) {
21763
+ if ((0, import_node_fs36.existsSync)(dir)) {
21380
21764
  actions.push({
21381
21765
  label: `Remove .claude/skills/${name}/`,
21382
- apply: () => (0, import_node_fs35.rmSync)(dir, { recursive: true, force: true })
21766
+ apply: () => (0, import_node_fs36.rmSync)(dir, { recursive: true, force: true })
21383
21767
  });
21384
21768
  }
21385
21769
  }
@@ -21393,24 +21777,24 @@ function registerUninstallCommand(program2) {
21393
21777
  });
21394
21778
  }
21395
21779
  const verityDir = projectPath(VERITY_DIR);
21396
- if ((0, import_node_fs35.existsSync)(verityDir)) {
21780
+ if ((0, import_node_fs36.existsSync)(verityDir)) {
21397
21781
  actions.push({
21398
21782
  label: `Remove ${VERITY_DIR}/`,
21399
- apply: () => (0, import_node_fs35.rmSync)(verityDir, { recursive: true, force: true })
21783
+ apply: () => (0, import_node_fs36.rmSync)(verityDir, { recursive: true, force: true })
21400
21784
  });
21401
21785
  }
21402
21786
  if (!keepVerityMd) {
21403
21787
  const verityMd = projectPath(VERITY_MD_FILE);
21404
- if ((0, import_node_fs35.existsSync)(verityMd)) {
21788
+ if ((0, import_node_fs36.existsSync)(verityMd)) {
21405
21789
  actions.push({
21406
21790
  label: `Remove ${VERITY_MD_FILE}`,
21407
- apply: () => (0, import_node_fs35.rmSync)(verityMd, { force: true })
21791
+ apply: () => (0, import_node_fs36.rmSync)(verityMd, { force: true })
21408
21792
  });
21409
21793
  }
21410
21794
  }
21411
21795
  const cleanupEmptyDir = (path) => {
21412
- if ((0, import_node_fs35.existsSync)(path) && (0, import_node_fs35.statSync)(path).isDirectory() && (0, import_node_fs35.readdirSync)(path).length === 0) {
21413
- (0, import_node_fs35.rmdirSync)(path);
21796
+ if ((0, import_node_fs36.existsSync)(path) && (0, import_node_fs36.statSync)(path).isDirectory() && (0, import_node_fs36.readdirSync)(path).length === 0) {
21797
+ (0, import_node_fs36.rmdirSync)(path);
21414
21798
  }
21415
21799
  };
21416
21800
  actions.push({
@@ -21422,10 +21806,10 @@ function registerUninstallCommand(program2) {
21422
21806
  });
21423
21807
  const home = process.env.HOME ?? "";
21424
21808
  const globalVerityDir = (0, import_node_path27.join)(home, ".verity");
21425
- if (purgeGlobal && (0, import_node_fs35.existsSync)(globalVerityDir)) {
21809
+ if (purgeGlobal && (0, import_node_fs36.existsSync)(globalVerityDir)) {
21426
21810
  actions.push({
21427
21811
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
21428
- apply: () => (0, import_node_fs35.rmSync)(globalVerityDir, { recursive: true, force: true })
21812
+ apply: () => (0, import_node_fs36.rmSync)(globalVerityDir, { recursive: true, force: true })
21429
21813
  });
21430
21814
  }
21431
21815
  if (actions.length === 0) {
@@ -21619,7 +22003,7 @@ function registerTaskCommands(program2) {
21619
22003
  }
21620
22004
 
21621
22005
  // src/commands/reset.ts
21622
- var import_node_fs36 = require("node:fs");
22006
+ var import_node_fs37 = require("node:fs");
21623
22007
  var import_node_path28 = require("node:path");
21624
22008
  function registerResetCommand(program2) {
21625
22009
  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) => {
@@ -21657,11 +22041,11 @@ function registerResetCommand(program2) {
21657
22041
  }
21658
22042
  const cacheDir = projectPath(CACHE_DIR);
21659
22043
  let purged = 0;
21660
- if ((0, import_node_fs36.existsSync)(cacheDir)) {
21661
- for (const entry of (0, import_node_fs36.readdirSync)(cacheDir)) {
22044
+ if ((0, import_node_fs37.existsSync)(cacheDir)) {
22045
+ for (const entry of (0, import_node_fs37.readdirSync)(cacheDir)) {
21662
22046
  if (entry.startsWith("pending-")) {
21663
22047
  try {
21664
- (0, import_node_fs36.unlinkSync)((0, import_node_path28.join)(cacheDir, entry));
22048
+ (0, import_node_fs37.unlinkSync)((0, import_node_path28.join)(cacheDir, entry));
21665
22049
  purged++;
21666
22050
  } catch {
21667
22051
  }
@@ -21676,19 +22060,19 @@ function registerResetCommand(program2) {
21676
22060
  projectPath(`${VERITY_DIR}/.last-analysis`)
21677
22061
  ];
21678
22062
  for (const file of filesToClear) {
21679
- if ((0, import_node_fs36.existsSync)(file)) {
22063
+ if ((0, import_node_fs37.existsSync)(file)) {
21680
22064
  try {
21681
- (0, import_node_fs36.writeFileSync)(file, "");
22065
+ (0, import_node_fs37.writeFileSync)(file, "");
21682
22066
  } catch {
21683
22067
  }
21684
22068
  }
21685
22069
  }
21686
22070
  if (opts.all) {
21687
22071
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
21688
- if ((0, import_node_fs36.existsSync)(logsDir)) {
21689
- for (const entry of (0, import_node_fs36.readdirSync)(logsDir)) {
22072
+ if ((0, import_node_fs37.existsSync)(logsDir)) {
22073
+ for (const entry of (0, import_node_fs37.readdirSync)(logsDir)) {
21690
22074
  try {
21691
- (0, import_node_fs36.unlinkSync)((0, import_node_path28.join)(logsDir, entry));
22075
+ (0, import_node_fs37.unlinkSync)((0, import_node_path28.join)(logsDir, entry));
21692
22076
  } catch {
21693
22077
  }
21694
22078
  }
@@ -21996,8 +22380,8 @@ function registerTelemetryCommands(program2) {
21996
22380
  }
21997
22381
 
21998
22382
  // src/cli.ts
21999
- program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.5fcba03").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) => {
22000
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.5fcba03");
22383
+ program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.6f2ede0").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) => {
22384
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.6f2ede0");
22001
22385
  setUserNamedServiceUrl(program.opts().serviceUrl);
22002
22386
  try {
22003
22387
  await foldLegacyLocalCredential();