@codacy/verity-cli 0.29.4-experimental.6726d0f → 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 +606 -161
  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);
@@ -16601,6 +16604,7 @@ function createRun(opts, globals) {
16601
16604
  urlResult: { ok: false, error: "unresolved" },
16602
16605
  serviceUrl: "",
16603
16606
  token: "",
16607
+ modeDecision: null,
16604
16608
  sessionIdForMemory: "",
16605
16609
  contextFilePaths: [],
16606
16610
  analysisMode: "standard",
@@ -16713,8 +16717,15 @@ function formatRunEvidence(run, startedAt) {
16713
16717
  out += row("changed", `${run.changedUniverse.length} from git \xB7 analyzable ${run.analyzable.length} \xB7 reviewable ${run.reviewable.length} \xB7 security ${run.securityFiles.length} \xB7 forReview ${run.allForReview.length}`);
16714
16718
  const done = (phase) => run.phasesCompleted.includes(phase);
16715
16719
  const ifDone = (phase, value) => done(phase) ? value : "?";
16716
- out += row("signals", `mode=${ifDone("mode", run.analysisMode)} \xB7 baseline=${ifDone("bootstrap", run.baseline ? "yes" : "no")} \xB7 authored=${ifDone("intentInputs", run.turnAuthoredCode ? "yes" : "no")} \xB7 observable=${ifDone("intentInputs", run.authorshipIsObservable ? "yes" : "no")}` + (run.actionSummary?.transcript_windowed ? ` \xB7 window=${run.actionSummary.transcript_windowed}` : ""));
16717
- if (!done("mode") || !done("intentInputs")) {
16720
+ const md = run.modeDecision;
16721
+ if (md) {
16722
+ const how = md.forced ? "forced by --mode" : `predicted=${md.predicted ?? "none"} \u2192 ${md.resolved}`;
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"}`);
16724
+ } else {
16725
+ out += row("mode", `? (this run stopped in ${run.phaseReached || "no phase"}, before the mode was decided)`);
16726
+ }
16727
+ out += row("signals", `baseline=${ifDone("bootstrap", run.baseline ? "yes" : "no")} \xB7 authored=${ifDone("intentInputs", run.turnAuthoredCode ? "yes" : "no")} \xB7 observable=${ifDone("intentInputs", run.authorshipIsObservable ? "yes" : "no")}` + (run.actionSummary?.transcript_windowed ? ` \xB7 window=${run.actionSummary.transcript_windowed}` : ""));
16728
+ if (!done("intentInputs")) {
16718
16729
  out += row("", `(\`?\` = the phase that determines it did not complete \u2014 this run stopped in ${run.phaseReached})`);
16719
16730
  }
16720
16731
  out += row("sent", `${sent.length} \xB7 ${list(sent)}`);
@@ -16741,6 +16752,46 @@ function formatRunEvidence(run, startedAt) {
16741
16752
  out += row("withheld", "(nothing \u2014 every changed file was reviewed)");
16742
16753
  }
16743
16754
  }
16755
+ const cov = run.foldResult?.coverage;
16756
+ if (cov) {
16757
+ const delegated = run.foldResult.authored.filter((a) => a.owner === "subagent").length;
16758
+ if (cov.dispatched > 0 || cov.subagentFiles > 0 || cov.subagentSkipped > 0) {
16759
+ out += row("delegated", `${cov.dispatched} dispatched \xB7 ${cov.subagentFiles} agent log(s) read \xB7 ${delegated} path(s) attributed to subagents`);
16760
+ }
16761
+ if (cov.subagentSkipped > 0) {
16762
+ out += row("", `\u26A0 ${cov.subagentSkipped} agent log(s) REFUSED by the byte budget \u2014 the authored set above is PARTIAL`);
16763
+ }
16764
+ if (cov.dispatched > 0 && cov.subagentFiles === 0) {
16765
+ out += row("", `\u26A0 this turn dispatched ${cov.dispatched} agent(s) but no agent log was found \u2014 delegated work is unattributed (has the transcript layout moved?)`);
16766
+ }
16767
+ if (cov.outsideRepo > 0) {
16768
+ out += row("", `${cov.outsideRepo} authored path(s) refused as outside the repo`);
16769
+ }
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
+ }
16744
16795
  if (run.staticResults.findings.length > 0) {
16745
16796
  out += row("static", `${run.staticResults.findings.length} finding(s) from ${run.staticResults.summary.tools_run.join(", ") || "no tools"}`);
16746
16797
  }
@@ -16936,6 +16987,8 @@ function buildSummary(lines) {
16936
16987
  break;
16937
16988
  case "Agent":
16938
16989
  case "Task":
16990
+ case "Workflow":
16991
+ case "SendMessage":
16939
16992
  subagents++;
16940
16993
  break;
16941
16994
  case "WebFetch":
@@ -17096,6 +17149,39 @@ async function bootstrap(run) {
17096
17149
  Object.assign(run, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
17097
17150
  }
17098
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
+
17099
17185
  // src/lib/channel.ts
17100
17186
  var MAX_AGENT_CONTEXT_CHARS = 1500;
17101
17187
  var MAX_AGENT_ITEMS = 5;
@@ -17105,6 +17191,29 @@ function renderItem(label2, text, patternId, file, line) {
17105
17191
  const id = patternId ? ` [${patternId}]` : "";
17106
17192
  return `- ${label2}${text}${where}${id}`;
17107
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
+ }
17108
17217
  function buildAgentContext(input) {
17109
17218
  const lines = [];
17110
17219
  if (input.reviewStatus === "not_reviewed") {
@@ -17190,7 +17299,7 @@ function channelSilence(input) {
17190
17299
  // src/lib/cli-version.ts
17191
17300
  function cliVersion() {
17192
17301
  try {
17193
- return true ? "0.29.4-experimental.6726d0f" : "dev";
17302
+ return true ? "0.29.4-experimental.6f2ede0" : "dev";
17194
17303
  } catch {
17195
17304
  return "dev";
17196
17305
  }
@@ -17512,9 +17621,10 @@ async function scope(run) {
17512
17621
  const { assistantResponse } = run;
17513
17622
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
17514
17623
  run.changedUniverse = allChanged;
17515
- const analyzable = filterAnalyzable(allChanged);
17516
- const reviewable = filterReviewable(allChanged);
17517
- 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);
17518
17628
  const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
17519
17629
  if (noFilesChanged && !assistantResponse) {
17520
17630
  await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
@@ -17541,18 +17651,26 @@ var SPEC_CANDIDATES = [
17541
17651
  "docs/API.md",
17542
17652
  "spec/ARCHITECTURE.md"
17543
17653
  ];
17544
- 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 = []) {
17545
17658
  const result = [];
17546
17659
  const seen = /* @__PURE__ */ new Set();
17547
17660
  let totalBytes = 0;
17548
- 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) => {
17549
17665
  if (result.length >= MAX_SPEC_FILES) return false;
17550
- 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;
17551
17668
  if (seen.has(specPath)) return true;
17552
17669
  if (!(0, import_node_fs21.existsSync)(specPath)) return true;
17553
17670
  seen.add(specPath);
17554
- const remaining = MAX_TOTAL_SPEC_BYTES - totalBytes;
17555
- 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);
17556
17674
  try {
17557
17675
  const buf = Buffer.alloc(readBytes);
17558
17676
  const fd = (0, import_node_fs21.openSync)(specPath, "r");
@@ -17566,6 +17684,9 @@ function discoverSpecs() {
17566
17684
  }
17567
17685
  return true;
17568
17686
  };
17687
+ for (const doc of consultedDocs) {
17688
+ if (!addSpec(doc, true)) break;
17689
+ }
17569
17690
  for (const candidate of SPEC_CANDIDATES) {
17570
17691
  if (!addSpec(candidate)) break;
17571
17692
  }
@@ -17636,7 +17757,7 @@ function discoverPlans() {
17636
17757
  async function intentInputs(run) {
17637
17758
  const { actionSummary, allForReview, assistantResponse, baseline, baselineSessionId } = run;
17638
17759
  const conversation = await readAndClearConversationBuffer(baselineSessionId);
17639
- const specs = discoverSpecs();
17760
+ const specs = discoverSpecs(actionSummary?.files_read ?? []);
17640
17761
  const plans = discoverPlans();
17641
17762
  const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
17642
17763
  const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
@@ -17859,7 +17980,8 @@ async function mode(run) {
17859
17980
  let analysisMode;
17860
17981
  const sessionAuthoredCode = !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
17861
17982
  const modeOverride = opts.mode;
17862
- if (modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride)) {
17983
+ const forced = !!modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride);
17984
+ if (forced) {
17863
17985
  analysisMode = modeOverride;
17864
17986
  } else {
17865
17987
  analysisMode = reconcileAnalysisMode(
@@ -17867,6 +17989,25 @@ async function mode(run) {
17867
17989
  { noFilesChanged, assistantResponse, actionSummary, conversationPrompts, sessionAuthoredCode }
17868
17990
  );
17869
17991
  }
17992
+ const investigated = didAgentInvestigate(actionSummary);
17993
+ run.modeDecision = {
17994
+ predicted: predictedMode ?? null,
17995
+ resolved: analysisMode,
17996
+ authored: turnAuthoredCode,
17997
+ investigated,
17998
+ forced
17999
+ };
18000
+ logEvent("mode_resolved", {
18001
+ predicted: predictedMode ?? null,
18002
+ resolved: analysisMode,
18003
+ forced,
18004
+ authored: turnAuthoredCode,
18005
+ investigated,
18006
+ // The two counters that decide `investigated`, so a false reading is
18007
+ // traceable to the tool that was not recognised.
18008
+ subagents: actionSummary?.subagents ?? null,
18009
+ files_read: actionSummary?.files_read.length ?? null
18010
+ });
17870
18011
  if (analysisMode === "skip") {
17871
18012
  await passAndExit(
17872
18013
  run,
@@ -17976,26 +18117,50 @@ function narrowToRecent(files, sessionId) {
17976
18117
  });
17977
18118
  return recent.length > 0 ? recent : files;
17978
18119
  }
17979
- function readIterationState(currentCommit) {
17980
- 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;
17981
18127
  try {
17982
18128
  const stored = (0, import_node_fs22.readFileSync)(ITERATION_FILE, "utf-8").trim();
17983
- const parts = stored.split(":");
17984
- const iter = parseInt(parts[0], 10);
17985
- const storedCommit = parts[1] ?? "";
17986
- const storedTimestamp = parseInt(parts[2] ?? "0", 10);
17987
- const fingerprint = parts.slice(3).join(":") || null;
17988
- if (isNaN(iter)) return { iteration: 1, fingerprint: null };
17989
- if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
17990
- if (storedTimestamp > 0) {
17991
- const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
17992
- if (elapsed > 600) return { iteration: 1, fingerprint: null };
17993
- }
17994
- 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 };
17995
18134
  } catch {
17996
- return { iteration: 1, fingerprint: null };
18135
+ return NO_BLOCKS;
17997
18136
  }
17998
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
+ }
17999
18164
  function findingsFingerprint(findings) {
18000
18165
  const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
18001
18166
  return [...new Set(keys)].sort().join(",");
@@ -18005,11 +18170,22 @@ function isSameProblem(previous, current) {
18005
18170
  const prev = new Set(previous.split(","));
18006
18171
  return current.split(",").some((k) => prev.has(k));
18007
18172
  }
18008
- function writeIteration(iteration, commit, _contentHash, fingerprint) {
18173
+ function writeBlockState(commit, state) {
18009
18174
  (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
18010
- const ts = Math.floor(Date.now() / 1e3);
18011
- const fp = fingerprint ? `:${fingerprint}` : "";
18012
- (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 });
18013
18189
  }
18014
18190
 
18015
18191
  // src/lib/fold.ts
@@ -18132,6 +18308,18 @@ function commandShape(cmd) {
18132
18308
  return out.join(" ").slice(0, COMMAND_HEAD_CHARS);
18133
18309
  }
18134
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
+ }
18135
18323
  var rootCandidateCache = /* @__PURE__ */ new Map();
18136
18324
  function candidateRoots(repoRoot2) {
18137
18325
  const cached2 = rootCandidateCache.get(repoRoot2);
@@ -18160,27 +18348,46 @@ function toRepoRelative(path, repoRoot2) {
18160
18348
  }
18161
18349
  return p.replace(/^\/+/, "");
18162
18350
  }
18351
+ function isInsideRepo(path, repoRoot2) {
18352
+ const p = path.replace(/\\/g, "/");
18353
+ if (!isAbsolutePath(p)) return true;
18354
+ if (!repoRoot2) return true;
18355
+ return candidateRoots(repoRoot2).some((root) => p === root || p.startsWith(root + "/"));
18356
+ }
18357
+ function isAbsolutePath(p) {
18358
+ return p.startsWith("/") || /^[A-Za-z]:\//.test(p);
18359
+ }
18163
18360
  function fold(transcriptPath, opts = {}) {
18164
18361
  const result = {
18165
18362
  authored: [],
18166
18363
  unobserved: [],
18167
18364
  commands: [],
18365
+ tools: [],
18366
+ tasks: [],
18168
18367
  unknownTypes: [],
18169
18368
  coverage: {
18170
18369
  recordCounts: {},
18171
18370
  totalRecords: 0,
18172
18371
  malformed: 0,
18173
18372
  subagentFiles: 0,
18373
+ outsideRepo: 0,
18374
+ toolNamesDropped: 0,
18174
18375
  dispatched: 0,
18175
18376
  userMessages: 0,
18176
18377
  subagentSkipped: 0,
18177
18378
  compactions: 0,
18178
18379
  complete: false
18179
- }
18380
+ },
18381
+ planApproval: { approvals: 0, activeSinceLastPrompt: false }
18180
18382
  };
18383
+ const flow = { seq: 0, lastPrompt: -1, lastApproval: -1, approvals: 0 };
18181
18384
  const byPath = /* @__PURE__ */ new Map();
18182
18385
  const commandStats = /* @__PURE__ */ new Map();
18183
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();
18184
18391
  const unknown = /* @__PURE__ */ new Set();
18185
18392
  const ingest = (raw, owner) => {
18186
18393
  for (const line of raw.split("\n")) {
@@ -18199,8 +18406,12 @@ function fold(transcriptPath, opts = {}) {
18199
18406
  if (type === "system" && record.subtype === "compact_boundary") {
18200
18407
  result.coverage.compactions++;
18201
18408
  }
18202
- if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
18203
- 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);
18204
18415
  }
18205
18416
  };
18206
18417
  try {
@@ -18211,7 +18422,11 @@ function fold(transcriptPath, opts = {}) {
18211
18422
  return result;
18212
18423
  }
18213
18424
  try {
18214
- const sidecarDir = (0, import_node_path18.join)((0, import_node_path18.dirname)(transcriptPath), "subagents");
18425
+ const sidecarDir = (0, import_node_path18.join)(
18426
+ (0, import_node_path18.dirname)(transcriptPath),
18427
+ (0, import_node_path18.basename)(transcriptPath).replace(/\.jsonl$/, ""),
18428
+ "subagents"
18429
+ );
18215
18430
  if ((0, import_node_fs23.existsSync)(sidecarDir)) {
18216
18431
  const maxFiles = opts.maxSidecars ?? 200;
18217
18432
  const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
@@ -18254,12 +18469,24 @@ function fold(transcriptPath, opts = {}) {
18254
18469
  result.authored = [...byPath.values()].sort((a, b) => a.p.localeCompare(b.p));
18255
18470
  result.unknownTypes = [...unknown].sort();
18256
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));
18257
18480
  const authoredPaths = new Set(result.authored.map((a) => a.p));
18258
18481
  for (const raw of opts.changedFiles ?? []) {
18259
18482
  const p = toRepoRelative(raw, opts.repoRoot);
18260
18483
  if (!p || authoredPaths.has(p)) continue;
18261
18484
  result.unobserved.push({ p, cause: classifyUnobserved(raw) });
18262
18485
  }
18486
+ result.planApproval = {
18487
+ approvals: flow.approvals,
18488
+ activeSinceLastPrompt: flow.lastApproval >= 0 && flow.lastApproval > flow.lastPrompt
18489
+ };
18263
18490
  return result;
18264
18491
  }
18265
18492
  function classifyUnobserved(path) {
@@ -18272,7 +18499,7 @@ function classifyUnobserved(path) {
18272
18499
  if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
18273
18500
  return "no_edit_record";
18274
18501
  }
18275
- function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2, tally) {
18502
+ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally, flow) {
18276
18503
  const message = record.message;
18277
18504
  const content = message?.content ?? record.content;
18278
18505
  const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
@@ -18281,8 +18508,38 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18281
18508
  if (blockType === "tool_use") {
18282
18509
  const name = String(block.name ?? "");
18283
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
+ }
18284
18537
  if (EDIT_TOOLS.has(name)) {
18285
18538
  const rawPath = typeof input.notebook_path === "string" ? input.notebook_path : typeof input.file_path === "string" ? input.file_path : null;
18539
+ if (rawPath && !isInsideRepo(rawPath, repoRoot2)) {
18540
+ if (tally) tally.outsideRepo += 1;
18541
+ continue;
18542
+ }
18286
18543
  const path = rawPath ? toRepoRelative(rawPath, repoRoot2) : null;
18287
18544
  if (path) {
18288
18545
  const entry = byPath.get(path) ?? { p: path, h: 0, a: 0, d: 0, owner };
@@ -18316,6 +18573,38 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18316
18573
  }
18317
18574
  if (blockType === "tool_result") {
18318
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
+ }
18319
18608
  const cls = id ? pendingByToolUse.get(id) : void 0;
18320
18609
  if (!cls) continue;
18321
18610
  pendingByToolUse.delete(id);
@@ -18358,8 +18647,13 @@ function checkConservation(changedFiles, result, repoRoot2) {
18358
18647
  // src/commands/analyze/phases/06-evidence.ts
18359
18648
  async function evidence(run) {
18360
18649
  const { opts } = run;
18361
- 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;
18362
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;
18363
18657
  let staticResults = {
18364
18658
  tool: "@codacy/analysis-cli",
18365
18659
  findings: [],
@@ -18379,8 +18673,9 @@ async function evidence(run) {
18379
18673
  const debounceSeconds = parseInt(opts.debounce, 10);
18380
18674
  const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
18381
18675
  if (debounceSkip) {
18382
- if (assistantResponse) {
18676
+ if (planWorthy) {
18383
18677
  analysisMode = "plan";
18678
+ recordFlip("debounce");
18384
18679
  } else {
18385
18680
  await passAndExit(run, debounceSkip, "debounce");
18386
18681
  }
@@ -18389,8 +18684,9 @@ async function evidence(run) {
18389
18684
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18390
18685
  const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
18391
18686
  if (mtimeSkip) {
18392
- if (assistantResponse) {
18687
+ if (planWorthy) {
18393
18688
  analysisMode = "plan";
18689
+ recordFlip("mtime");
18394
18690
  } else {
18395
18691
  await passAndExit(run, mtimeSkip, "no-delta-since-last-review");
18396
18692
  }
@@ -18401,8 +18697,9 @@ async function evidence(run) {
18401
18697
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18402
18698
  const hashResult = checkContentHash(allCheckable, baselineSessionId);
18403
18699
  if (hashResult.skip) {
18404
- if (assistantResponse) {
18700
+ if (planWorthy) {
18405
18701
  analysisMode = "plan";
18702
+ recordFlip("content-hash");
18406
18703
  } else {
18407
18704
  await passAndExit(run, hashResult.skip, "no-delta-since-last-review");
18408
18705
  }
@@ -18464,8 +18761,9 @@ async function evidence(run) {
18464
18761
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
18465
18762
  });
18466
18763
  if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
18467
- if (assistantResponse) {
18764
+ if (planWorthy) {
18468
18765
  analysisMode = "plan";
18766
+ recordFlip("empty-after-scoping");
18469
18767
  } else {
18470
18768
  await passAndExit(
18471
18769
  run,
@@ -18485,13 +18783,13 @@ async function evidence(run) {
18485
18783
  snapshotResult = generateSnapshotDiffs(codeDelta.files);
18486
18784
  }
18487
18785
  currentCommit = getCurrentCommit();
18488
- iteration = readIterationState(currentCommit).iteration;
18786
+ iteration = readIteration(currentCommit);
18489
18787
  }
18490
18788
  }
18491
18789
  if (analysisMode === "plan") {
18492
18790
  recordAnalysisStart();
18493
18791
  currentCommit = getCurrentCommit();
18494
- iteration = readIterationState(currentCommit).iteration;
18792
+ iteration = readIteration(currentCommit);
18495
18793
  }
18496
18794
  Object.assign(run, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
18497
18795
  }
@@ -18587,7 +18885,8 @@ function gatherContextFiles(contextPaths, deltaFiles) {
18587
18885
  // src/commands/analyze/phases/07-context-files.ts
18588
18886
  async function contextFiles(run) {
18589
18887
  const { codeDelta, contextFilePaths } = run;
18590
- const contextFiles2 = gatherContextFiles(contextFilePaths, codeDelta.files);
18888
+ const { kept: externalContext } = partitionVerityOwned(contextFilePaths ?? []);
18889
+ const contextFiles2 = gatherContextFiles(externalContext, codeDelta.files);
18591
18890
  for (const f of codeDelta.files) {
18592
18891
  f.role = "delta";
18593
18892
  }
@@ -19157,6 +19456,54 @@ async function workingMemory(run) {
19157
19456
  Object.assign(run, { incrementReport, memory, memorySession, reachability });
19158
19457
  }
19159
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
+
19160
19507
  // src/lib/run-mode.ts
19161
19508
  function parseAutonomousEnv(raw) {
19162
19509
  if (raw === void 0) return void 0;
@@ -19250,7 +19597,13 @@ async function buildRequest(run) {
19250
19597
  excluded_by_reason: excludedByReason,
19251
19598
  // was the transcript itself truncated? The 256 KB window means "this turn"
19252
19599
  // can quietly mean "the last 256 KB of it".
19253
- 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
19254
19607
  };
19255
19608
  const requestBody = {
19256
19609
  coverage_telemetry: coverageTelemetry,
@@ -19309,6 +19662,8 @@ async function buildRequest(run) {
19309
19662
  authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
19310
19663
  unobserved: foldResult?.unobserved ?? [],
19311
19664
  commands: foldResult?.commands ?? [],
19665
+ tools: foldResult?.tools ?? [],
19666
+ tasks: foldResult?.tasks ?? [],
19312
19667
  unknown_types: foldResult?.unknownTypes ?? [],
19313
19668
  coverage: foldResult?.coverage ?? { recordCounts: {}, totalRecords: 0, malformed: 0, subagentFiles: 0, subagentSkipped: 0, complete: false },
19314
19669
  // INV-19, computed client-side and sent so a conservation failure is
@@ -19387,7 +19742,8 @@ async function buildRequest(run) {
19387
19742
  }
19388
19743
  const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
19389
19744
  const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
19390
- 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;
19391
19747
  if (hasIntent) {
19392
19748
  const intentContext = {};
19393
19749
  if (conversation && conversation.prompts.length > 0) {
@@ -19419,6 +19775,10 @@ async function buildRequest(run) {
19419
19775
  intentContext.user_prompt = w4Task.goal;
19420
19776
  logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
19421
19777
  }
19778
+ if (planApprovalActive) {
19779
+ intentContext.plan_approved = true;
19780
+ logEvent("plan_approval_carried", { approvals: foldResult.planApproval.approvals });
19781
+ }
19422
19782
  if (assistantResponse) {
19423
19783
  const cap = analysisMode === "plan" ? MAX_ASSISTANT_RESPONSE_CHARS_PLAN : MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT;
19424
19784
  intentContext.assistant_response = assistantResponse.length > cap ? assistantResponse.slice(0, cap) : assistantResponse;
@@ -19438,14 +19798,14 @@ async function buildRequest(run) {
19438
19798
  }
19439
19799
 
19440
19800
  // src/lib/offline.ts
19441
- var import_node_fs28 = require("node:fs");
19801
+ var import_node_fs29 = require("node:fs");
19442
19802
  var import_node_crypto11 = require("node:crypto");
19443
19803
  function cacheRequest(body) {
19444
19804
  try {
19445
- (0, import_node_fs28.mkdirSync)(CACHE_DIR, { recursive: true });
19805
+ (0, import_node_fs29.mkdirSync)(CACHE_DIR, { recursive: true });
19446
19806
  const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
19447
19807
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
19448
- (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)));
19449
19809
  } catch {
19450
19810
  }
19451
19811
  }
@@ -19564,10 +19924,10 @@ async function transmit(run) {
19564
19924
  }
19565
19925
 
19566
19926
  // src/commands/analyze/phases/13-reconcile.ts
19567
- var import_node_fs29 = require("node:fs");
19927
+ var import_node_fs30 = require("node:fs");
19568
19928
  var import_node_path23 = require("node:path");
19569
19929
  async function reconcile(run) {
19570
- 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;
19571
19931
  const sentPaths = codeDelta.files.map((f) => f.path);
19572
19932
  let openElsewhere = [];
19573
19933
  if (memorySession) {
@@ -19575,7 +19935,7 @@ async function reconcile(run) {
19575
19935
  const st = foldDossier(memorySession.d);
19576
19936
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
19577
19937
  try {
19578
- 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");
19579
19939
  const at = src[line - 1];
19580
19940
  return at === void 0 ? null : lineSha(at);
19581
19941
  } catch {
@@ -19585,6 +19945,7 @@ async function reconcile(run) {
19585
19945
  } catch {
19586
19946
  }
19587
19947
  }
19948
+ const { kept: externalChanged, owned: verityOwned } = partitionVerityOwned(allChanged);
19588
19949
  const reviewCoverage = {
19589
19950
  reviewed: sentPaths,
19590
19951
  // Declared drops from the stages that DO report themselves today. The other
@@ -19626,11 +19987,20 @@ async function reconcile(run) {
19626
19987
  stage: "baseline-scoping",
19627
19988
  kind: "policy"
19628
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
+ })),
19629
19999
  // The extension allowlist. POLICY: a changed README was never going to be
19630
20000
  // reviewed, and calling that a coverage gap would downgrade nearly every
19631
20001
  // PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
19632
20002
  // so "what did Verity ignore entirely" is answerable.
19633
- ...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) => ({
19634
20004
  path,
19635
20005
  reason: "not-a-reviewed-file-type",
19636
20006
  stage: "extension-allowlist",
@@ -19667,6 +20037,29 @@ async function reconcile(run) {
19667
20037
  decision
19668
20038
  });
19669
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
+ );
19670
20063
  let intentRepeatCount = 0;
19671
20064
  const priorPendingFingerprints = memorySession ? (() => {
19672
20065
  try {
@@ -19682,6 +20075,11 @@ async function reconcile(run) {
19682
20075
  decision,
19683
20076
  branch: getCurrentBranch(),
19684
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,
19685
20083
  findings: response.findings?.map((f) => ({
19686
20084
  file: f.file,
19687
20085
  line: f.line,
@@ -19748,6 +20146,39 @@ ${YELLOW2}${note}${NC2}
19748
20146
  return exit(0);
19749
20147
  }
19750
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
+
19751
20182
  // src/lib/remediation-guard.ts
19752
20183
  var TOOL_CONFIG_PATTERNS = [
19753
20184
  /(^|\/)\.codacy\//,
@@ -19790,19 +20221,7 @@ function screenRemediation(fix, findingFile) {
19790
20221
 
19791
20222
  // src/commands/analyze/phases/14-render.ts
19792
20223
  function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
19793
- const metadata = response.metadata ?? {};
19794
- const intent = response.intent_alignment ?? {};
19795
- return buildAgentContext({
19796
- intentRepeat,
19797
- priorPendingFingerprints,
19798
- gateDecision: String(response.gate_decision ?? ""),
19799
- findings: response.findings ?? [],
19800
- pendingItems: response.pending_items ?? [],
19801
- reviewStatus: metadata.review_status,
19802
- coverage: metadata.coverage,
19803
- intentVerdict: intent.verdict,
19804
- intentGaps: intent.gaps
19805
- });
20224
+ return buildAgentContext(channelInputFrom(response, intentRepeat, priorPendingFingerprints));
19806
20225
  }
19807
20226
  async function render(run) {
19808
20227
  const { opts, globals } = run;
@@ -19896,38 +20315,63 @@ async function render(run) {
19896
20315
  reverify_by: response.reverify_by
19897
20316
  });
19898
20317
  const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
19899
- let capReleased = false;
20318
+ let release = null;
19900
20319
  let effectiveDecision = decision;
19901
20320
  if (decision === "FAIL") {
19902
- const blocking = (response.findings ?? []).filter((f) => {
20321
+ const findings = response.findings ?? [];
20322
+ const blocking = findings.filter((f) => {
19903
20323
  const sev = String(f.severity ?? "").toLowerCase();
19904
20324
  return sev === "critical" || sev === "high";
19905
20325
  });
19906
20326
  const fingerprint = findingsFingerprint(blocking);
19907
- const prior = readIterationState(currentCommit);
20327
+ const prior = readBlockState(currentCommit, {
20328
+ newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
20329
+ });
19908
20330
  const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
19909
- const nextIteration = sameProblem ? prior.iteration + 1 : 1;
19910
20331
  const maxIterations = parseInt(opts.maxIterations, 10);
19911
- writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
19912
- iteration = nextIteration;
19913
- if (nextIteration > maxIterations) {
19914
- 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;
19915
20347
  effectiveDecision = "WARN";
19916
- 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
+ });
19917
20356
  }
19918
20357
  }
19919
- if (capReleased) {
20358
+ if (release) {
19920
20359
  const findings = response.findings ?? [];
19921
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
+ });
19922
20365
  emitVerdict({
19923
20366
  proposed: "WARN",
19924
20367
  changed: run.changedUniverse,
19925
20368
  coverage: reviewCoverage,
19926
- 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.
19927
- ${lines.join("\n")}`,
20369
+ userSummary: lines.length > 0 ? `${summary}
20370
+ ${lines.join("\n")}` : summary,
19928
20371
  agentContext: null,
19929
20372
  silenced: true
19930
20373
  });
20374
+ return;
19931
20375
  }
19932
20376
  switch (effectiveDecision) {
19933
20377
  case "FAIL": {
@@ -20026,7 +20470,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20026
20470
  break;
20027
20471
  }
20028
20472
  case "PASS": {
20029
- writeIteration(1, currentCommit, contentHash ?? void 0);
20473
+ resetBlockState(currentCommit);
20030
20474
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20031
20475
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20032
20476
  let userSummary = response.user_summary ?? "Verity: PASS";
@@ -20047,6 +20491,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20047
20491
  break;
20048
20492
  }
20049
20493
  case "WARN": {
20494
+ if (decision !== "FAIL") resetBlockState(currentCommit);
20050
20495
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20051
20496
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20052
20497
  let userSummary = response.user_summary ?? "Verity: WARN";
@@ -20145,7 +20590,7 @@ async function runAnalyze(opts, globals) {
20145
20590
  }
20146
20591
 
20147
20592
  // src/commands/baseline.ts
20148
- var import_node_fs30 = require("node:fs");
20593
+ var import_node_fs31 = require("node:fs");
20149
20594
  function registerBaselineCommands(program2) {
20150
20595
  const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
20151
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) => {
@@ -20154,7 +20599,7 @@ function registerBaselineCommands(program2) {
20154
20599
  process.chdir(repoRoot());
20155
20600
  } catch {
20156
20601
  }
20157
- if (!(0, import_node_fs30.existsSync)(VERITY_DIR)) {
20602
+ if (!(0, import_node_fs31.existsSync)(VERITY_DIR)) {
20158
20603
  process.exit(0);
20159
20604
  }
20160
20605
  let sessionId = opts.sessionId;
@@ -20194,7 +20639,7 @@ async function readStdin() {
20194
20639
  }
20195
20640
 
20196
20641
  // src/commands/review.ts
20197
- var import_node_fs31 = require("node:fs");
20642
+ var import_node_fs32 = require("node:fs");
20198
20643
  function registerReviewCommand(program2) {
20199
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) => {
20200
20645
  const globals = program2.opts();
@@ -20213,7 +20658,7 @@ async function runReview(opts, globals) {
20213
20658
  const securityFiles = filterSecurity(allFiles);
20214
20659
  let staticResults;
20215
20660
  if (isCodacyAvailable()) {
20216
- 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);
20217
20662
  staticResults = runCodacyAnalysis(scannable);
20218
20663
  } else {
20219
20664
  staticResults = {
@@ -20239,10 +20684,10 @@ async function runReview(opts, globals) {
20239
20684
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
20240
20685
  specs = [];
20241
20686
  for (const p of specPaths) {
20242
- if (!(0, import_node_fs31.existsSync)(p)) continue;
20687
+ if (!(0, import_node_fs32.existsSync)(p)) continue;
20243
20688
  try {
20244
- const { readFileSync: readFileSync18 } = await import("node:fs");
20245
- const content = readFileSync18(p, "utf-8");
20689
+ const { readFileSync: readFileSync19 } = await import("node:fs");
20690
+ const content = readFileSync19(p, "utf-8");
20246
20691
  specs.push({ path: p, content: content.slice(0, 10240) });
20247
20692
  } catch {
20248
20693
  }
@@ -20299,7 +20744,7 @@ async function runReview(opts, globals) {
20299
20744
  }
20300
20745
 
20301
20746
  // src/commands/guard.ts
20302
- var import_node_fs32 = require("node:fs");
20747
+ var import_node_fs33 = require("node:fs");
20303
20748
  var import_node_path24 = require("node:path");
20304
20749
  var GUARD_BLOCK_CAP = 2;
20305
20750
  var GUARD_ITER_FILE = (0, import_node_path24.join)(VERITY_DIR, ".guard-iteration");
@@ -20366,7 +20811,7 @@ function classifyCommand2(command, on) {
20366
20811
  }
20367
20812
  function readIterMap() {
20368
20813
  try {
20369
- 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"));
20370
20815
  if (raw && typeof raw === "object") {
20371
20816
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
20372
20817
  return { [raw.moment]: raw.count };
@@ -20386,10 +20831,10 @@ function readIter(moment) {
20386
20831
  }
20387
20832
  function writeIter(moment, count) {
20388
20833
  try {
20389
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20834
+ (0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
20390
20835
  const map = readIterMap();
20391
20836
  map[moment] = count;
20392
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20837
+ (0, import_node_fs33.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20393
20838
  } catch {
20394
20839
  }
20395
20840
  }
@@ -20399,10 +20844,10 @@ function resetIter(moment) {
20399
20844
  if (!(moment in map)) return;
20400
20845
  delete map[moment];
20401
20846
  if (Object.keys(map).length === 0) {
20402
- 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);
20403
20848
  } else {
20404
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20405
- (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));
20406
20851
  }
20407
20852
  } catch {
20408
20853
  }
@@ -20466,7 +20911,7 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
20466
20911
  const securityFiles = filterSecurity(files);
20467
20912
  let staticResults;
20468
20913
  if (isCodacyAvailable()) {
20469
- 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);
20470
20915
  staticResults = runCodacyAnalysis(scannable);
20471
20916
  } else {
20472
20917
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -20511,7 +20956,7 @@ function emitAllowNotice(userMsg, agentMsg) {
20511
20956
  async function runGuard(opts, globals) {
20512
20957
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
20513
20958
  const { command, cwd, sessionId } = await readPreToolUseStdin();
20514
- if (cwd && (0, import_node_fs32.existsSync)(cwd)) {
20959
+ if (cwd && (0, import_node_fs33.existsSync)(cwd)) {
20515
20960
  try {
20516
20961
  process.chdir(cwd);
20517
20962
  } catch {
@@ -20617,14 +21062,14 @@ function writeBlockMessage(moment, response) {
20617
21062
  }
20618
21063
 
20619
21064
  // src/commands/init.ts
20620
- var import_node_fs34 = require("node:fs");
21065
+ var import_node_fs35 = require("node:fs");
20621
21066
  var import_promises13 = require("node:fs/promises");
20622
21067
  var import_node_path26 = require("node:path");
20623
21068
  var import_node_child_process10 = require("node:child_process");
20624
21069
  var readline2 = __toESM(require("node:readline/promises"));
20625
21070
 
20626
21071
  // src/commands/migrate.ts
20627
- var import_node_fs33 = require("node:fs");
21072
+ var import_node_fs34 = require("node:fs");
20628
21073
  var import_node_path25 = require("node:path");
20629
21074
  var import_node_child_process9 = require("node:child_process");
20630
21075
 
@@ -20756,10 +21201,10 @@ async function runMigration(opts = {}) {
20756
21201
  function migrateProjectDir(root, actions) {
20757
21202
  const gateDir = (0, import_node_path25.join)(root, ".gate");
20758
21203
  const verityDir = (0, import_node_path25.join)(root, ".verity");
20759
- 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)) {
20760
21205
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
20761
21206
  }
20762
- 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)) {
20763
21208
  return migrateProjectDirCarry(gateDir, verityDir, actions);
20764
21209
  }
20765
21210
  return false;
@@ -20780,13 +21225,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
20780
21225
  }
20781
21226
  }
20782
21227
  if (moved) {
20783
- if ((0, import_node_fs33.existsSync)(gateDir)) {
21228
+ if ((0, import_node_fs34.existsSync)(gateDir)) {
20784
21229
  const carried = carryLegacyContents(gateDir, verityDir);
20785
21230
  if (carried > 0) {
20786
21231
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
20787
21232
  }
20788
21233
  try {
20789
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21234
+ (0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
20790
21235
  } catch {
20791
21236
  }
20792
21237
  }
@@ -20802,7 +21247,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
20802
21247
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
20803
21248
  }
20804
21249
  try {
20805
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21250
+ (0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
20806
21251
  } catch {
20807
21252
  }
20808
21253
  return carried > 0;
@@ -20811,9 +21256,9 @@ function migrateGlobalCredentials(home, actions) {
20811
21256
  if (!home) return;
20812
21257
  const gateCreds = (0, import_node_path25.join)(home, ".gate", "credentials");
20813
21258
  const verityCreds = (0, import_node_path25.join)(home, ".verity", "credentials");
20814
- if (!(0, import_node_fs33.existsSync)(gateCreds)) return;
20815
- if (!(0, import_node_fs33.existsSync)(verityCreds)) {
20816
- (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 });
20817
21262
  moveFile(gateCreds, verityCreds);
20818
21263
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
20819
21264
  return;
@@ -20836,7 +21281,7 @@ async function migrateLegacyHooks(root, actions) {
20836
21281
  }
20837
21282
  async function migrateClaudeMd(root, actions) {
20838
21283
  const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
20839
- const hadLegacyBlock = (0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21284
+ const hadLegacyBlock = (0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
20840
21285
  if (!hadLegacyBlock) return;
20841
21286
  try {
20842
21287
  await ensureClaudeMdPointer(root);
@@ -20848,7 +21293,7 @@ async function migrateClaudeMd(root, actions) {
20848
21293
  function migrateStandardFile(root, actions) {
20849
21294
  const gateMd = (0, import_node_path25.join)(root, "GATE.md");
20850
21295
  const verityMd = (0, import_node_path25.join)(root, "VERITY.md");
20851
- 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;
20852
21297
  let moved = false;
20853
21298
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
20854
21299
  try {
@@ -20860,12 +21305,12 @@ function migrateStandardFile(root, actions) {
20860
21305
  if (!moved) moveFile(gateMd, verityMd);
20861
21306
  const content = readFileSyncSafe(verityMd);
20862
21307
  const refreshed = content.split("GATE.md").join("VERITY.md");
20863
- if (refreshed !== content) (0, import_node_fs33.writeFileSync)(verityMd, refreshed);
21308
+ if (refreshed !== content) (0, import_node_fs34.writeFileSync)(verityMd, refreshed);
20864
21309
  actions.push("Renamed GATE.md \u2192 VERITY.md");
20865
21310
  }
20866
21311
  async function migrateTelemetryHeaders(root, actions) {
20867
21312
  const file = (0, import_node_path25.join)(root, ".claude", "settings.local.json");
20868
- if (!(0, import_node_fs33.existsSync)(file)) return;
21313
+ if (!(0, import_node_fs34.existsSync)(file)) return;
20869
21314
  let settings;
20870
21315
  try {
20871
21316
  settings = JSON.parse(readFileSyncSafe(file) || "{}");
@@ -20913,14 +21358,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
20913
21358
  }
20914
21359
  if (toAppend.length > 0) {
20915
21360
  const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
20916
- (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");
20917
21362
  }
20918
- (0, import_node_fs33.rmSync)(gateCreds, { force: true });
21363
+ (0, import_node_fs34.rmSync)(gateCreds, { force: true });
20919
21364
  return toAppend.length;
20920
21365
  }
20921
21366
  function readFileSyncSafe(path) {
20922
21367
  try {
20923
- return (0, import_node_fs33.readFileSync)(path, "utf-8");
21368
+ return (0, import_node_fs34.readFileSync)(path, "utf-8");
20924
21369
  } catch {
20925
21370
  return "";
20926
21371
  }
@@ -20935,35 +21380,35 @@ function hasStagedChanges(root) {
20935
21380
  }
20936
21381
  function moveDir(from, to) {
20937
21382
  try {
20938
- (0, import_node_fs33.renameSync)(from, to);
21383
+ (0, import_node_fs34.renameSync)(from, to);
20939
21384
  } catch (err) {
20940
21385
  if (err.code !== "EXDEV") throw err;
20941
- (0, import_node_fs33.cpSync)(from, to, { recursive: true });
20942
- (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 });
20943
21388
  }
20944
21389
  }
20945
21390
  function moveFile(from, to) {
20946
21391
  try {
20947
- (0, import_node_fs33.renameSync)(from, to);
21392
+ (0, import_node_fs34.renameSync)(from, to);
20948
21393
  } catch (err) {
20949
21394
  if (err.code !== "EXDEV") throw err;
20950
- (0, import_node_fs33.cpSync)(from, to);
20951
- (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 });
20952
21397
  }
20953
21398
  }
20954
21399
  function carryLegacyContents(gateDir, verityDir) {
20955
21400
  let copied = 0;
20956
21401
  const walk = (relDir) => {
20957
21402
  const srcDir = (0, import_node_path25.join)(gateDir, relDir);
20958
- for (const entry of (0, import_node_fs33.readdirSync)(srcDir)) {
21403
+ for (const entry of (0, import_node_fs34.readdirSync)(srcDir)) {
20959
21404
  const rel = relDir ? (0, import_node_path25.join)(relDir, entry) : entry;
20960
21405
  const src = (0, import_node_path25.join)(gateDir, rel);
20961
21406
  const dest = (0, import_node_path25.join)(verityDir, rel);
20962
- if ((0, import_node_fs33.statSync)(src).isDirectory()) {
21407
+ if ((0, import_node_fs34.statSync)(src).isDirectory()) {
20963
21408
  walk(rel);
20964
- } else if (!(0, import_node_fs33.existsSync)(dest)) {
20965
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
20966
- (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);
20967
21412
  copied++;
20968
21413
  }
20969
21414
  }
@@ -20974,20 +21419,20 @@ function carryLegacyContents(gateDir, verityDir) {
20974
21419
  async function needsMigration(root = repoRoot()) {
20975
21420
  const gateDir = (0, import_node_path25.join)(root, ".gate");
20976
21421
  const verityDir = (0, import_node_path25.join)(root, ".verity");
20977
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) return true;
20978
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
20979
- 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"))) {
20980
21425
  return true;
20981
21426
  }
20982
- 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"))) {
20983
21428
  return true;
20984
21429
  }
20985
21430
  }
20986
21431
  const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
20987
- if ((0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21432
+ if ((0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
20988
21433
  return true;
20989
21434
  }
20990
- 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"))) {
20991
21436
  return true;
20992
21437
  }
20993
21438
  if (await hasLegacyHooksAt(root)) return true;
@@ -21129,7 +21574,7 @@ function resolveDataDir() {
21129
21574
  // local dev: running from repo root
21130
21575
  ];
21131
21576
  for (const candidate of candidates) {
21132
- 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"))) {
21133
21578
  return candidate;
21134
21579
  }
21135
21580
  }
@@ -21145,7 +21590,7 @@ function registerInitCommand(program2) {
21145
21590
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
21146
21591
  const force = opts.force ?? false;
21147
21592
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
21148
- const isProject = projectMarkers.some((m) => (0, import_node_fs34.existsSync)(m));
21593
+ const isProject = projectMarkers.some((m) => (0, import_node_fs35.existsSync)(m));
21149
21594
  if (!isProject) {
21150
21595
  printError("No project detected in the current directory.");
21151
21596
  printInfo('Run "verity init" from your project root.');
@@ -21215,14 +21660,14 @@ function registerInitCommand(program2) {
21215
21660
  for (const skill of skills) {
21216
21661
  const src = (0, import_node_path26.join)(skillsSource, skill);
21217
21662
  const dest = (0, import_node_path26.join)(skillsDest, skill);
21218
- if (!(0, import_node_fs34.existsSync)(src)) {
21663
+ if (!(0, import_node_fs35.existsSync)(src)) {
21219
21664
  printWarn(` Skill data not found: ${skill}`);
21220
21665
  continue;
21221
21666
  }
21222
- if ((0, import_node_fs34.existsSync)(dest) && !force) {
21667
+ if ((0, import_node_fs35.existsSync)(dest) && !force) {
21223
21668
  const srcSkill = (0, import_node_path26.join)(src, "SKILL.md");
21224
21669
  const destSkill = (0, import_node_path26.join)(dest, "SKILL.md");
21225
- if ((0, import_node_fs34.existsSync)(destSkill)) {
21670
+ if ((0, import_node_fs35.existsSync)(destSkill)) {
21226
21671
  try {
21227
21672
  const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
21228
21673
  const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
@@ -21294,7 +21739,7 @@ function registerInitCommand(program2) {
21294
21739
  }
21295
21740
 
21296
21741
  // src/commands/uninstall.ts
21297
- var import_node_fs35 = require("node:fs");
21742
+ var import_node_fs36 = require("node:fs");
21298
21743
  var import_node_path27 = require("node:path");
21299
21744
  var SKILL_NAMES = [
21300
21745
  "verity-setup",
@@ -21315,10 +21760,10 @@ function registerUninstallCommand(program2) {
21315
21760
  const skillsRoot = projectPath(".claude/skills");
21316
21761
  for (const name of SKILL_NAMES) {
21317
21762
  const dir = (0, import_node_path27.join)(skillsRoot, name);
21318
- if ((0, import_node_fs35.existsSync)(dir)) {
21763
+ if ((0, import_node_fs36.existsSync)(dir)) {
21319
21764
  actions.push({
21320
21765
  label: `Remove .claude/skills/${name}/`,
21321
- apply: () => (0, import_node_fs35.rmSync)(dir, { recursive: true, force: true })
21766
+ apply: () => (0, import_node_fs36.rmSync)(dir, { recursive: true, force: true })
21322
21767
  });
21323
21768
  }
21324
21769
  }
@@ -21332,24 +21777,24 @@ function registerUninstallCommand(program2) {
21332
21777
  });
21333
21778
  }
21334
21779
  const verityDir = projectPath(VERITY_DIR);
21335
- if ((0, import_node_fs35.existsSync)(verityDir)) {
21780
+ if ((0, import_node_fs36.existsSync)(verityDir)) {
21336
21781
  actions.push({
21337
21782
  label: `Remove ${VERITY_DIR}/`,
21338
- apply: () => (0, import_node_fs35.rmSync)(verityDir, { recursive: true, force: true })
21783
+ apply: () => (0, import_node_fs36.rmSync)(verityDir, { recursive: true, force: true })
21339
21784
  });
21340
21785
  }
21341
21786
  if (!keepVerityMd) {
21342
21787
  const verityMd = projectPath(VERITY_MD_FILE);
21343
- if ((0, import_node_fs35.existsSync)(verityMd)) {
21788
+ if ((0, import_node_fs36.existsSync)(verityMd)) {
21344
21789
  actions.push({
21345
21790
  label: `Remove ${VERITY_MD_FILE}`,
21346
- apply: () => (0, import_node_fs35.rmSync)(verityMd, { force: true })
21791
+ apply: () => (0, import_node_fs36.rmSync)(verityMd, { force: true })
21347
21792
  });
21348
21793
  }
21349
21794
  }
21350
21795
  const cleanupEmptyDir = (path) => {
21351
- if ((0, import_node_fs35.existsSync)(path) && (0, import_node_fs35.statSync)(path).isDirectory() && (0, import_node_fs35.readdirSync)(path).length === 0) {
21352
- (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);
21353
21798
  }
21354
21799
  };
21355
21800
  actions.push({
@@ -21361,10 +21806,10 @@ function registerUninstallCommand(program2) {
21361
21806
  });
21362
21807
  const home = process.env.HOME ?? "";
21363
21808
  const globalVerityDir = (0, import_node_path27.join)(home, ".verity");
21364
- if (purgeGlobal && (0, import_node_fs35.existsSync)(globalVerityDir)) {
21809
+ if (purgeGlobal && (0, import_node_fs36.existsSync)(globalVerityDir)) {
21365
21810
  actions.push({
21366
21811
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
21367
- apply: () => (0, import_node_fs35.rmSync)(globalVerityDir, { recursive: true, force: true })
21812
+ apply: () => (0, import_node_fs36.rmSync)(globalVerityDir, { recursive: true, force: true })
21368
21813
  });
21369
21814
  }
21370
21815
  if (actions.length === 0) {
@@ -21558,7 +22003,7 @@ function registerTaskCommands(program2) {
21558
22003
  }
21559
22004
 
21560
22005
  // src/commands/reset.ts
21561
- var import_node_fs36 = require("node:fs");
22006
+ var import_node_fs37 = require("node:fs");
21562
22007
  var import_node_path28 = require("node:path");
21563
22008
  function registerResetCommand(program2) {
21564
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) => {
@@ -21596,11 +22041,11 @@ function registerResetCommand(program2) {
21596
22041
  }
21597
22042
  const cacheDir = projectPath(CACHE_DIR);
21598
22043
  let purged = 0;
21599
- if ((0, import_node_fs36.existsSync)(cacheDir)) {
21600
- 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)) {
21601
22046
  if (entry.startsWith("pending-")) {
21602
22047
  try {
21603
- (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));
21604
22049
  purged++;
21605
22050
  } catch {
21606
22051
  }
@@ -21615,19 +22060,19 @@ function registerResetCommand(program2) {
21615
22060
  projectPath(`${VERITY_DIR}/.last-analysis`)
21616
22061
  ];
21617
22062
  for (const file of filesToClear) {
21618
- if ((0, import_node_fs36.existsSync)(file)) {
22063
+ if ((0, import_node_fs37.existsSync)(file)) {
21619
22064
  try {
21620
- (0, import_node_fs36.writeFileSync)(file, "");
22065
+ (0, import_node_fs37.writeFileSync)(file, "");
21621
22066
  } catch {
21622
22067
  }
21623
22068
  }
21624
22069
  }
21625
22070
  if (opts.all) {
21626
22071
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
21627
- if ((0, import_node_fs36.existsSync)(logsDir)) {
21628
- 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)) {
21629
22074
  try {
21630
- (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));
21631
22076
  } catch {
21632
22077
  }
21633
22078
  }
@@ -21935,8 +22380,8 @@ function registerTelemetryCommands(program2) {
21935
22380
  }
21936
22381
 
21937
22382
  // src/cli.ts
21938
- program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.6726d0f").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) => {
21939
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.6726d0f");
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");
21940
22385
  setUserNamedServiceUrl(program.opts().serviceUrl);
21941
22386
  try {
21942
22387
  await foldLegacyLocalCredential();