@codacy/verity-cli 0.23.2 → 0.24.0-experimental.6a390ac

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/verity.js CHANGED
@@ -10379,6 +10379,7 @@ var MAX_PLAN_FILES = 3;
10379
10379
  var MAX_PLAN_FILE_BYTES = 10240;
10380
10380
  var MAX_INTENT_CHARS = 2e3;
10381
10381
  var SNAPSHOT_DIR = `${VERITY_DIR}/.snapshot`;
10382
+ var BASELINE_DIR = `${VERITY_DIR}/.baseline`;
10382
10383
  var CONVERSATION_BUFFER_FILE = `${VERITY_DIR}/.conversation-buffer`;
10383
10384
  var CONVERSATION_MAX_ENTRIES = 10;
10384
10385
  var CONVERSATION_WINDOW_MINUTES = 15;
@@ -10473,7 +10474,8 @@ var SECURITY_PATTERNS = [
10473
10474
  /Cargo\.lock$/,
10474
10475
  /Dockerfile/
10475
10476
  ];
10476
- var DEFAULT_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10477
+ var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10478
+ var DEFAULT_SERVICE_URL = "https://yykkfdexzljmmkklpgyz.supabase.co/functions/v1".length > 0 ? "https://yykkfdexzljmmkklpgyz.supabase.co/functions/v1" : PROD_SERVICE_URL;
10477
10479
 
10478
10480
  // src/lib/auth.ts
10479
10481
  async function resolveToken(flagToken) {
@@ -10848,6 +10850,10 @@ var VERITY_INTENT_HOOK = {
10848
10850
  type: "command",
10849
10851
  command: "verity intent capture"
10850
10852
  };
10853
+ var VERITY_BASELINE_HOOK = {
10854
+ type: "command",
10855
+ command: "verity baseline capture"
10856
+ };
10851
10857
  var GUARD_TIMEOUT = 300;
10852
10858
  function buildGuardHook(on) {
10853
10859
  return {
@@ -10860,9 +10866,13 @@ function buildGuardHook(on) {
10860
10866
  var VERITY_STOP_RE = /(?:^|[\/\s"'])verity\s+analyze\b/;
10861
10867
  var VERITY_INTENT_RE = /(?:^|[\/\s"'])verity\s+intent\s+capture\b/;
10862
10868
  var VERITY_GUARD_RE = /(?:^|[\/\s"'])verity\s+guard\b/;
10869
+ var VERITY_BASELINE_RE = /(?:^|[\/\s"'])verity\s+baseline\s+capture\b/;
10863
10870
  function isVerityGuardHook(entry) {
10864
10871
  return VERITY_GUARD_RE.test(entry.command ?? "");
10865
10872
  }
10873
+ function isVerityBaselineHook(entry) {
10874
+ return VERITY_BASELINE_RE.test(entry.command ?? "");
10875
+ }
10866
10876
  var LEGACY_STOP_RE = /(?:^|[\/\s"'])gate\s+analyze\b/;
10867
10877
  var LEGACY_INTENT_RE = /(?:^|[\/\s"'])gate\s+intent\s+capture\b/;
10868
10878
  function isVerityStopHook(entry) {
@@ -10874,7 +10884,7 @@ function isVerityIntentHook(entry) {
10874
10884
  return VERITY_INTENT_RE.test(c) || LEGACY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh") || c.includes(".gate/hooks/capture-intent.sh");
10875
10885
  }
10876
10886
  function isVerityHook(entry) {
10877
- return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry);
10887
+ return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry);
10878
10888
  }
10879
10889
  function isCurrentVerityStopHook(entry) {
10880
10890
  const c = entry.command ?? "";
@@ -10885,7 +10895,7 @@ function isCurrentVerityIntentHook(entry) {
10885
10895
  return VERITY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh");
10886
10896
  }
10887
10897
  function isCurrentVerityHook(entry) {
10888
- return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry);
10898
+ return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry);
10889
10899
  }
10890
10900
  function settingsHasLegacyHook(settings) {
10891
10901
  for (const groups of Object.values(settings.hooks ?? {})) {
@@ -10927,16 +10937,18 @@ async function readAllSettings() {
10927
10937
  async function checkAllVerityHooks() {
10928
10938
  let stop = false;
10929
10939
  let intent = false;
10940
+ let baseline = false;
10930
10941
  let guard = false;
10931
10942
  let guardOn = [];
10932
10943
  for (const settings of await readAllSettings()) {
10933
10944
  const r = checkVerityHooks(settings);
10934
10945
  stop = stop || r.stop;
10935
10946
  intent = intent || r.intent;
10947
+ baseline = baseline || r.baseline;
10936
10948
  guard = guard || r.guard;
10937
10949
  if (r.guardOn.length > guardOn.length) guardOn = r.guardOn;
10938
10950
  }
10939
- return { stop, intent, guard, guardOn };
10951
+ return { stop, intent, baseline, guard, guardOn };
10940
10952
  }
10941
10953
  async function checkExternalVerityHooks() {
10942
10954
  let stop = false;
@@ -10959,12 +10971,14 @@ async function checkExternalVerityHooks() {
10959
10971
  async function checkAllVerityHooksDetailed() {
10960
10972
  let stop = false;
10961
10973
  let intent = false;
10974
+ let baseline = false;
10962
10975
  let hasCurrent = false;
10963
10976
  let hasLegacy = false;
10964
10977
  for (const settings of await readAllSettings()) {
10965
10978
  const r = checkVerityHooks(settings);
10966
10979
  stop = stop || r.stop;
10967
10980
  intent = intent || r.intent;
10981
+ baseline = baseline || r.baseline;
10968
10982
  for (const groups of Object.values(settings.hooks ?? {})) {
10969
10983
  for (const g of groups ?? []) {
10970
10984
  for (const h of g.hooks ?? []) {
@@ -10974,7 +10988,7 @@ async function checkAllVerityHooksDetailed() {
10974
10988
  }
10975
10989
  }
10976
10990
  }
10977
- return { stop, intent, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
10991
+ return { stop, intent, baseline, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
10978
10992
  }
10979
10993
  async function writeSettings(settings) {
10980
10994
  await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
@@ -11017,6 +11031,10 @@ function checkVerityHooks(settings) {
11017
11031
  const hasIntent = intentGroups.some(
11018
11032
  (g) => g.hooks?.some((h) => isCurrentVerityIntentHook(h))
11019
11033
  );
11034
+ const sessionStartGroups = hooks["SessionStart"] ?? [];
11035
+ const hasBaseline = sessionStartGroups.some(
11036
+ (g) => g.hooks?.some((h) => isVerityBaselineHook(h))
11037
+ );
11020
11038
  let guard = false;
11021
11039
  let guardOn = [];
11022
11040
  for (const g of hooks["PreToolUse"] ?? []) {
@@ -11029,16 +11047,17 @@ function checkVerityHooks(settings) {
11029
11047
  }
11030
11048
  }
11031
11049
  }
11032
- return { stop: hasStop, intent: hasIntent, guard, guardOn };
11050
+ return { stop: hasStop, intent: hasIntent, baseline: hasBaseline, guard, guardOn };
11033
11051
  }
11034
- function installVerityHooks(settings, force, externalPresent = { stop: false, intent: false }) {
11052
+ function installVerityHooks(settings, force, externalPresent = { stop: false, intent: false, baseline: false }) {
11035
11053
  const local = checkVerityHooks(settings);
11036
11054
  const existing = {
11037
11055
  stop: local.stop || externalPresent.stop,
11038
- intent: local.intent || externalPresent.intent
11056
+ intent: local.intent || externalPresent.intent,
11057
+ baseline: local.baseline || (externalPresent.baseline ?? false)
11039
11058
  };
11040
11059
  const hasLocalLegacy = settingsHasLegacyHook(settings);
11041
- if (existing.stop && existing.intent && !force && !hasLocalLegacy) {
11060
+ if (existing.stop && existing.intent && existing.baseline && !force && !hasLocalLegacy) {
11042
11061
  return { ok: false, error: "Verity hooks already installed. Use --force to overwrite." };
11043
11062
  }
11044
11063
  const newSettings = { ...settings };
@@ -11046,7 +11065,7 @@ function installVerityHooks(settings, force, externalPresent = { stop: false, in
11046
11065
  newSettings.hooks = {};
11047
11066
  }
11048
11067
  const shouldStrip = force ? isVerityHook : isLegacyHook;
11049
- for (const key of ["Stop", "UserPromptSubmit"]) {
11068
+ for (const key of ["Stop", "UserPromptSubmit", "SessionStart"]) {
11050
11069
  const groups = newSettings.hooks[key];
11051
11070
  if (groups) {
11052
11071
  newSettings.hooks[key] = groups.map((g) => ({
@@ -11073,6 +11092,15 @@ function installVerityHooks(settings, force, externalPresent = { stop: false, in
11073
11092
  if (!newSettings.hooks["UserPromptSubmit"]) newSettings.hooks["UserPromptSubmit"] = [];
11074
11093
  newSettings.hooks["UserPromptSubmit"].push({ hooks: [VERITY_INTENT_HOOK] });
11075
11094
  }
11095
+ if (!existing.baseline) {
11096
+ if (!newSettings.hooks["SessionStart"]) {
11097
+ newSettings.hooks["SessionStart"] = [];
11098
+ }
11099
+ newSettings.hooks["SessionStart"].push({ hooks: [VERITY_BASELINE_HOOK] });
11100
+ } else if (force && local.baseline) {
11101
+ if (!newSettings.hooks["SessionStart"]) newSettings.hooks["SessionStart"] = [];
11102
+ newSettings.hooks["SessionStart"].push({ hooks: [VERITY_BASELINE_HOOK] });
11103
+ }
11076
11104
  return { ok: true, data: newSettings };
11077
11105
  }
11078
11106
  function removeVerityHooks(settings) {
@@ -11106,6 +11134,7 @@ function reconcileMomentHooks(settings, moments, externalPresent = {
11106
11134
  (s.hooks[event] ??= []).push(group);
11107
11135
  };
11108
11136
  if (!externalPresent.intent) push("UserPromptSubmit", { hooks: [VERITY_INTENT_HOOK] });
11137
+ push("SessionStart", { hooks: [VERITY_BASELINE_HOOK] });
11109
11138
  if (moments.includes("stop") && !externalPresent.stop) {
11110
11139
  push("Stop", { hooks: [VERITY_STOP_HOOK] });
11111
11140
  }
@@ -11154,7 +11183,7 @@ function registerHooksCommands(program2) {
11154
11183
  return;
11155
11184
  }
11156
11185
  const detail = await checkAllVerityHooksDetailed();
11157
- const present = { stop: detail.stop, intent: detail.intent };
11186
+ const present = { stop: detail.stop, intent: detail.intent, baseline: detail.baseline };
11158
11187
  if (detail.legacyOnly) {
11159
11188
  printInfo("Found a legacy GATE.md hook \u2014 upgrading it to Verity...");
11160
11189
  const settings2 = removeVerityHooks(await readSettings());
@@ -11167,11 +11196,12 @@ function registerHooksCommands(program2) {
11167
11196
  printInfo("Verity hooks installed in .claude/settings.json (legacy hook removed)");
11168
11197
  printInfo(" Stop hook: verity analyze");
11169
11198
  printInfo(" UserPromptSubmit hook: verity intent capture");
11199
+ printInfo(" SessionStart hook: verity baseline capture");
11170
11200
  return;
11171
11201
  }
11172
11202
  const settings = await readSettings();
11173
11203
  const hasLocalLegacy = settingsHasLegacyHook(settings);
11174
- if (!force && present.stop && present.intent && !hasLocalLegacy) {
11204
+ if (!force && present.stop && present.intent && present.baseline && !hasLocalLegacy) {
11175
11205
  printInfo("Verity hooks already installed (found in .claude/settings.json, settings.local.json, or your global settings).");
11176
11206
  printInfo("Use --force to rewrite the project settings.json copy.");
11177
11207
  return;
@@ -11185,11 +11215,13 @@ function registerHooksCommands(program2) {
11185
11215
  printInfo("Verity hooks installed in .claude/settings.json");
11186
11216
  printInfo(" Stop hook: verity analyze");
11187
11217
  printInfo(" UserPromptSubmit hook: verity intent capture");
11218
+ printInfo(" SessionStart hook: verity baseline capture");
11188
11219
  });
11189
11220
  hooks.command("check").description("Check if Verity hooks are installed").action(async () => {
11190
11221
  const status = await checkAllVerityHooks();
11191
11222
  printInfo(`Stop hook (verity analyze): ${status.stop ? "installed" : "not installed"}`);
11192
11223
  printInfo(`Intent hook (verity intent capture): ${status.intent ? "installed" : "not installed"}`);
11224
+ printInfo(`Baseline hook (verity baseline capture): ${status.baseline ? "installed" : "not installed"}`);
11193
11225
  const gates = status.guard ? status.guardOn.join(", ") : "none";
11194
11226
  printInfo(`Git-moment gate (verity guard): ${status.guard ? `installed [${gates}]` : "not installed"}`);
11195
11227
  if (!status.stop && !status.guard) {
@@ -11242,15 +11274,28 @@ async function appendToConversationBuffer(prompt, sessionId) {
11242
11274
  } catch {
11243
11275
  }
11244
11276
  }
11245
- async function readAndClearConversationBuffer() {
11277
+ async function readAndClearConversationBuffer(currentSessionId) {
11246
11278
  try {
11247
11279
  if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
11248
11280
  const entries = await readBufferEntries();
11249
- await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11250
- });
11251
- if (entries.length > 0) {
11281
+ let mine = entries;
11282
+ let others = [];
11283
+ if (currentSessionId) {
11284
+ mine = entries.filter((e) => e.session_id === currentSessionId || !e.session_id);
11285
+ others = entries.filter((e) => e.session_id && e.session_id !== currentSessionId);
11286
+ }
11287
+ if (others.length > 0) {
11288
+ const remaining = others.map((e) => JSON.stringify(e)).join("\n") + "\n";
11289
+ const tmpFile = `${CONVERSATION_BUFFER_FILE}.tmp`;
11290
+ await (0, import_promises5.writeFile)(tmpFile, remaining);
11291
+ await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11292
+ } else {
11293
+ await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11294
+ });
11295
+ }
11296
+ if (mine.length > 0) {
11252
11297
  return {
11253
- prompts: entries,
11298
+ prompts: mine,
11254
11299
  recent_commits: getRecentCommitMessages()
11255
11300
  };
11256
11301
  }
@@ -12718,8 +12763,8 @@ async function sendGeneralFeedback(message, opts, globals) {
12718
12763
  }
12719
12764
 
12720
12765
  // src/commands/analyze.ts
12721
- var import_node_fs18 = require("node:fs");
12722
- var import_node_path13 = require("node:path");
12766
+ var import_node_fs19 = require("node:fs");
12767
+ var import_node_path14 = require("node:path");
12723
12768
 
12724
12769
  // src/lib/git.ts
12725
12770
  var import_node_child_process5 = require("node:child_process");
@@ -12811,6 +12856,26 @@ function getChangedFiles() {
12811
12856
  function getStagedFiles() {
12812
12857
  return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12813
12858
  }
12859
+ function getDirtyFiles() {
12860
+ const set = /* @__PURE__ */ new Set();
12861
+ for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
12862
+ for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
12863
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
12864
+ return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12865
+ }
12866
+ function showContentAtRef(ref, repoRelPath) {
12867
+ if (!ref || ref === "no-git") return null;
12868
+ const normalizedPath = repoRelPath.replace(/\\/g, "/");
12869
+ try {
12870
+ return (0, import_node_child_process5.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
12871
+ encoding: "utf-8",
12872
+ maxBuffer: 64 * 1024 * 1024,
12873
+ stdio: ["pipe", "pipe", "pipe"]
12874
+ });
12875
+ } catch {
12876
+ return null;
12877
+ }
12878
+ }
12814
12879
  function getPushRangeFiles() {
12815
12880
  const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12816
12881
  const resolvers = [
@@ -12833,6 +12898,11 @@ function getPushRangeFiles() {
12833
12898
  const last = diff("HEAD~1..HEAD");
12834
12899
  return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
12835
12900
  }
12901
+ function getPushRangeMessages() {
12902
+ const { range } = getPushRangeFiles();
12903
+ if (!range) return "";
12904
+ return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
12905
+ }
12836
12906
  function getWorktreeFiles() {
12837
12907
  const result = [];
12838
12908
  const worktreeDir = ".claude/worktrees";
@@ -13458,15 +13528,207 @@ function cleanStaleSnapshots(dir, keepSet) {
13458
13528
  }
13459
13529
  }
13460
13530
 
13461
- // src/lib/offline.ts
13531
+ // src/lib/baseline.ts
13462
13532
  var import_node_fs13 = require("node:fs");
13533
+ var import_node_path11 = require("node:path");
13463
13534
  var import_node_crypto4 = require("node:crypto");
13535
+ var BASELINE_VERSION = 1;
13536
+ var BASELINE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
13537
+ var MIRROR_MAX_BYTES = 2 * 1024 * 1024;
13538
+ var DEFAULT_SESSION_KEY = "_default";
13539
+ function sessionKey(sessionId) {
13540
+ if (!sessionId) return DEFAULT_SESSION_KEY;
13541
+ return (0, import_node_crypto4.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
13542
+ }
13543
+ function sessionDir(key) {
13544
+ return (0, import_node_path11.join)(projectPath(BASELINE_DIR), key);
13545
+ }
13546
+ function manifestPath(dir) {
13547
+ return (0, import_node_path11.join)(dir, "manifest.json");
13548
+ }
13549
+ function mirrorPath(dir, repoRelPath) {
13550
+ return (0, import_node_path11.join)(dir, "files", repoRelPath);
13551
+ }
13552
+ function captureBaseline(opts = {}) {
13553
+ const key = sessionKey(opts.sessionId);
13554
+ const dir = sessionDir(key);
13555
+ const existing = readManifest(dir);
13556
+ const freshStart = opts.source === "startup" || opts.source === "clear";
13557
+ if (existing && !freshStart) {
13558
+ return { baseline: existing, created: false };
13559
+ }
13560
+ const head_sha = getCurrentCommit();
13561
+ const dirty = getDirtyFiles();
13562
+ try {
13563
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13564
+ } catch {
13565
+ }
13566
+ const filesDir = (0, import_node_path11.join)(dir, "files");
13567
+ const mirrored = [];
13568
+ try {
13569
+ (0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
13570
+ for (const p of dirty) {
13571
+ if (p.includes("..")) continue;
13572
+ const content = safeReadForMirror(projectPath(p));
13573
+ if (content === null) continue;
13574
+ const dest = mirrorPath(dir, p);
13575
+ try {
13576
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(dest), { recursive: true });
13577
+ (0, import_node_fs13.writeFileSync)(dest, content);
13578
+ mirrored.push(p);
13579
+ } catch {
13580
+ }
13581
+ }
13582
+ } catch {
13583
+ }
13584
+ const baseline = {
13585
+ session_id: opts.sessionId ?? "",
13586
+ head_sha,
13587
+ captured_at: Date.now(),
13588
+ dirty_paths: mirrored,
13589
+ version: BASELINE_VERSION
13590
+ };
13591
+ try {
13592
+ (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
13593
+ (0, import_node_fs13.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
13594
+ } catch {
13595
+ }
13596
+ pruneOldBaselines();
13597
+ return { baseline, created: true };
13598
+ }
13599
+ function readBaseline(sessionId) {
13600
+ return readManifest(sessionDir(sessionKey(sessionId)));
13601
+ }
13602
+ function readManifest(dir) {
13603
+ const mp = manifestPath(dir);
13604
+ if (!(0, import_node_fs13.existsSync)(mp)) return null;
13605
+ try {
13606
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(mp, "utf-8"));
13607
+ if (typeof parsed.head_sha !== "string" || typeof parsed.captured_at !== "number" || !Array.isArray(parsed.dirty_paths) || parsed.version !== BASELINE_VERSION) {
13608
+ return null;
13609
+ }
13610
+ return {
13611
+ session_id: typeof parsed.session_id === "string" ? parsed.session_id : "",
13612
+ head_sha: parsed.head_sha,
13613
+ captured_at: parsed.captured_at,
13614
+ dirty_paths: parsed.dirty_paths.filter((p) => typeof p === "string"),
13615
+ version: parsed.version
13616
+ };
13617
+ } catch {
13618
+ return null;
13619
+ }
13620
+ }
13621
+ var preImageCache = /* @__PURE__ */ new WeakMap();
13622
+ function preImage(repoRelPath, baseline) {
13623
+ let perBaseline = preImageCache.get(baseline);
13624
+ if (!perBaseline) {
13625
+ perBaseline = /* @__PURE__ */ new Map();
13626
+ preImageCache.set(baseline, perBaseline);
13627
+ }
13628
+ const cached = perBaseline.get(repoRelPath);
13629
+ if (cached) return cached;
13630
+ const resolved = resolvePreImage(repoRelPath, baseline);
13631
+ perBaseline.set(repoRelPath, resolved);
13632
+ return resolved;
13633
+ }
13634
+ function resolvePreImage(repoRelPath, baseline) {
13635
+ if (baseline.dirty_paths.includes(repoRelPath)) {
13636
+ const mp = mirrorPath(sessionDir(sessionKey(baseline.session_id)), repoRelPath);
13637
+ if ((0, import_node_fs13.existsSync)(mp)) {
13638
+ try {
13639
+ return { content: (0, import_node_fs13.readFileSync)(mp, "utf-8"), existed: true };
13640
+ } catch {
13641
+ }
13642
+ }
13643
+ }
13644
+ const atHead = showContentAtRef(baseline.head_sha, repoRelPath);
13645
+ if (atHead !== null) return { content: atHead, existed: true };
13646
+ return { content: "", existed: false };
13647
+ }
13648
+ function generateBaselineDiffs(files, baseline) {
13649
+ if (!baseline) return { diffs: [], has_baseline: false };
13650
+ const diffs = [];
13651
+ for (const file of files) {
13652
+ const language = file.language ?? detectLanguage(file.path);
13653
+ const pre = preImage(file.path, baseline);
13654
+ if (pre.existed) {
13655
+ if (pre.content === file.content) continue;
13656
+ const diff = computeDiff(pre.content, file.content, file.path);
13657
+ if (diff) diffs.push({ path: file.path, language, diff, status: "modified" });
13658
+ } else {
13659
+ const addedLines = file.content.split("\n").map((l) => `+${l}`).join("\n");
13660
+ diffs.push({
13661
+ path: file.path,
13662
+ language,
13663
+ diff: `--- /dev/null
13664
+ +++ b/${file.path}
13665
+ @@ -0,0 +1,${file.content.split("\n").length} @@
13666
+ ${addedLines}`,
13667
+ status: "added"
13668
+ });
13669
+ }
13670
+ }
13671
+ return { diffs, has_baseline: true };
13672
+ }
13673
+ function changedSinceBaseline(repoRelPath, baseline) {
13674
+ const pre = preImage(repoRelPath, baseline);
13675
+ let current;
13676
+ try {
13677
+ current = (0, import_node_fs13.readFileSync)(projectPath(repoRelPath), "utf-8");
13678
+ } catch {
13679
+ return pre.existed;
13680
+ }
13681
+ if (!pre.existed) return true;
13682
+ return current !== pre.content;
13683
+ }
13684
+ function safeReadForMirror(absPath) {
13685
+ try {
13686
+ if ((0, import_node_fs13.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
13687
+ const buf = (0, import_node_fs13.readFileSync)(absPath);
13688
+ if (buf.includes(0)) return null;
13689
+ return buf.toString("utf-8");
13690
+ } catch {
13691
+ return null;
13692
+ }
13693
+ }
13694
+ function pruneOldBaselines() {
13695
+ const root = projectPath(BASELINE_DIR);
13696
+ let entries;
13697
+ try {
13698
+ entries = (0, import_node_fs13.readdirSync)(root);
13699
+ } catch {
13700
+ return;
13701
+ }
13702
+ const now = Date.now();
13703
+ for (const name of entries) {
13704
+ const dir = (0, import_node_path11.join)(root, name);
13705
+ const manifest = readManifest(dir);
13706
+ if (!manifest) {
13707
+ try {
13708
+ if (now - (0, import_node_fs13.statSync)(dir).mtimeMs > BASELINE_TTL_MS) {
13709
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13710
+ }
13711
+ } catch {
13712
+ }
13713
+ continue;
13714
+ }
13715
+ if (now - manifest.captured_at <= BASELINE_TTL_MS) continue;
13716
+ try {
13717
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13718
+ } catch {
13719
+ }
13720
+ }
13721
+ }
13722
+
13723
+ // src/lib/offline.ts
13724
+ var import_node_fs14 = require("node:fs");
13725
+ var import_node_crypto5 = require("node:crypto");
13464
13726
  function cacheRequest(body) {
13465
13727
  try {
13466
- (0, import_node_fs13.mkdirSync)(CACHE_DIR, { recursive: true });
13467
- const suffix = (0, import_node_crypto4.randomBytes)(4).toString("hex");
13728
+ (0, import_node_fs14.mkdirSync)(CACHE_DIR, { recursive: true });
13729
+ const suffix = (0, import_node_crypto5.randomBytes)(4).toString("hex");
13468
13730
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
13469
- (0, import_node_fs13.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
13731
+ (0, import_node_fs14.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
13470
13732
  } catch {
13471
13733
  }
13472
13734
  }
@@ -13480,7 +13742,7 @@ function buildOfflineFallback(reason, staticResults) {
13480
13742
  }
13481
13743
 
13482
13744
  // src/lib/context-files.ts
13483
- var import_node_fs14 = require("node:fs");
13745
+ var import_node_fs15 = require("node:fs");
13484
13746
  var MAX_CONTEXT_FILES = 10;
13485
13747
  var MAX_CONTEXT_FILE_BYTES = 10240;
13486
13748
  var MAX_CONTEXT_TOTAL_BYTES = 51200;
@@ -13492,7 +13754,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
13492
13754
  if (result.length >= MAX_CONTEXT_FILES) break;
13493
13755
  if (deltaPaths.has(filePath)) continue;
13494
13756
  try {
13495
- const content = (0, import_node_fs14.readFileSync)(filePath, "utf8");
13757
+ const content = (0, import_node_fs15.readFileSync)(filePath, "utf8");
13496
13758
  const bytes = Buffer.byteLength(content);
13497
13759
  if (bytes > MAX_CONTEXT_FILE_BYTES) {
13498
13760
  logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
@@ -13537,20 +13799,20 @@ function gatherContextFiles(contextPaths, deltaFiles) {
13537
13799
  }
13538
13800
 
13539
13801
  // src/lib/cache-cleanup.ts
13540
- var import_node_fs15 = require("node:fs");
13541
- var import_node_path11 = require("node:path");
13802
+ var import_node_fs16 = require("node:fs");
13803
+ var import_node_path12 = require("node:path");
13542
13804
  var CACHE_TTL_DAYS = 7;
13543
13805
  function pruneStaleCache() {
13544
13806
  try {
13545
13807
  const dir = projectPath(CACHE_DIR);
13546
13808
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
13547
- for (const entry of (0, import_node_fs15.readdirSync)(dir)) {
13809
+ for (const entry of (0, import_node_fs16.readdirSync)(dir)) {
13548
13810
  if (!entry.startsWith("pending-")) continue;
13549
- const path = (0, import_node_path11.join)(dir, entry);
13811
+ const path = (0, import_node_path12.join)(dir, entry);
13550
13812
  try {
13551
- const stat3 = (0, import_node_fs15.statSync)(path);
13813
+ const stat3 = (0, import_node_fs16.statSync)(path);
13552
13814
  if (stat3.mtimeMs < cutoff) {
13553
- (0, import_node_fs15.unlinkSync)(path);
13815
+ (0, import_node_fs16.unlinkSync)(path);
13554
13816
  logEvent("cache_entry_pruned", {
13555
13817
  path: entry,
13556
13818
  age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
@@ -13622,10 +13884,11 @@ function reconcileAnalysisMode(predictedMode, signals) {
13622
13884
  signals.noFilesChanged,
13623
13885
  signals.assistantResponse,
13624
13886
  signals.conversationPrompts,
13625
- signals.actionSummary
13887
+ signals.actionSummary,
13888
+ signals.sessionAuthoredCode
13626
13889
  );
13627
13890
  }
13628
- const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0));
13891
+ const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
13629
13892
  const agentInvestigated = didAgentInvestigate(signals.actionSummary);
13630
13893
  switch (predictedMode) {
13631
13894
  case "skip":
@@ -13650,8 +13913,8 @@ function didAgentInvestigate(summary) {
13650
13913
  function isValidMode(mode) {
13651
13914
  return mode === "standard" || mode === "plan" || mode === "debug" || mode === "skip";
13652
13915
  }
13653
- function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary) {
13654
- const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
13916
+ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
13917
+ const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
13655
13918
  if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
13656
13919
  if (!agentAuthoredCode) return "skip";
13657
13920
  }
@@ -13705,7 +13968,12 @@ function isReflectionQuestion(response) {
13705
13968
  /reflection\s+for\s+future\s+agents/i,
13706
13969
  /what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
13707
13970
  /say\s+['"]?skip['"]?\s+to\s+skip/i,
13708
- /quick\s+reflection\s+question/i
13971
+ /quick\s+reflection\s+question/i,
13972
+ // Post-flip (VRT-21): the agent drafts the reflection itself and, when
13973
+ // interactive, asks the user to confirm/correct before recording. That
13974
+ // turn authors no code either, so it's still a reflection turn.
13975
+ /reflection\s+draft/i,
13976
+ /confirm,?\s+correct,?\s+or\s+add/i
13709
13977
  ];
13710
13978
  return markers.some((m) => m.test(response));
13711
13979
  }
@@ -13726,7 +13994,7 @@ function isMetaTaskLabel(label2) {
13726
13994
  }
13727
13995
 
13728
13996
  // src/lib/transcript.ts
13729
- var import_node_fs16 = require("node:fs");
13997
+ var import_node_fs17 = require("node:fs");
13730
13998
  var MAX_READ_BYTES = 256 * 1024;
13731
13999
  var SMALL_FILE_BYTES = 64 * 1024;
13732
14000
  var MAX_FILES_LIST = 20;
@@ -13748,14 +14016,14 @@ async function extractActionSummary(transcriptPath) {
13748
14016
  function readTurnLines(transcriptPath) {
13749
14017
  let size;
13750
14018
  try {
13751
- size = (0, import_node_fs16.statSync)(transcriptPath).size;
14019
+ size = (0, import_node_fs17.statSync)(transcriptPath).size;
13752
14020
  } catch {
13753
14021
  return null;
13754
14022
  }
13755
14023
  if (size === 0) return null;
13756
14024
  let raw;
13757
14025
  if (size <= SMALL_FILE_BYTES) {
13758
- raw = (0, import_node_fs16.readFileSync)(transcriptPath, "utf-8");
14026
+ raw = (0, import_node_fs17.readFileSync)(transcriptPath, "utf-8");
13759
14027
  } else {
13760
14028
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
13761
14029
  const fd = require("node:fs").openSync(transcriptPath, "r");
@@ -13843,6 +14111,12 @@ function buildSummary(lines) {
13843
14111
  case "Edit":
13844
14112
  addPath(filesEdited, input.file_path);
13845
14113
  break;
14114
+ case "MultiEdit":
14115
+ addPath(filesEdited, input.file_path);
14116
+ break;
14117
+ case "NotebookEdit":
14118
+ addPath(filesEdited, input.notebook_path ?? input.file_path);
14119
+ break;
13846
14120
  case "Write":
13847
14121
  addPath(filesCreated, input.file_path);
13848
14122
  addPath(filesEdited, input.file_path);
@@ -13926,8 +14200,8 @@ function capArray(set, max) {
13926
14200
 
13927
14201
  // src/lib/seed-runner.ts
13928
14202
  var import_promises11 = require("node:fs/promises");
13929
- var import_node_fs17 = require("node:fs");
13930
- var import_node_path12 = require("node:path");
14203
+ var import_node_fs18 = require("node:fs");
14204
+ var import_node_path13 = require("node:path");
13931
14205
  var import_yaml2 = __toESM(require_dist());
13932
14206
 
13933
14207
  // src/lib/seed.ts
@@ -14166,7 +14440,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
14166
14440
  return fm;
14167
14441
  }
14168
14442
  async function runSeed(opts) {
14169
- if (!(0, import_node_fs17.existsSync)(STANDARD_FILE)) {
14443
+ if (!(0, import_node_fs18.existsSync)(STANDARD_FILE)) {
14170
14444
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
14171
14445
  }
14172
14446
  let standardDoc;
@@ -14178,7 +14452,7 @@ async function runSeed(opts) {
14178
14452
  }
14179
14453
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
14180
14454
  let readmeContent;
14181
- if ((0, import_node_fs17.existsSync)("README.md")) {
14455
+ if ((0, import_node_fs18.existsSync)("README.md")) {
14182
14456
  try {
14183
14457
  readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
14184
14458
  } catch {
@@ -14186,7 +14460,7 @@ async function runSeed(opts) {
14186
14460
  }
14187
14461
  let claudeMdContent;
14188
14462
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
14189
- if ((0, import_node_fs17.existsSync)(p)) {
14463
+ if ((0, import_node_fs18.existsSync)(p)) {
14190
14464
  try {
14191
14465
  claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
14192
14466
  break;
@@ -14209,8 +14483,8 @@ async function runSeed(opts) {
14209
14483
  if (candidates.length === 0) {
14210
14484
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
14211
14485
  }
14212
- const overviewPath = (0, import_node_path12.join)(MEMORY_DIR, "domain", "project-overview.md");
14213
- if ((0, import_node_fs17.existsSync)(overviewPath) && !opts.force) {
14486
+ const overviewPath = (0, import_node_path13.join)(MEMORY_DIR, "domain", "project-overview.md");
14487
+ if ((0, import_node_fs18.existsSync)(overviewPath) && !opts.force) {
14214
14488
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
14215
14489
  }
14216
14490
  if (opts.dryRun) {
@@ -14245,9 +14519,9 @@ async function runSeed(opts) {
14245
14519
  }
14246
14520
  const nodeId = res.data.node_id;
14247
14521
  const filePathRel = res.data.file_path;
14248
- const targetPath = (0, import_node_path12.join)(MEMORY_DIR, filePathRel);
14522
+ const targetPath = (0, import_node_path13.join)(MEMORY_DIR, filePathRel);
14249
14523
  try {
14250
- await (0, import_promises11.mkdir)((0, import_node_path12.dirname)(targetPath), { recursive: true });
14524
+ await (0, import_promises11.mkdir)((0, import_node_path13.dirname)(targetPath), { recursive: true });
14251
14525
  await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
14252
14526
  created++;
14253
14527
  opts.onCreated?.(nodeId, filePathRel, c);
@@ -14317,6 +14591,15 @@ async function runAnalyze(opts, globals) {
14317
14591
  }
14318
14592
  const { assistantMessage: assistantResponse, stopReason, transcriptPath, sessionId } = await readStopHookStdin();
14319
14593
  const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
14594
+ const baselineSessionId = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
14595
+ const baseline = readBaseline(baselineSessionId);
14596
+ if (baseline) {
14597
+ logEvent("baseline_loaded", {
14598
+ head: baseline.head_sha.slice(0, 12),
14599
+ dirty_count: baseline.dirty_paths.length,
14600
+ age_ms: Date.now() - baseline.captured_at
14601
+ });
14602
+ }
14320
14603
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
14321
14604
  const analyzable = filterAnalyzable(allChanged);
14322
14605
  const reviewable = filterReviewable(allChanged);
@@ -14326,7 +14609,7 @@ async function runAnalyze(opts, globals) {
14326
14609
  passAndExit("No analyzable files changed");
14327
14610
  }
14328
14611
  const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
14329
- const conversation = await readAndClearConversationBuffer();
14612
+ const conversation = await readAndClearConversationBuffer(sessionId ?? void 0);
14330
14613
  const specs = discoverSpecs();
14331
14614
  const plans = discoverPlans();
14332
14615
  const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
@@ -14376,13 +14659,14 @@ async function runAnalyze(opts, globals) {
14376
14659
  }
14377
14660
  const conversationPrompts = (conversation?.prompts ?? []).map((p) => p.prompt);
14378
14661
  let analysisMode;
14662
+ const sessionAuthoredCode = !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
14379
14663
  const modeOverride = opts.mode;
14380
14664
  if (modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride)) {
14381
14665
  analysisMode = modeOverride;
14382
14666
  } else {
14383
14667
  analysisMode = reconcileAnalysisMode(
14384
14668
  predictedMode,
14385
- { noFilesChanged, assistantResponse, actionSummary, conversationPrompts }
14669
+ { noFilesChanged, assistantResponse, actionSummary, conversationPrompts, sessionAuthoredCode }
14386
14670
  );
14387
14671
  }
14388
14672
  if (analysisMode === "skip") {
@@ -14440,10 +14724,16 @@ async function runAnalyze(opts, globals) {
14440
14724
  const baseForReview = agentNarrowed.length > 0 ? agentNarrowed : allForReview;
14441
14725
  const recentForReview = narrowToRecent(baseForReview);
14442
14726
  if (!opts.skipStatic && isCodacyAvailable()) {
14443
- const allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14444
- staticResults = runCodacyAnalysis(allScannable);
14727
+ let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14728
+ if (baseline) {
14729
+ allScannable = allScannable.filter((f) => changedSinceBaseline(f, baseline));
14730
+ }
14731
+ if (allScannable.length > 0) {
14732
+ staticResults = runCodacyAnalysis(allScannable);
14733
+ }
14445
14734
  }
14446
- codeDelta = collectCodeDelta(recentForReview, {
14735
+ const deltaSet = baseline ? recentForReview.filter((f) => changedSinceBaseline(f, baseline)) : recentForReview;
14736
+ codeDelta = collectCodeDelta(deltaSet, {
14447
14737
  maxFiles: parseInt(opts.maxFiles, 10),
14448
14738
  maxFileBytes: parseInt(opts.maxFileSize, 10),
14449
14739
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
@@ -14458,7 +14748,12 @@ async function runAnalyze(opts, globals) {
14458
14748
  }
14459
14749
  }
14460
14750
  if (analysisMode !== "plan") {
14461
- snapshotResult = generateSnapshotDiffs(codeDelta.files);
14751
+ if (baseline) {
14752
+ const baselineDiffs = generateBaselineDiffs(codeDelta.files, baseline);
14753
+ snapshotResult = { has_snapshots: baselineDiffs.diffs.length > 0, diffs: baselineDiffs.diffs };
14754
+ } else {
14755
+ snapshotResult = generateSnapshotDiffs(codeDelta.files);
14756
+ }
14462
14757
  currentCommit = getCurrentCommit();
14463
14758
  const maxIterations = parseInt(opts.maxIterations, 10);
14464
14759
  const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
@@ -14490,9 +14785,9 @@ async function runAnalyze(opts, globals) {
14490
14785
  let autoSeedNotice = null;
14491
14786
  try {
14492
14787
  await ensureMemoryDir();
14493
- const seedMarker = (0, import_node_path13.join)(VERITY_DIR, ".seeded");
14494
- const hasStandard = (0, import_node_fs18.existsSync)(STANDARD_FILE);
14495
- const alreadyTried = (0, import_node_fs18.existsSync)(seedMarker);
14788
+ const seedMarker = (0, import_node_path14.join)(VERITY_DIR, ".seeded");
14789
+ const hasStandard = (0, import_node_fs19.existsSync)(STANDARD_FILE);
14790
+ const alreadyTried = (0, import_node_fs19.existsSync)(seedMarker);
14496
14791
  if (hasStandard && !alreadyTried) {
14497
14792
  const preManifest = await buildManifest();
14498
14793
  if (preManifest.nodes.length === 0) {
@@ -14505,7 +14800,7 @@ async function runAnalyze(opts, globals) {
14505
14800
  dryRun: false
14506
14801
  });
14507
14802
  if (seedResult.created > 0) {
14508
- (0, import_node_fs18.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
14803
+ (0, import_node_fs19.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
14509
14804
  `);
14510
14805
  autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
14511
14806
  logEvent("auto_seed_ran", {
@@ -14513,7 +14808,7 @@ async function runAnalyze(opts, globals) {
14513
14808
  failed: seedResult.failed
14514
14809
  });
14515
14810
  } else if (seedResult.skipped === "already_seeded") {
14516
- (0, import_node_fs18.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
14811
+ (0, import_node_fs19.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
14517
14812
  `);
14518
14813
  } else {
14519
14814
  logEvent("auto_seed_noop", {
@@ -14831,8 +15126,54 @@ async function runAnalyze(opts, globals) {
14831
15126
  }
14832
15127
  }
14833
15128
 
15129
+ // src/commands/baseline.ts
15130
+ var import_node_fs20 = require("node:fs");
15131
+ function registerBaselineCommands(program2) {
15132
+ const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
15133
+ 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) => {
15134
+ try {
15135
+ try {
15136
+ process.chdir(repoRoot());
15137
+ } catch {
15138
+ }
15139
+ if (!(0, import_node_fs20.existsSync)(VERITY_DIR)) {
15140
+ process.exit(0);
15141
+ }
15142
+ let sessionId = opts.sessionId;
15143
+ let source = opts.source;
15144
+ if (!process.stdin.isTTY) {
15145
+ const input = (await readStdin()).trim();
15146
+ if (input) {
15147
+ try {
15148
+ const event = JSON.parse(input);
15149
+ sessionId = sessionId ?? event.session_id;
15150
+ source = source ?? event.source;
15151
+ } catch {
15152
+ }
15153
+ }
15154
+ }
15155
+ const result = captureBaseline({ sessionId, source });
15156
+ logEvent("baseline_capture", {
15157
+ created: result.created,
15158
+ source: source ?? null,
15159
+ dirty_count: result.baseline.dirty_paths.length,
15160
+ head: result.baseline.head_sha.slice(0, 12)
15161
+ });
15162
+ } catch {
15163
+ }
15164
+ process.exit(0);
15165
+ });
15166
+ }
15167
+ async function readStdin() {
15168
+ const chunks = [];
15169
+ for await (const chunk of process.stdin) {
15170
+ chunks.push(chunk);
15171
+ }
15172
+ return Buffer.concat(chunks).toString("utf-8");
15173
+ }
15174
+
14834
15175
  // src/commands/review.ts
14835
- var import_node_fs19 = require("node:fs");
15176
+ var import_node_fs21 = require("node:fs");
14836
15177
  function registerReviewCommand(program2) {
14837
15178
  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) => {
14838
15179
  const globals = program2.opts();
@@ -14851,7 +15192,7 @@ async function runReview(opts, globals) {
14851
15192
  const securityFiles = filterSecurity(allFiles);
14852
15193
  let staticResults;
14853
15194
  if (isCodacyAvailable()) {
14854
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs19.existsSync)(f) || resolveFile(f) !== null);
15195
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs21.existsSync)(f) || resolveFile(f) !== null);
14855
15196
  staticResults = runCodacyAnalysis(scannable);
14856
15197
  } else {
14857
15198
  staticResults = {
@@ -14876,10 +15217,10 @@ async function runReview(opts, globals) {
14876
15217
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
14877
15218
  specs = [];
14878
15219
  for (const p of specPaths) {
14879
- if (!(0, import_node_fs19.existsSync)(p)) continue;
15220
+ if (!(0, import_node_fs21.existsSync)(p)) continue;
14880
15221
  try {
14881
- const { readFileSync: readFileSync11 } = await import("node:fs");
14882
- const content = readFileSync11(p, "utf-8");
15222
+ const { readFileSync: readFileSync12 } = await import("node:fs");
15223
+ const content = readFileSync12(p, "utf-8");
14883
15224
  specs.push({ path: p, content: content.slice(0, 10240) });
14884
15225
  } catch {
14885
15226
  }
@@ -14936,10 +15277,10 @@ async function runReview(opts, globals) {
14936
15277
  }
14937
15278
 
14938
15279
  // src/commands/guard.ts
14939
- var import_node_fs20 = require("node:fs");
14940
- var import_node_path14 = require("node:path");
15280
+ var import_node_fs22 = require("node:fs");
15281
+ var import_node_path15 = require("node:path");
14941
15282
  var GUARD_BLOCK_CAP = 2;
14942
- var GUARD_ITER_FILE = (0, import_node_path14.join)(VERITY_DIR, ".guard-iteration");
15283
+ var GUARD_ITER_FILE = (0, import_node_path15.join)(VERITY_DIR, ".guard-iteration");
14943
15284
  function readPreToolUseStdin() {
14944
15285
  const empty = { command: "", cwd: null, sessionId: null };
14945
15286
  return new Promise((resolve) => {
@@ -15003,7 +15344,7 @@ function classifyCommand(command, on) {
15003
15344
  }
15004
15345
  function readIterMap() {
15005
15346
  try {
15006
- const raw = JSON.parse((0, import_node_fs20.readFileSync)(GUARD_ITER_FILE, "utf-8"));
15347
+ const raw = JSON.parse((0, import_node_fs22.readFileSync)(GUARD_ITER_FILE, "utf-8"));
15007
15348
  if (raw && typeof raw === "object") {
15008
15349
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
15009
15350
  return { [raw.moment]: raw.count };
@@ -15023,10 +15364,10 @@ function readIter(moment) {
15023
15364
  }
15024
15365
  function writeIter(moment, count) {
15025
15366
  try {
15026
- (0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
15367
+ (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
15027
15368
  const map = readIterMap();
15028
15369
  map[moment] = count;
15029
- (0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15370
+ (0, import_node_fs22.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15030
15371
  } catch {
15031
15372
  }
15032
15373
  }
@@ -15036,10 +15377,10 @@ function resetIter(moment) {
15036
15377
  if (!(moment in map)) return;
15037
15378
  delete map[moment];
15038
15379
  if (Object.keys(map).length === 0) {
15039
- if ((0, import_node_fs20.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs20.unlinkSync)(GUARD_ITER_FILE);
15380
+ if ((0, import_node_fs22.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs22.unlinkSync)(GUARD_ITER_FILE);
15040
15381
  } else {
15041
- (0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
15042
- (0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15382
+ (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
15383
+ (0, import_node_fs22.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15043
15384
  }
15044
15385
  } catch {
15045
15386
  }
@@ -15057,12 +15398,53 @@ function registerGuardCommand(program2) {
15057
15398
  function getMomentFiles(moment) {
15058
15399
  return moment === "pre-commit" ? getStagedFiles() : getPushRangeFiles().files;
15059
15400
  }
15060
- function buildGuardRequest(moment, files, iter, sessionId) {
15401
+ function matchFlagValue(command, flags) {
15402
+ const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
15403
+ const m = re.exec(command);
15404
+ if (!m) return null;
15405
+ const v = m[2] ?? m[3] ?? m[4];
15406
+ return v != null ? v.replace(/\\(["'])/g, "$1") : null;
15407
+ }
15408
+ function parseCommitMessage(command) {
15409
+ const parts = [];
15410
+ const re = /(?<![\w-])(?:--message|-am|-m)(?:=|\s+)('((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)"|([^\s'"-][^\s]*))/g;
15411
+ let m;
15412
+ while ((m = re.exec(command)) !== null) {
15413
+ const v = m[2] ?? m[3] ?? m[4];
15414
+ if (v != null && v.length) parts.push(v.replace(/\\(["'])/g, "$1"));
15415
+ }
15416
+ return parts.length ? parts.join("\n\n") : null;
15417
+ }
15418
+ function parsePrIntent(command) {
15419
+ const title = matchFlagValue(command, "--title|-t");
15420
+ const body = matchFlagValue(command, "--body|-b");
15421
+ const parts = [title, body].filter((x) => !!x);
15422
+ return parts.length ? parts.join("\n\n") : null;
15423
+ }
15424
+ var TRIVIAL_INTENT = /^(wip|tmp|temp|test|fix|chore|amend|\.+|fixup!.*|squash!.*|merge\b.*)$/i;
15425
+ var SHELL_PLUMBING = /\$\(|`|<<-?\s*['"]?[A-Za-z_]/;
15426
+ function isSubstantiveIntent(text) {
15427
+ if (!text) return false;
15428
+ const t = text.trim();
15429
+ if (t.length < 12) return false;
15430
+ if (TRIVIAL_INTENT.test(t)) return false;
15431
+ if (SHELL_PLUMBING.test(t)) return false;
15432
+ return true;
15433
+ }
15434
+ function extractStatedIntent(moment, command) {
15435
+ const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? (getPushRangeMessages() || null);
15436
+ return isSubstantiveIntent(text) ? text : null;
15437
+ }
15438
+ function hasBlockingFinding(response) {
15439
+ const findings = response.findings ?? [];
15440
+ return findings.some((f) => f.scope !== "pre-existing" && ["critical", "high"].includes((f.severity ?? "").toLowerCase()));
15441
+ }
15442
+ function buildGuardRequest(moment, files, iter, sessionId, command) {
15061
15443
  const analyzable = filterAnalyzable(files);
15062
15444
  const securityFiles = filterSecurity(files);
15063
15445
  let staticResults;
15064
15446
  if (isCodacyAvailable()) {
15065
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs20.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
15447
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs22.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
15066
15448
  staticResults = runCodacyAnalysis(scannable);
15067
15449
  } else {
15068
15450
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -15087,26 +15469,27 @@ function buildGuardRequest(moment, files, iter, sessionId) {
15087
15469
  };
15088
15470
  const specs = discoverSpecs();
15089
15471
  const plans = discoverPlans();
15090
- if (specs.length > 0 || plans.length > 0) {
15472
+ const statedIntent = extractStatedIntent(moment, command);
15473
+ if (specs.length > 0 || plans.length > 0 || statedIntent) {
15091
15474
  const intentContext = {};
15475
+ if (statedIntent) intentContext.user_prompt = statedIntent;
15092
15476
  if (specs.length > 0) intentContext.specs = specs;
15093
15477
  if (plans.length > 0) intentContext.plans = plans;
15094
15478
  requestBody.intent_context = intentContext;
15095
15479
  }
15096
15480
  return requestBody;
15097
15481
  }
15098
- function allowWithNotice(moment, decision) {
15099
- resetIter(moment);
15100
- if (decision === "WARN") {
15101
- process.stderr.write(`${YELLOW}Verity ${moment}: WARN \u2014 proceeding. See findings in the report.${NC}
15102
- `);
15103
- }
15482
+ function emitAllowNotice(userMsg, agentMsg) {
15483
+ process.stdout.write(JSON.stringify({
15484
+ systemMessage: userMsg,
15485
+ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: agentMsg }
15486
+ }) + "\n");
15104
15487
  process.exit(0);
15105
15488
  }
15106
15489
  async function runGuard(opts, globals) {
15107
15490
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
15108
15491
  const { command, cwd, sessionId } = await readPreToolUseStdin();
15109
- if (cwd && (0, import_node_fs20.existsSync)(cwd)) {
15492
+ if (cwd && (0, import_node_fs22.existsSync)(cwd)) {
15110
15493
  try {
15111
15494
  process.chdir(cwd);
15112
15495
  } catch {
@@ -15114,19 +15497,21 @@ async function runGuard(opts, globals) {
15114
15497
  }
15115
15498
  const moment = classifyCommand(command, on);
15116
15499
  if (!moment) process.exit(0);
15500
+ const verb = moment === "pre-commit" ? "commit" : "push";
15117
15501
  const iter = readIter(moment);
15118
15502
  if (iter >= GUARD_BLOCK_CAP) {
15119
- process.stderr.write(`${DIM}Verity ${moment}: ${GUARD_BLOCK_CAP} review cycles reached \u2014 allowing through.${NC}
15120
- `);
15121
15503
  resetIter(moment);
15122
- process.exit(0);
15504
+ emitAllowNotice(
15505
+ `Verity ${moment}: ${GUARD_BLOCK_CAP} review cycles reached \u2014 allowing the ${verb} through`,
15506
+ `Verity ${moment}: review-cycle cap (${GUARD_BLOCK_CAP}) reached; the ${verb} was allowed without a further block.`
15507
+ );
15123
15508
  }
15124
15509
  const files = getMomentFiles(moment);
15125
15510
  if (files.length === 0) process.exit(0);
15126
15511
  const tokenResult = await resolveToken(globals.token);
15127
15512
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
15128
15513
  if (!tokenResult.ok || !urlResult.ok) process.exit(0);
15129
- const requestBody = buildGuardRequest(moment, files, iter, sessionId);
15514
+ const requestBody = buildGuardRequest(moment, files, iter, sessionId, command);
15130
15515
  if (!requestBody) process.exit(0);
15131
15516
  const result = await analyzeRequest({
15132
15517
  serviceUrl: urlResult.data,
@@ -15137,21 +15522,39 @@ async function runGuard(opts, globals) {
15137
15522
  cmd: "guard"
15138
15523
  });
15139
15524
  if (!result.ok) {
15140
- process.stderr.write(`${DIM}Verity ${moment}: service unavailable \u2014 allowing commit/push (${result.error}).${NC}
15141
- `);
15142
- process.exit(0);
15143
- }
15144
- if (opts.json) {
15145
- process.stdout.write(JSON.stringify(result.data) + "\n");
15525
+ emitAllowNotice(
15526
+ `\u26A0 Verity ${moment}: service offline \u2014 ${verb}ed WITHOUT review`,
15527
+ `Verity ${moment}: service unavailable (${result.error}); the ${verb} was allowed WITHOUT a Verity review.`
15528
+ );
15146
15529
  }
15530
+ if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
15147
15531
  const response = result.data;
15148
15532
  const decision = response.gate_decision ?? "PASS";
15149
- if (decision === "FAIL") {
15533
+ const viewUrl = response.view_url ?? "";
15534
+ const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
15535
+ if (decision === "FAIL" && hasBlockingFinding(response)) {
15150
15536
  writeIter(moment, iter + 1);
15151
15537
  writeBlockMessage(moment, response);
15152
15538
  process.exit(2);
15153
15539
  }
15154
- allowWithNotice(moment, decision);
15540
+ resetIter(moment);
15541
+ if (decision === "FAIL") {
15542
+ const narrative = response.assessment?.narrative ?? "";
15543
+ emitAllowNotice(
15544
+ `\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding${link}`,
15545
+ `Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}${viewUrl ? ` Report: ${viewUrl}` : ""}`
15546
+ );
15547
+ }
15548
+ if (decision === "WARN") {
15549
+ emitAllowNotice(
15550
+ `\u26A0 Verity ${moment}: WARN \u2014 proceeding${link}`,
15551
+ `Verity ${moment} review: WARN (proceeding).${viewUrl ? ` Report: ${viewUrl}` : ""}`
15552
+ );
15553
+ }
15554
+ emitAllowNotice(
15555
+ `\u2713 Verity ${moment}: PASS${link}`,
15556
+ `Verity ${moment} review: PASS.${viewUrl ? ` Report: ${viewUrl}` : ""}`
15557
+ );
15155
15558
  }
15156
15559
  function writeBlockMessage(moment, response) {
15157
15560
  const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
@@ -15191,14 +15594,14 @@ function writeBlockMessage(moment, response) {
15191
15594
  }
15192
15595
 
15193
15596
  // src/commands/init.ts
15194
- var import_node_fs22 = require("node:fs");
15597
+ var import_node_fs24 = require("node:fs");
15195
15598
  var import_promises12 = require("node:fs/promises");
15196
- var import_node_path16 = require("node:path");
15599
+ var import_node_path17 = require("node:path");
15197
15600
  var import_node_child_process9 = require("node:child_process");
15198
15601
 
15199
15602
  // src/commands/migrate.ts
15200
- var import_node_fs21 = require("node:fs");
15201
- var import_node_path15 = require("node:path");
15603
+ var import_node_fs23 = require("node:fs");
15604
+ var import_node_path16 = require("node:path");
15202
15605
  var import_node_child_process8 = require("node:child_process");
15203
15606
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
15204
15607
  function defaultNpmRemover(pkg) {
@@ -15234,12 +15637,12 @@ async function runMigration(opts = {}) {
15234
15637
  return { actions, migrated: actions.length > 0 };
15235
15638
  }
15236
15639
  function migrateProjectDir(root, actions) {
15237
- const gateDir = (0, import_node_path15.join)(root, ".gate");
15238
- const verityDir = (0, import_node_path15.join)(root, ".verity");
15239
- if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) {
15640
+ const gateDir = (0, import_node_path16.join)(root, ".gate");
15641
+ const verityDir = (0, import_node_path16.join)(root, ".verity");
15642
+ if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) {
15240
15643
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
15241
15644
  }
15242
- if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
15645
+ if ((0, import_node_fs23.existsSync)(gateDir) && (0, import_node_fs23.existsSync)(verityDir)) {
15243
15646
  return migrateProjectDirCarry(gateDir, verityDir, actions);
15244
15647
  }
15245
15648
  return false;
@@ -15260,13 +15663,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
15260
15663
  }
15261
15664
  }
15262
15665
  if (moved) {
15263
- if ((0, import_node_fs21.existsSync)(gateDir)) {
15666
+ if ((0, import_node_fs23.existsSync)(gateDir)) {
15264
15667
  const carried = carryLegacyContents(gateDir, verityDir);
15265
15668
  if (carried > 0) {
15266
15669
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
15267
15670
  }
15268
15671
  try {
15269
- (0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
15672
+ (0, import_node_fs23.rmSync)(gateDir, { recursive: true, force: true });
15270
15673
  } catch {
15271
15674
  }
15272
15675
  }
@@ -15282,18 +15685,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
15282
15685
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
15283
15686
  }
15284
15687
  try {
15285
- (0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
15688
+ (0, import_node_fs23.rmSync)(gateDir, { recursive: true, force: true });
15286
15689
  } catch {
15287
15690
  }
15288
15691
  return carried > 0;
15289
15692
  }
15290
15693
  function migrateGlobalCredentials(home, actions) {
15291
15694
  if (!home) return;
15292
- const gateCreds = (0, import_node_path15.join)(home, ".gate", "credentials");
15293
- const verityCreds = (0, import_node_path15.join)(home, ".verity", "credentials");
15294
- if (!(0, import_node_fs21.existsSync)(gateCreds)) return;
15295
- if (!(0, import_node_fs21.existsSync)(verityCreds)) {
15296
- (0, import_node_fs21.mkdirSync)((0, import_node_path15.join)(home, ".verity"), { recursive: true });
15695
+ const gateCreds = (0, import_node_path16.join)(home, ".gate", "credentials");
15696
+ const verityCreds = (0, import_node_path16.join)(home, ".verity", "credentials");
15697
+ if (!(0, import_node_fs23.existsSync)(gateCreds)) return;
15698
+ if (!(0, import_node_fs23.existsSync)(verityCreds)) {
15699
+ (0, import_node_fs23.mkdirSync)((0, import_node_path16.join)(home, ".verity"), { recursive: true });
15297
15700
  moveFile(gateCreds, verityCreds);
15298
15701
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
15299
15702
  return;
@@ -15315,8 +15718,8 @@ async function migrateLegacyHooks(root, actions) {
15315
15718
  }
15316
15719
  }
15317
15720
  async function migrateClaudeMd(root, actions) {
15318
- const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
15319
- const hadLegacyBlock = (0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15721
+ const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
15722
+ const hadLegacyBlock = (0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15320
15723
  if (!hadLegacyBlock) return;
15321
15724
  try {
15322
15725
  await ensureClaudeMdPointer(root);
@@ -15326,9 +15729,9 @@ async function migrateClaudeMd(root, actions) {
15326
15729
  }
15327
15730
  }
15328
15731
  function migrateStandardFile(root, actions) {
15329
- const gateMd = (0, import_node_path15.join)(root, "GATE.md");
15330
- const verityMd = (0, import_node_path15.join)(root, "VERITY.md");
15331
- if (!(0, import_node_fs21.existsSync)(gateMd) || (0, import_node_fs21.existsSync)(verityMd)) return;
15732
+ const gateMd = (0, import_node_path16.join)(root, "GATE.md");
15733
+ const verityMd = (0, import_node_path16.join)(root, "VERITY.md");
15734
+ if (!(0, import_node_fs23.existsSync)(gateMd) || (0, import_node_fs23.existsSync)(verityMd)) return;
15332
15735
  let moved = false;
15333
15736
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
15334
15737
  try {
@@ -15340,7 +15743,7 @@ function migrateStandardFile(root, actions) {
15340
15743
  if (!moved) moveFile(gateMd, verityMd);
15341
15744
  const content = readFileSyncSafe(verityMd);
15342
15745
  const refreshed = content.split("GATE.md").join("VERITY.md");
15343
- if (refreshed !== content) (0, import_node_fs21.writeFileSync)(verityMd, refreshed);
15746
+ if (refreshed !== content) (0, import_node_fs23.writeFileSync)(verityMd, refreshed);
15344
15747
  actions.push("Renamed GATE.md \u2192 VERITY.md");
15345
15748
  }
15346
15749
  function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
@@ -15374,14 +15777,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
15374
15777
  }
15375
15778
  if (toAppend.length > 0) {
15376
15779
  const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
15377
- (0, import_node_fs21.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
15780
+ (0, import_node_fs23.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
15378
15781
  }
15379
- (0, import_node_fs21.rmSync)(gateCreds, { force: true });
15782
+ (0, import_node_fs23.rmSync)(gateCreds, { force: true });
15380
15783
  return toAppend.length;
15381
15784
  }
15382
15785
  function readFileSyncSafe(path) {
15383
15786
  try {
15384
- return (0, import_node_fs21.readFileSync)(path, "utf-8");
15787
+ return (0, import_node_fs23.readFileSync)(path, "utf-8");
15385
15788
  } catch {
15386
15789
  return "";
15387
15790
  }
@@ -15396,35 +15799,35 @@ function hasStagedChanges(root) {
15396
15799
  }
15397
15800
  function moveDir(from, to) {
15398
15801
  try {
15399
- (0, import_node_fs21.renameSync)(from, to);
15802
+ (0, import_node_fs23.renameSync)(from, to);
15400
15803
  } catch (err) {
15401
15804
  if (err.code !== "EXDEV") throw err;
15402
- (0, import_node_fs21.cpSync)(from, to, { recursive: true });
15403
- (0, import_node_fs21.rmSync)(from, { recursive: true, force: true });
15805
+ (0, import_node_fs23.cpSync)(from, to, { recursive: true });
15806
+ (0, import_node_fs23.rmSync)(from, { recursive: true, force: true });
15404
15807
  }
15405
15808
  }
15406
15809
  function moveFile(from, to) {
15407
15810
  try {
15408
- (0, import_node_fs21.renameSync)(from, to);
15811
+ (0, import_node_fs23.renameSync)(from, to);
15409
15812
  } catch (err) {
15410
15813
  if (err.code !== "EXDEV") throw err;
15411
- (0, import_node_fs21.cpSync)(from, to);
15412
- (0, import_node_fs21.rmSync)(from, { force: true });
15814
+ (0, import_node_fs23.cpSync)(from, to);
15815
+ (0, import_node_fs23.rmSync)(from, { force: true });
15413
15816
  }
15414
15817
  }
15415
15818
  function carryLegacyContents(gateDir, verityDir) {
15416
15819
  let copied = 0;
15417
15820
  const walk = (relDir) => {
15418
- const srcDir = (0, import_node_path15.join)(gateDir, relDir);
15419
- for (const entry of (0, import_node_fs21.readdirSync)(srcDir)) {
15420
- const rel = relDir ? (0, import_node_path15.join)(relDir, entry) : entry;
15421
- const src = (0, import_node_path15.join)(gateDir, rel);
15422
- const dest = (0, import_node_path15.join)(verityDir, rel);
15423
- if ((0, import_node_fs21.statSync)(src).isDirectory()) {
15821
+ const srcDir = (0, import_node_path16.join)(gateDir, relDir);
15822
+ for (const entry of (0, import_node_fs23.readdirSync)(srcDir)) {
15823
+ const rel = relDir ? (0, import_node_path16.join)(relDir, entry) : entry;
15824
+ const src = (0, import_node_path16.join)(gateDir, rel);
15825
+ const dest = (0, import_node_path16.join)(verityDir, rel);
15826
+ if ((0, import_node_fs23.statSync)(src).isDirectory()) {
15424
15827
  walk(rel);
15425
- } else if (!(0, import_node_fs21.existsSync)(dest)) {
15426
- (0, import_node_fs21.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15427
- (0, import_node_fs21.cpSync)(src, dest);
15828
+ } else if (!(0, import_node_fs23.existsSync)(dest)) {
15829
+ (0, import_node_fs23.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
15830
+ (0, import_node_fs23.cpSync)(src, dest);
15428
15831
  copied++;
15429
15832
  }
15430
15833
  }
@@ -15433,22 +15836,22 @@ function carryLegacyContents(gateDir, verityDir) {
15433
15836
  return copied;
15434
15837
  }
15435
15838
  async function needsMigration(root = repoRoot()) {
15436
- const gateDir = (0, import_node_path15.join)(root, ".gate");
15437
- const verityDir = (0, import_node_path15.join)(root, ".verity");
15438
- if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) return true;
15439
- if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
15440
- if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(gateDir, "credentials")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(verityDir, "credentials"))) {
15839
+ const gateDir = (0, import_node_path16.join)(root, ".gate");
15840
+ const verityDir = (0, import_node_path16.join)(root, ".verity");
15841
+ if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) return true;
15842
+ if ((0, import_node_fs23.existsSync)(gateDir) && (0, import_node_fs23.existsSync)(verityDir)) {
15843
+ if ((0, import_node_fs23.existsSync)((0, import_node_path16.join)(gateDir, "credentials")) && !(0, import_node_fs23.existsSync)((0, import_node_path16.join)(verityDir, "credentials"))) {
15441
15844
  return true;
15442
15845
  }
15443
- if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(gateDir, "memory")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(verityDir, "memory"))) {
15846
+ if ((0, import_node_fs23.existsSync)((0, import_node_path16.join)(gateDir, "memory")) && !(0, import_node_fs23.existsSync)((0, import_node_path16.join)(verityDir, "memory"))) {
15444
15847
  return true;
15445
15848
  }
15446
15849
  }
15447
- const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
15448
- if ((0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15850
+ const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
15851
+ if ((0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15449
15852
  return true;
15450
15853
  }
15451
- if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(root, "GATE.md")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(root, "VERITY.md"))) {
15854
+ if ((0, import_node_fs23.existsSync)((0, import_node_path16.join)(root, "GATE.md")) && !(0, import_node_fs23.existsSync)((0, import_node_path16.join)(root, "VERITY.md"))) {
15452
15855
  return true;
15453
15856
  }
15454
15857
  if (await hasLegacyHooksAt(root)) return true;
@@ -15476,15 +15879,15 @@ function registerMigrateCommand(program2) {
15476
15879
  // src/commands/init.ts
15477
15880
  function resolveDataDir() {
15478
15881
  const candidates = [
15479
- (0, import_node_path16.join)(__dirname, "..", "data"),
15882
+ (0, import_node_path17.join)(__dirname, "..", "data"),
15480
15883
  // installed: node_modules/@codacy/verity-cli/data
15481
- (0, import_node_path16.join)(__dirname, "..", "..", "data"),
15884
+ (0, import_node_path17.join)(__dirname, "..", "..", "data"),
15482
15885
  // edge case: nested resolution
15483
- (0, import_node_path16.join)(process.cwd(), "cli", "data")
15886
+ (0, import_node_path17.join)(process.cwd(), "cli", "data")
15484
15887
  // local dev: running from repo root
15485
15888
  ];
15486
15889
  for (const candidate of candidates) {
15487
- if ((0, import_node_fs22.existsSync)((0, import_node_path16.join)(candidate, "skills"))) {
15890
+ if ((0, import_node_fs24.existsSync)((0, import_node_path17.join)(candidate, "skills"))) {
15488
15891
  return candidate;
15489
15892
  }
15490
15893
  }
@@ -15500,7 +15903,7 @@ function registerInitCommand(program2) {
15500
15903
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
15501
15904
  const force = opts.force ?? false;
15502
15905
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
15503
- const isProject = projectMarkers.some((m) => (0, import_node_fs22.existsSync)(m));
15906
+ const isProject = projectMarkers.some((m) => (0, import_node_fs24.existsSync)(m));
15504
15907
  if (!isProject) {
15505
15908
  printError("No project detected in the current directory.");
15506
15909
  printInfo('Run "verity init" from your project root.');
@@ -15563,21 +15966,21 @@ function registerInitCommand(program2) {
15563
15966
  console.log("");
15564
15967
  printInfo("Installing skills...");
15565
15968
  const dataDir = resolveDataDir();
15566
- const skillsSource = (0, import_node_path16.join)(dataDir, "skills");
15969
+ const skillsSource = (0, import_node_path17.join)(dataDir, "skills");
15567
15970
  const skillsDest = ".claude/skills";
15568
15971
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
15569
15972
  let skillsInstalled = 0;
15570
15973
  for (const skill of skills) {
15571
- const src = (0, import_node_path16.join)(skillsSource, skill);
15572
- const dest = (0, import_node_path16.join)(skillsDest, skill);
15573
- if (!(0, import_node_fs22.existsSync)(src)) {
15974
+ const src = (0, import_node_path17.join)(skillsSource, skill);
15975
+ const dest = (0, import_node_path17.join)(skillsDest, skill);
15976
+ if (!(0, import_node_fs24.existsSync)(src)) {
15574
15977
  printWarn(` Skill data not found: ${skill}`);
15575
15978
  continue;
15576
15979
  }
15577
- if ((0, import_node_fs22.existsSync)(dest) && !force) {
15578
- const srcSkill = (0, import_node_path16.join)(src, "SKILL.md");
15579
- const destSkill = (0, import_node_path16.join)(dest, "SKILL.md");
15580
- if ((0, import_node_fs22.existsSync)(destSkill)) {
15980
+ if ((0, import_node_fs24.existsSync)(dest) && !force) {
15981
+ const srcSkill = (0, import_node_path17.join)(src, "SKILL.md");
15982
+ const destSkill = (0, import_node_path17.join)(dest, "SKILL.md");
15983
+ if ((0, import_node_fs24.existsSync)(destSkill)) {
15581
15984
  try {
15582
15985
  const srcContent = await (0, import_promises12.readFile)(srcSkill, "utf-8");
15583
15986
  const destContent = await (0, import_promises12.readFile)(destSkill, "utf-8");
@@ -15601,6 +16004,7 @@ function registerInitCommand(program2) {
15601
16004
  await writeSettings(hookResult.data);
15602
16005
  printInfo(" Stop hook: verity analyze \u2713");
15603
16006
  printInfo(" Intent hook: verity intent capture \u2713");
16007
+ printInfo(" Baseline hook: verity baseline capture \u2713");
15604
16008
  } else {
15605
16009
  printWarn(` ${hookResult.error}`);
15606
16010
  printInfo(' Run "verity hooks install --force" to overwrite.');
@@ -15613,7 +16017,7 @@ function registerInitCommand(program2) {
15613
16017
  } catch (err) {
15614
16018
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
15615
16019
  }
15616
- const globalVerityDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".verity");
16020
+ const globalVerityDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".verity");
15617
16021
  await (0, import_promises12.mkdir)(globalVerityDir, { recursive: true });
15618
16022
  console.log("");
15619
16023
  printInfo("Verity initialized!");
@@ -15636,8 +16040,8 @@ function registerInitCommand(program2) {
15636
16040
  }
15637
16041
 
15638
16042
  // src/commands/uninstall.ts
15639
- var import_node_fs23 = require("node:fs");
15640
- var import_node_path17 = require("node:path");
16043
+ var import_node_fs25 = require("node:fs");
16044
+ var import_node_path18 = require("node:path");
15641
16045
  var SKILL_NAMES = [
15642
16046
  "verity-setup",
15643
16047
  "verity-analyze",
@@ -15656,11 +16060,11 @@ function registerUninstallCommand(program2) {
15656
16060
  const actions = [];
15657
16061
  const skillsRoot = projectPath(".claude/skills");
15658
16062
  for (const name of SKILL_NAMES) {
15659
- const dir = (0, import_node_path17.join)(skillsRoot, name);
15660
- if ((0, import_node_fs23.existsSync)(dir)) {
16063
+ const dir = (0, import_node_path18.join)(skillsRoot, name);
16064
+ if ((0, import_node_fs25.existsSync)(dir)) {
15661
16065
  actions.push({
15662
16066
  label: `Remove .claude/skills/${name}/`,
15663
- apply: () => (0, import_node_fs23.rmSync)(dir, { recursive: true, force: true })
16067
+ apply: () => (0, import_node_fs25.rmSync)(dir, { recursive: true, force: true })
15664
16068
  });
15665
16069
  }
15666
16070
  }
@@ -15674,24 +16078,24 @@ function registerUninstallCommand(program2) {
15674
16078
  });
15675
16079
  }
15676
16080
  const verityDir = projectPath(VERITY_DIR);
15677
- if ((0, import_node_fs23.existsSync)(verityDir)) {
16081
+ if ((0, import_node_fs25.existsSync)(verityDir)) {
15678
16082
  actions.push({
15679
16083
  label: `Remove ${VERITY_DIR}/`,
15680
- apply: () => (0, import_node_fs23.rmSync)(verityDir, { recursive: true, force: true })
16084
+ apply: () => (0, import_node_fs25.rmSync)(verityDir, { recursive: true, force: true })
15681
16085
  });
15682
16086
  }
15683
16087
  if (!keepVerityMd) {
15684
16088
  const verityMd = projectPath(VERITY_MD_FILE);
15685
- if ((0, import_node_fs23.existsSync)(verityMd)) {
16089
+ if ((0, import_node_fs25.existsSync)(verityMd)) {
15686
16090
  actions.push({
15687
16091
  label: `Remove ${VERITY_MD_FILE}`,
15688
- apply: () => (0, import_node_fs23.rmSync)(verityMd, { force: true })
16092
+ apply: () => (0, import_node_fs25.rmSync)(verityMd, { force: true })
15689
16093
  });
15690
16094
  }
15691
16095
  }
15692
16096
  const cleanupEmptyDir = (path) => {
15693
- if ((0, import_node_fs23.existsSync)(path) && (0, import_node_fs23.statSync)(path).isDirectory() && (0, import_node_fs23.readdirSync)(path).length === 0) {
15694
- (0, import_node_fs23.rmdirSync)(path);
16097
+ if ((0, import_node_fs25.existsSync)(path) && (0, import_node_fs25.statSync)(path).isDirectory() && (0, import_node_fs25.readdirSync)(path).length === 0) {
16098
+ (0, import_node_fs25.rmdirSync)(path);
15695
16099
  }
15696
16100
  };
15697
16101
  actions.push({
@@ -15702,11 +16106,11 @@ function registerUninstallCommand(program2) {
15702
16106
  }
15703
16107
  });
15704
16108
  const home = process.env.HOME ?? "";
15705
- const globalVerityDir = (0, import_node_path17.join)(home, ".verity");
15706
- if (purgeGlobal && (0, import_node_fs23.existsSync)(globalVerityDir)) {
16109
+ const globalVerityDir = (0, import_node_path18.join)(home, ".verity");
16110
+ if (purgeGlobal && (0, import_node_fs25.existsSync)(globalVerityDir)) {
15707
16111
  actions.push({
15708
16112
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
15709
- apply: () => (0, import_node_fs23.rmSync)(globalVerityDir, { recursive: true, force: true })
16113
+ apply: () => (0, import_node_fs25.rmSync)(globalVerityDir, { recursive: true, force: true })
15710
16114
  });
15711
16115
  }
15712
16116
  if (actions.length === 0) {
@@ -15900,8 +16304,8 @@ function registerTaskCommands(program2) {
15900
16304
  }
15901
16305
 
15902
16306
  // src/commands/reset.ts
15903
- var import_node_fs24 = require("node:fs");
15904
- var import_node_path18 = require("node:path");
16307
+ var import_node_fs26 = require("node:fs");
16308
+ var import_node_path19 = require("node:path");
15905
16309
  function registerResetCommand(program2) {
15906
16310
  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) => {
15907
16311
  const globals = program2.opts();
@@ -15938,11 +16342,11 @@ function registerResetCommand(program2) {
15938
16342
  }
15939
16343
  const cacheDir = projectPath(CACHE_DIR);
15940
16344
  let purged = 0;
15941
- if ((0, import_node_fs24.existsSync)(cacheDir)) {
15942
- for (const entry of (0, import_node_fs24.readdirSync)(cacheDir)) {
16345
+ if ((0, import_node_fs26.existsSync)(cacheDir)) {
16346
+ for (const entry of (0, import_node_fs26.readdirSync)(cacheDir)) {
15943
16347
  if (entry.startsWith("pending-")) {
15944
16348
  try {
15945
- (0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(cacheDir, entry));
16349
+ (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(cacheDir, entry));
15946
16350
  purged++;
15947
16351
  } catch {
15948
16352
  }
@@ -15957,19 +16361,19 @@ function registerResetCommand(program2) {
15957
16361
  projectPath(`${VERITY_DIR}/.last-analysis`)
15958
16362
  ];
15959
16363
  for (const file of filesToClear) {
15960
- if ((0, import_node_fs24.existsSync)(file)) {
16364
+ if ((0, import_node_fs26.existsSync)(file)) {
15961
16365
  try {
15962
- (0, import_node_fs24.writeFileSync)(file, "");
16366
+ (0, import_node_fs26.writeFileSync)(file, "");
15963
16367
  } catch {
15964
16368
  }
15965
16369
  }
15966
16370
  }
15967
16371
  if (opts.all) {
15968
16372
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
15969
- if ((0, import_node_fs24.existsSync)(logsDir)) {
15970
- for (const entry of (0, import_node_fs24.readdirSync)(logsDir)) {
16373
+ if ((0, import_node_fs26.existsSync)(logsDir)) {
16374
+ for (const entry of (0, import_node_fs26.readdirSync)(logsDir)) {
15971
16375
  try {
15972
- (0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(logsDir, entry));
16376
+ (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(logsDir, entry));
15973
16377
  } catch {
15974
16378
  }
15975
16379
  }
@@ -15980,10 +16384,29 @@ function registerResetCommand(program2) {
15980
16384
  });
15981
16385
  }
15982
16386
 
16387
+ // src/lib/run-mode.ts
16388
+ function parseAutonomousEnv(raw) {
16389
+ if (raw === void 0) return void 0;
16390
+ const v = raw.trim().toLowerCase();
16391
+ if (v === "") return void 0;
16392
+ if (v === "0" || v === "false" || v === "off" || v === "no") return false;
16393
+ return true;
16394
+ }
16395
+ function resolveRunMode(inputs = {}) {
16396
+ if (inputs.autonomousFlag === true) return "autonomous";
16397
+ if (inputs.autonomousFlag === false) return "interactive";
16398
+ const env = inputs.env ?? process.env;
16399
+ const envDecision = parseAutonomousEnv(env.VERITY_AUTONOMOUS);
16400
+ if (envDecision !== void 0) return envDecision ? "autonomous" : "interactive";
16401
+ const isTTY = inputs.isTTY ?? Boolean(process.stdin?.isTTY);
16402
+ return isTTY ? "interactive" : "autonomous";
16403
+ }
16404
+
15983
16405
  // src/commands/reflect.ts
15984
16406
  function registerReflectCommand(program2) {
15985
- program2.command("reflect").description("Capture learnings \u2014 auto-extract or submit a human reflection").option("--user-input <text>", "Your reflection (becomes a high-confidence knowledge node)").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--task-id <id>", "Task to reflect on (defaults to current task)").action(async (opts) => {
16407
+ program2.command("reflect").description("Capture learnings \u2014 auto-extract or submit a human reflection").option("--user-input <text>", "The reflection to record (the agent-drafted or user-confirmed text)").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--task-id <id>", "Task to reflect on (defaults to current task)").option("--autonomous", "Record the drafted reflection without a confirm step (auto-detected from TTY / VERITY_AUTONOMOUS when omitted)").action(async (opts) => {
15986
16408
  const globals = program2.opts();
16409
+ const mode = resolveRunMode({ autonomousFlag: opts.autonomous });
15987
16410
  await ensureMemoryDir();
15988
16411
  const tokenResult = await resolveToken(globals.token);
15989
16412
  if (!tokenResult.ok) {
@@ -16074,7 +16497,9 @@ function registerReflectCommand(program2) {
16074
16497
  const nodeId = result.data.node_id;
16075
16498
  const filePath = result.data.file_path;
16076
16499
  printInfo(`Queued: ${nodeId} (will sync to .verity/memory/${filePath} on next analysis).`);
16077
- printInfo("Your reflection is now part of the project knowledge base (confidence: 100%).");
16500
+ printInfo(
16501
+ mode === "autonomous" ? "Recorded autonomously (no confirm step) into the project knowledge base." : "Recorded your confirmed reflection into the project knowledge base."
16502
+ );
16078
16503
  if (taskResolution === "none") {
16079
16504
  printInfo("(No task context \u2014 reflection lives at the project level.)");
16080
16505
  }
@@ -16207,7 +16632,7 @@ function registerRunCommand(program2) {
16207
16632
 
16208
16633
  // src/lib/telemetry.ts
16209
16634
  var import_promises13 = require("node:fs/promises");
16210
- var import_node_path19 = require("node:path");
16635
+ var import_node_path20 = require("node:path");
16211
16636
  var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
16212
16637
  var GITIGNORE_FILE = ".gitignore";
16213
16638
  var GITIGNORE_ENTRY = ".claude/settings.local.json";
@@ -16245,7 +16670,7 @@ async function readSettingsLocal() {
16245
16670
  }
16246
16671
  async function writeSettingsLocal(settings) {
16247
16672
  const file = projectPath(SETTINGS_LOCAL_FILE2);
16248
- await (0, import_promises13.mkdir)((0, import_node_path19.dirname)(file), { recursive: true });
16673
+ await (0, import_promises13.mkdir)((0, import_node_path20.dirname)(file), { recursive: true });
16249
16674
  await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
16250
16675
  }
16251
16676
  async function ensureGitignore() {
@@ -16341,7 +16766,7 @@ function registerTelemetryCommands(program2) {
16341
16766
  }
16342
16767
 
16343
16768
  // src/cli.ts
16344
- program.name("verity").description("CLI for Verity quality gate service").version("0.23.2").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16769
+ program.name("verity").description("CLI for Verity quality gate service").version("0.24.0-experimental.6a390ac").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16345
16770
  registerAuthCommands(program);
16346
16771
  registerHooksCommands(program);
16347
16772
  registerIntentCommands(program);
@@ -16350,6 +16775,7 @@ registerConfigCommands(program);
16350
16775
  registerStatusCommand(program);
16351
16776
  registerFeedbackCommand(program);
16352
16777
  registerAnalyzeCommand(program);
16778
+ registerBaselineCommands(program);
16353
16779
  registerReviewCommand(program);
16354
16780
  registerGuardCommand(program);
16355
16781
  registerInitCommand(program);