@codacy/verity-cli 0.21.0 → 0.23.0

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 +499 -117
  2. package/package.json +3 -2
package/bin/verity.js CHANGED
@@ -10648,6 +10648,10 @@ async function apiRequest(options) {
10648
10648
  if (token) {
10649
10649
  headers["Authorization"] = `Bearer ${token}`;
10650
10650
  }
10651
+ const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
10652
+ if (testMockScenario) {
10653
+ headers["X-Verity-Mock-Scenario"] = testMockScenario;
10654
+ }
10651
10655
  printVerbose(`${method} ${url}`, verbose);
10652
10656
  let serializedBody;
10653
10657
  if (body !== void 0 && body !== null) {
@@ -10719,6 +10723,20 @@ async function apiRequest(options) {
10719
10723
  });
10720
10724
  return { ok: true, data };
10721
10725
  }
10726
+ function analyzeRequest(options) {
10727
+ return apiRequest({
10728
+ method: "POST",
10729
+ path: "/analyze",
10730
+ serviceUrl: options.serviceUrl,
10731
+ token: options.token,
10732
+ body: options.body,
10733
+ timeout: options.timeout,
10734
+ cmd: options.cmd,
10735
+ verbose: options.verbose,
10736
+ retry: options.retry,
10737
+ encodeBody: true
10738
+ });
10739
+ }
10722
10740
 
10723
10741
  // src/commands/auth.ts
10724
10742
  function registerAuthCommands(program2) {
@@ -10830,8 +10848,21 @@ var VERITY_INTENT_HOOK = {
10830
10848
  type: "command",
10831
10849
  command: "verity intent capture"
10832
10850
  };
10851
+ var GUARD_TIMEOUT = 300;
10852
+ function buildGuardHook(on) {
10853
+ return {
10854
+ type: "command",
10855
+ command: `verity guard --on ${on.join(",")}`,
10856
+ timeout: GUARD_TIMEOUT,
10857
+ statusMessage: "Running Verity git-moment review"
10858
+ };
10859
+ }
10833
10860
  var VERITY_STOP_RE = /(?:^|[\/\s"'])verity\s+analyze\b/;
10834
10861
  var VERITY_INTENT_RE = /(?:^|[\/\s"'])verity\s+intent\s+capture\b/;
10862
+ var VERITY_GUARD_RE = /(?:^|[\/\s"'])verity\s+guard\b/;
10863
+ function isVerityGuardHook(entry) {
10864
+ return VERITY_GUARD_RE.test(entry.command ?? "");
10865
+ }
10835
10866
  var LEGACY_STOP_RE = /(?:^|[\/\s"'])gate\s+analyze\b/;
10836
10867
  var LEGACY_INTENT_RE = /(?:^|[\/\s"'])gate\s+intent\s+capture\b/;
10837
10868
  function isVerityStopHook(entry) {
@@ -10843,7 +10874,7 @@ function isVerityIntentHook(entry) {
10843
10874
  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");
10844
10875
  }
10845
10876
  function isVerityHook(entry) {
10846
- return isVerityStopHook(entry) || isVerityIntentHook(entry);
10877
+ return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry);
10847
10878
  }
10848
10879
  function isCurrentVerityStopHook(entry) {
10849
10880
  const c = entry.command ?? "";
@@ -10854,7 +10885,7 @@ function isCurrentVerityIntentHook(entry) {
10854
10885
  return VERITY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh");
10855
10886
  }
10856
10887
  function isCurrentVerityHook(entry) {
10857
- return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry);
10888
+ return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry);
10858
10889
  }
10859
10890
  function settingsHasLegacyHook(settings) {
10860
10891
  for (const groups of Object.values(settings.hooks ?? {})) {
@@ -10894,12 +10925,16 @@ async function readAllSettings() {
10894
10925
  async function checkAllVerityHooks() {
10895
10926
  let stop = false;
10896
10927
  let intent = false;
10928
+ let guard = false;
10929
+ let guardOn = [];
10897
10930
  for (const settings of await readAllSettings()) {
10898
10931
  const r = checkVerityHooks(settings);
10899
10932
  stop = stop || r.stop;
10900
10933
  intent = intent || r.intent;
10934
+ guard = guard || r.guard;
10935
+ if (r.guardOn.length > guardOn.length) guardOn = r.guardOn;
10901
10936
  }
10902
- return { stop, intent };
10937
+ return { stop, intent, guard, guardOn };
10903
10938
  }
10904
10939
  async function checkAllVerityHooksDetailed() {
10905
10940
  let stop = false;
@@ -10962,7 +10997,19 @@ function checkVerityHooks(settings) {
10962
10997
  const hasIntent = intentGroups.some(
10963
10998
  (g) => g.hooks?.some((h) => isCurrentVerityIntentHook(h))
10964
10999
  );
10965
- return { stop: hasStop, intent: hasIntent };
11000
+ let guard = false;
11001
+ let guardOn = [];
11002
+ for (const g of hooks["PreToolUse"] ?? []) {
11003
+ for (const h of g.hooks ?? []) {
11004
+ if (!isVerityGuardHook(h)) continue;
11005
+ guard = true;
11006
+ const m = (h.command ?? "").match(/--on[=\s]+([a-z,]+)/);
11007
+ if (m) {
11008
+ guardOn = m[1].split(",").filter((x) => x === "commit" || x === "push");
11009
+ }
11010
+ }
11011
+ }
11012
+ return { stop: hasStop, intent: hasIntent, guard, guardOn };
10966
11013
  }
10967
11014
  function installVerityHooks(settings, force, externalPresent = { stop: false, intent: false }) {
10968
11015
  const local = checkVerityHooks(settings);
@@ -11011,6 +11058,7 @@ function installVerityHooks(settings, force, externalPresent = { stop: false, in
11011
11058
  function removeVerityHooks(settings) {
11012
11059
  const newSettings = { ...settings };
11013
11060
  if (!newSettings.hooks) return newSettings;
11061
+ newSettings.hooks = { ...newSettings.hooks };
11014
11062
  for (const key of Object.keys(newSettings.hooks)) {
11015
11063
  const groups = newSettings.hooks[key];
11016
11064
  if (!groups) continue;
@@ -11027,12 +11075,60 @@ function removeVerityHooks(settings) {
11027
11075
  }
11028
11076
  return newSettings;
11029
11077
  }
11078
+ function reconcileMomentHooks(settings, moments, externalPresent = {
11079
+ stop: false,
11080
+ intent: false,
11081
+ guardOn: []
11082
+ }) {
11083
+ const s = removeVerityHooks(settings);
11084
+ if (!s.hooks) s.hooks = {};
11085
+ const push = (event, group) => {
11086
+ (s.hooks[event] ??= []).push(group);
11087
+ };
11088
+ if (!externalPresent.intent) push("UserPromptSubmit", { hooks: [VERITY_INTENT_HOOK] });
11089
+ if (moments.includes("stop") && !externalPresent.stop) {
11090
+ push("Stop", { hooks: [VERITY_STOP_HOOK] });
11091
+ }
11092
+ const on = [];
11093
+ if (moments.includes("pre-commit")) on.push("commit");
11094
+ if (moments.includes("pre-push")) on.push("push");
11095
+ const remaining = on.filter((m) => !externalPresent.guardOn.includes(m));
11096
+ if (remaining.length > 0) {
11097
+ push("PreToolUse", { matcher: "Bash", hooks: [buildGuardHook(remaining)] });
11098
+ }
11099
+ for (const k of Object.keys(s.hooks)) {
11100
+ if (s.hooks[k].length === 0) delete s.hooks[k];
11101
+ }
11102
+ if (Object.keys(s.hooks).length === 0) delete s.hooks;
11103
+ return s;
11104
+ }
11030
11105
 
11031
11106
  // src/commands/hooks.ts
11107
+ var ALL_MOMENTS = ["stop", "pre-commit", "pre-push"];
11108
+ function parseMoments(raw) {
11109
+ const seen = /* @__PURE__ */ new Set();
11110
+ for (const part of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
11111
+ if (ALL_MOMENTS.includes(part)) seen.add(part);
11112
+ }
11113
+ return ALL_MOMENTS.filter((m) => seen.has(m));
11114
+ }
11032
11115
  function registerHooksCommands(program2) {
11033
11116
  const hooks = program2.command("hooks").description("Manage Claude Code hook wiring");
11034
- hooks.command("install").description("Install Verity hooks into Claude Code settings").option("--force", "Overwrite existing Verity hooks").action(async (opts) => {
11117
+ hooks.command("install").description("Install Verity hooks into Claude Code settings").option("--force", "Overwrite existing Verity hooks").option("--moments <list>", "Reconcile to exactly these moments: stop,pre-commit,pre-push").action(async (opts) => {
11035
11118
  const force = opts.force ?? false;
11119
+ if (opts.moments != null) {
11120
+ const moments = parseMoments(opts.moments);
11121
+ const external = await checkAllVerityHooks();
11122
+ const settings2 = reconcileMomentHooks(await readSettings(), moments, external);
11123
+ await writeSettings(settings2);
11124
+ const status = await checkAllVerityHooks();
11125
+ printInfo("Verity hooks reconciled in .claude/settings.json:");
11126
+ printInfo(` Stop (verity analyze): ${moments.includes("stop") ? "on" : "off"}`);
11127
+ printInfo(` Pre-commit gate: ${status.guardOn.includes("commit") ? "on" : "off"}`);
11128
+ printInfo(` Pre-push/PR gate: ${status.guardOn.includes("push") ? "on" : "off"}`);
11129
+ printInfo(" UserPromptSubmit (verity intent capture): on");
11130
+ return;
11131
+ }
11036
11132
  const detail = await checkAllVerityHooksDetailed();
11037
11133
  const present = { stop: detail.stop, intent: detail.intent };
11038
11134
  if (detail.legacyOnly) {
@@ -11070,7 +11166,12 @@ function registerHooksCommands(program2) {
11070
11166
  const status = await checkAllVerityHooks();
11071
11167
  printInfo(`Stop hook (verity analyze): ${status.stop ? "installed" : "not installed"}`);
11072
11168
  printInfo(`Intent hook (verity intent capture): ${status.intent ? "installed" : "not installed"}`);
11073
- if (!status.stop || !status.intent) {
11169
+ const gates = status.guard ? status.guardOn.join(", ") : "none";
11170
+ printInfo(`Git-moment gate (verity guard): ${status.guard ? `installed [${gates}]` : "not installed"}`);
11171
+ if (!status.stop && !status.guard) {
11172
+ printWarn('No analysis moment is active (neither Stop nor a git-moment gate) \u2014 code changes will not be reviewed. Run "verity hooks install --moments stop" (or /verity-setup) to enable one.');
11173
+ }
11174
+ if (!status.intent) {
11074
11175
  printWarn('Run "verity hooks install" to install missing hooks.');
11075
11176
  }
11076
11177
  });
@@ -12437,6 +12538,12 @@ function registerStatusCommand(program2) {
12437
12538
  printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
12438
12539
  printInfo(`Languages: ${s.languages.join(", ")}`);
12439
12540
  }
12541
+ const hookStatus = await checkAllVerityHooks();
12542
+ const moments = [];
12543
+ if (hookStatus.stop) moments.push("stop");
12544
+ if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
12545
+ if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
12546
+ printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
12440
12547
  if (mem.recent_runs) {
12441
12548
  const r = mem.recent_runs;
12442
12549
  printInfo("");
@@ -12677,6 +12784,31 @@ function getChangedFiles() {
12677
12784
  const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12678
12785
  return { files: filtered, hasRecentCommitFiles };
12679
12786
  }
12787
+ function getStagedFiles() {
12788
+ return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12789
+ }
12790
+ function getPushRangeFiles() {
12791
+ const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12792
+ const resolvers = [
12793
+ () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
12794
+ () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
12795
+ () => {
12796
+ const branch = execGit("git rev-parse --abbrev-ref HEAD");
12797
+ return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
12798
+ }
12799
+ ];
12800
+ for (const resolve of resolvers) {
12801
+ const range = resolve();
12802
+ if (range) return { files: diff(range), range };
12803
+ }
12804
+ const baseline = readBaselineSha();
12805
+ if (baseline) {
12806
+ const files = diff(`${baseline}..HEAD`);
12807
+ if (files.length > 0) return { files, range: `${baseline}..HEAD` };
12808
+ }
12809
+ const last = diff("HEAD~1..HEAD");
12810
+ return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
12811
+ }
12680
12812
  function getWorktreeFiles() {
12681
12813
  const result = [];
12682
12814
  const worktreeDir = ".claude/worktrees";
@@ -14107,7 +14239,7 @@ async function runSeed(opts) {
14107
14239
  var MAX_ASSISTANT_RESPONSE_CHARS_PLAN = 32768;
14108
14240
  var MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT = 8e3;
14109
14241
  async function readStopHookStdin() {
14110
- const empty = { assistantMessage: null, stopReason: null, transcriptPath: null };
14242
+ const empty = { assistantMessage: null, stopReason: null, transcriptPath: null, sessionId: null };
14111
14243
  try {
14112
14244
  if (process.stdin.isTTY) return empty;
14113
14245
  const chunks = [];
@@ -14125,7 +14257,8 @@ async function readStopHookStdin() {
14125
14257
  resolve({
14126
14258
  assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
14127
14259
  stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
14128
- transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null
14260
+ transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null,
14261
+ sessionId: typeof data.session_id === "string" ? data.session_id : null
14129
14262
  });
14130
14263
  } catch {
14131
14264
  resolve(empty);
@@ -14158,7 +14291,7 @@ async function runAnalyze(opts, globals) {
14158
14291
  process.chdir(repoRoot());
14159
14292
  } catch {
14160
14293
  }
14161
- const { assistantMessage: assistantResponse, stopReason, transcriptPath } = await readStopHookStdin();
14294
+ const { assistantMessage: assistantResponse, stopReason, transcriptPath, sessionId } = await readStopHookStdin();
14162
14295
  const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
14163
14296
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
14164
14297
  const analyzable = filterAnalyzable(allChanged);
@@ -14191,7 +14324,7 @@ async function runAnalyze(opts, globals) {
14191
14324
  if (!urlResult.ok) {
14192
14325
  passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14193
14326
  }
14194
- const sessionIdForMemory = process.env.CLAUDE_SESSION_ID ?? "";
14327
+ const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
14195
14328
  let contextFilePaths = [];
14196
14329
  let predictedMode;
14197
14330
  try {
@@ -14389,7 +14522,7 @@ async function runAnalyze(opts, globals) {
14389
14522
  context: {
14390
14523
  agent_model: process.env.CLAUDE_MODEL ?? "unknown",
14391
14524
  agent_harness: "claude-code",
14392
- session_id: process.env.CLAUDE_SESSION_ID ?? "unknown",
14525
+ session_id: sessionId || process.env.CLAUDE_SESSION_ID || "unknown",
14393
14526
  iteration
14394
14527
  },
14395
14528
  ...memoryManifest && { memory_manifest: memoryManifest },
@@ -14430,16 +14563,13 @@ async function runAnalyze(opts, globals) {
14430
14563
  requestBody.intent_context = intentContext;
14431
14564
  }
14432
14565
  const ANALYZE_TIMEOUT_MS = 1e5;
14433
- let result = await apiRequest({
14434
- method: "POST",
14435
- path: "/analyze",
14566
+ let result = await analyzeRequest({
14436
14567
  serviceUrl: urlResult.data,
14437
14568
  token: tokenResult.data.token,
14438
14569
  body: requestBody,
14439
14570
  verbose: globals.verbose,
14440
14571
  timeout: ANALYZE_TIMEOUT_MS,
14441
- cmd: "analyze",
14442
- encodeBody: true
14572
+ cmd: "analyze"
14443
14573
  });
14444
14574
  if (!result.ok && shouldWarmRetryAnalyze(result)) {
14445
14575
  logEvent("analyze_warm_retry", {
@@ -14456,17 +14586,14 @@ async function runAnalyze(opts, globals) {
14456
14586
  timeout: 15e3,
14457
14587
  cmd: "analyze_warmup"
14458
14588
  });
14459
- result = await apiRequest({
14460
- method: "POST",
14461
- path: "/analyze",
14589
+ result = await analyzeRequest({
14462
14590
  serviceUrl: urlResult.data,
14463
14591
  token: tokenResult.data.token,
14464
14592
  body: requestBody,
14465
14593
  verbose: globals.verbose,
14466
14594
  timeout: ANALYZE_TIMEOUT_MS,
14467
14595
  cmd: "analyze",
14468
- retry: true,
14469
- encodeBody: true
14596
+ retry: true
14470
14597
  });
14471
14598
  }
14472
14599
  if (!result.ok) {
@@ -14727,8 +14854,8 @@ async function runReview(opts, globals) {
14727
14854
  for (const p of specPaths) {
14728
14855
  if (!(0, import_node_fs19.existsSync)(p)) continue;
14729
14856
  try {
14730
- const { readFileSync: readFileSync10 } = await import("node:fs");
14731
- const content = readFileSync10(p, "utf-8");
14857
+ const { readFileSync: readFileSync11 } = await import("node:fs");
14858
+ const content = readFileSync11(p, "utf-8");
14732
14859
  specs.push({ path: p, content: content.slice(0, 10240) });
14733
14860
  } catch {
14734
14861
  }
@@ -14758,14 +14885,13 @@ async function runReview(opts, globals) {
14758
14885
  if (plans.length > 0) intentContext.plans = plans;
14759
14886
  requestBody.intent_context = intentContext;
14760
14887
  }
14761
- const result = await apiRequest({
14762
- method: "POST",
14763
- path: "/analyze",
14888
+ const result = await analyzeRequest({
14764
14889
  serviceUrl: urlResult.data,
14765
14890
  token: tokenResult.data.token,
14766
14891
  body: requestBody,
14767
14892
  verbose: globals.verbose,
14768
- timeout: 12e4
14893
+ timeout: 12e4,
14894
+ cmd: "review"
14769
14895
  });
14770
14896
  if (!result.ok) {
14771
14897
  printError(`Service error: ${result.error}`);
@@ -14785,15 +14911,270 @@ async function runReview(opts, globals) {
14785
14911
  process.exit(0);
14786
14912
  }
14787
14913
 
14914
+ // src/commands/guard.ts
14915
+ var import_node_fs20 = require("node:fs");
14916
+ var import_node_path14 = require("node:path");
14917
+ var GUARD_BLOCK_CAP = 2;
14918
+ var GUARD_ITER_FILE = (0, import_node_path14.join)(VERITY_DIR, ".guard-iteration");
14919
+ function readPreToolUseStdin() {
14920
+ const empty = { command: "", cwd: null, sessionId: null };
14921
+ return new Promise((resolve) => {
14922
+ try {
14923
+ if (process.stdin.isTTY) return resolve(empty);
14924
+ const chunks = [];
14925
+ let timer;
14926
+ let settled = false;
14927
+ const onData = (c) => chunks.push(c);
14928
+ const settle = (value) => {
14929
+ if (settled) return;
14930
+ settled = true;
14931
+ if (timer) clearTimeout(timer);
14932
+ process.stdin.removeListener("data", onData);
14933
+ process.stdin.removeListener("end", onEnd);
14934
+ process.stdin.removeListener("error", onError);
14935
+ process.stdin.pause();
14936
+ resolve(value);
14937
+ };
14938
+ const onEnd = () => {
14939
+ try {
14940
+ const data = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
14941
+ settle({
14942
+ command: typeof data?.tool_input?.command === "string" ? data.tool_input.command : "",
14943
+ cwd: typeof data?.cwd === "string" ? data.cwd : null,
14944
+ sessionId: typeof data?.session_id === "string" ? data.session_id : null
14945
+ });
14946
+ } catch {
14947
+ settle(empty);
14948
+ }
14949
+ };
14950
+ const onError = () => settle(empty);
14951
+ timer = setTimeout(() => settle(empty), 2e3);
14952
+ process.stdin.on("data", onData);
14953
+ process.stdin.on("end", onEnd);
14954
+ process.stdin.on("error", onError);
14955
+ process.stdin.resume();
14956
+ } catch {
14957
+ resolve(empty);
14958
+ }
14959
+ });
14960
+ }
14961
+ function buildCommandRe(head) {
14962
+ return new RegExp(`(?:^|[\\s;&|(])${head}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${head}`);
14963
+ }
14964
+ var GIT_GLOBAL_OPTS = "(?:\\s+(?:-[Cc]\\s+\\S+|--?[\\w-]+(?:=\\S+)?))*";
14965
+ var COMMIT_RE = buildCommandRe(`git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`);
14966
+ var PUSH_RE = buildCommandRe(`git${GIT_GLOBAL_OPTS}\\s+push\\b`);
14967
+ var GH_PR_RE = buildCommandRe(`gh${GIT_GLOBAL_OPTS}\\s+pr\\s+create\\b`);
14968
+ function classifyCommand(command, on) {
14969
+ let commit = false;
14970
+ let push = false;
14971
+ for (const seg of (command ?? "").split(/&&|\|\||;|\n/)) {
14972
+ if (/--dry-run\b/.test(seg)) continue;
14973
+ if (COMMIT_RE.test(seg)) commit = true;
14974
+ if (PUSH_RE.test(seg) || GH_PR_RE.test(seg)) push = true;
14975
+ }
14976
+ if (commit && on.includes("commit")) return "pre-commit";
14977
+ if (push && on.includes("push")) return "pre-push";
14978
+ return null;
14979
+ }
14980
+ function readIterMap() {
14981
+ try {
14982
+ const raw = JSON.parse((0, import_node_fs20.readFileSync)(GUARD_ITER_FILE, "utf-8"));
14983
+ if (raw && typeof raw === "object") {
14984
+ if (typeof raw.moment === "string" && typeof raw.count === "number") {
14985
+ return { [raw.moment]: raw.count };
14986
+ }
14987
+ const out = {};
14988
+ for (const m of ["pre-commit", "pre-push"]) {
14989
+ if (typeof raw[m] === "number") out[m] = raw[m];
14990
+ }
14991
+ return out;
14992
+ }
14993
+ } catch {
14994
+ }
14995
+ return {};
14996
+ }
14997
+ function readIter(moment) {
14998
+ return readIterMap()[moment] ?? 0;
14999
+ }
15000
+ function writeIter(moment, count) {
15001
+ try {
15002
+ (0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
15003
+ const map = readIterMap();
15004
+ map[moment] = count;
15005
+ (0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15006
+ } catch {
15007
+ }
15008
+ }
15009
+ function resetIter(moment) {
15010
+ try {
15011
+ const map = readIterMap();
15012
+ if (!(moment in map)) return;
15013
+ delete map[moment];
15014
+ if (Object.keys(map).length === 0) {
15015
+ if ((0, import_node_fs20.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs20.unlinkSync)(GUARD_ITER_FILE);
15016
+ } else {
15017
+ (0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
15018
+ (0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15019
+ }
15020
+ } catch {
15021
+ }
15022
+ }
15023
+ function registerGuardCommand(program2) {
15024
+ program2.command("guard").description("Git-moment gate: review staged/to-push changes before a commit/push (PreToolUse hook)").option("--on <moments>", "Which git moments to gate: commit,push", "commit,push").option("--json", "Output raw JSON response (debug)").action(async (opts) => {
15025
+ const globals = program2.opts();
15026
+ try {
15027
+ await runGuard(opts, globals);
15028
+ } catch {
15029
+ process.exit(0);
15030
+ }
15031
+ });
15032
+ }
15033
+ function getMomentFiles(moment) {
15034
+ return moment === "pre-commit" ? getStagedFiles() : getPushRangeFiles().files;
15035
+ }
15036
+ function buildGuardRequest(moment, files, iter, sessionId) {
15037
+ const analyzable = filterAnalyzable(files);
15038
+ const securityFiles = filterSecurity(files);
15039
+ let staticResults;
15040
+ if (isCodacyAvailable()) {
15041
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs20.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
15042
+ staticResults = runCodacyAnalysis(scannable);
15043
+ } else {
15044
+ staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
15045
+ }
15046
+ const codeDelta = collectCodeDelta(files);
15047
+ if (codeDelta.total_files === 0) return null;
15048
+ const trigger = moment === "pre-commit" ? "hook:pre-commit" : "hook:pre-push";
15049
+ const requestBody = {
15050
+ static_results: staticResults,
15051
+ code_delta: codeDelta,
15052
+ changed_files: files,
15053
+ standard_version: "latest",
15054
+ context: {
15055
+ agent_model: process.env.CLAUDE_MODEL ?? "unknown",
15056
+ agent_harness: "claude-code",
15057
+ // Prefer the session id Claude Code passes on the hook payload; fall back
15058
+ // to the env var, then 'unknown'.
15059
+ session_id: sessionId ?? process.env.CLAUDE_SESSION_ID ?? "unknown",
15060
+ trigger,
15061
+ iteration: iter + 1
15062
+ }
15063
+ };
15064
+ const specs = discoverSpecs();
15065
+ const plans = discoverPlans();
15066
+ if (specs.length > 0 || plans.length > 0) {
15067
+ const intentContext = {};
15068
+ if (specs.length > 0) intentContext.specs = specs;
15069
+ if (plans.length > 0) intentContext.plans = plans;
15070
+ requestBody.intent_context = intentContext;
15071
+ }
15072
+ return requestBody;
15073
+ }
15074
+ function allowWithNotice(moment, decision) {
15075
+ resetIter(moment);
15076
+ if (decision === "WARN") {
15077
+ process.stderr.write(`${YELLOW}Verity ${moment}: WARN \u2014 proceeding. See findings in the report.${NC}
15078
+ `);
15079
+ }
15080
+ process.exit(0);
15081
+ }
15082
+ async function runGuard(opts, globals) {
15083
+ const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
15084
+ const { command, cwd, sessionId } = await readPreToolUseStdin();
15085
+ if (cwd && (0, import_node_fs20.existsSync)(cwd)) {
15086
+ try {
15087
+ process.chdir(cwd);
15088
+ } catch {
15089
+ }
15090
+ }
15091
+ const moment = classifyCommand(command, on);
15092
+ if (!moment) process.exit(0);
15093
+ const iter = readIter(moment);
15094
+ if (iter >= GUARD_BLOCK_CAP) {
15095
+ process.stderr.write(`${DIM}Verity ${moment}: ${GUARD_BLOCK_CAP} review cycles reached \u2014 allowing through.${NC}
15096
+ `);
15097
+ resetIter(moment);
15098
+ process.exit(0);
15099
+ }
15100
+ const files = getMomentFiles(moment);
15101
+ if (files.length === 0) process.exit(0);
15102
+ const tokenResult = await resolveToken(globals.token);
15103
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
15104
+ if (!tokenResult.ok || !urlResult.ok) process.exit(0);
15105
+ const requestBody = buildGuardRequest(moment, files, iter, sessionId);
15106
+ if (!requestBody) process.exit(0);
15107
+ const result = await analyzeRequest({
15108
+ serviceUrl: urlResult.data,
15109
+ token: tokenResult.data.token,
15110
+ body: requestBody,
15111
+ verbose: globals.verbose,
15112
+ timeout: 29e4,
15113
+ cmd: "guard"
15114
+ });
15115
+ if (!result.ok) {
15116
+ process.stderr.write(`${DIM}Verity ${moment}: service unavailable \u2014 allowing commit/push (${result.error}).${NC}
15117
+ `);
15118
+ process.exit(0);
15119
+ }
15120
+ if (opts.json) {
15121
+ process.stdout.write(JSON.stringify(result.data) + "\n");
15122
+ }
15123
+ const response = result.data;
15124
+ const decision = response.gate_decision ?? "PASS";
15125
+ if (decision === "FAIL") {
15126
+ writeIter(moment, iter + 1);
15127
+ writeBlockMessage(moment, response);
15128
+ process.exit(2);
15129
+ }
15130
+ allowWithNotice(moment, decision);
15131
+ }
15132
+ function writeBlockMessage(moment, response) {
15133
+ const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
15134
+ const verb = moment === "pre-commit" ? "commit" : "push";
15135
+ const assessment = response.assessment;
15136
+ const narrative = assessment?.narrative ?? "";
15137
+ const findings = response.findings ?? [];
15138
+ const viewUrl = response.view_url ?? "";
15139
+ process.stderr.write(`${RED}${BOLD}\u2501\u2501\u2501 Verity ${label2} gate: FAIL \u2501\u2501\u2501${NC}
15140
+
15141
+ `);
15142
+ if (narrative) process.stderr.write(`${DIM}${narrative}${NC}
15143
+
15144
+ `);
15145
+ const agentFindings = findings.filter((f) => f.scope !== "pre-existing");
15146
+ if (agentFindings.length > 0) {
15147
+ process.stderr.write(`${BOLD}${agentFindings.length} issue(s) in the ${verb} \u2014 fix critical/high severity before ${verb === "commit" ? "committing" : "pushing"}:${NC}
15148
+
15149
+ `);
15150
+ for (const f of agentFindings) {
15151
+ const severity = (f.severity ?? "").toUpperCase();
15152
+ const fix = f.fix;
15153
+ process.stderr.write(`[${severity}] ${f.title ?? ""}
15154
+ `);
15155
+ process.stderr.write(` File: ${f.file ?? ""}:${f.line ?? ""}
15156
+ `);
15157
+ process.stderr.write(` Fix: ${fix?.description ?? "See description"}
15158
+
15159
+ `);
15160
+ }
15161
+ }
15162
+ if (viewUrl) process.stderr.write(`${CYAN}Full report: ${viewUrl}${NC}
15163
+
15164
+ `);
15165
+ process.stderr.write(`${BOLD}Fix these, then re-run the ${verb}.${NC}
15166
+ `);
15167
+ }
15168
+
14788
15169
  // src/commands/init.ts
14789
- var import_node_fs21 = require("node:fs");
15170
+ var import_node_fs22 = require("node:fs");
14790
15171
  var import_promises12 = require("node:fs/promises");
14791
- var import_node_path15 = require("node:path");
15172
+ var import_node_path16 = require("node:path");
14792
15173
  var import_node_child_process9 = require("node:child_process");
14793
15174
 
14794
15175
  // src/commands/migrate.ts
14795
- var import_node_fs20 = require("node:fs");
14796
- var import_node_path14 = require("node:path");
15176
+ var import_node_fs21 = require("node:fs");
15177
+ var import_node_path15 = require("node:path");
14797
15178
  var import_node_child_process8 = require("node:child_process");
14798
15179
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
14799
15180
  function defaultNpmRemover(pkg) {
@@ -14829,12 +15210,12 @@ async function runMigration(opts = {}) {
14829
15210
  return { actions, migrated: actions.length > 0 };
14830
15211
  }
14831
15212
  function migrateProjectDir(root, actions) {
14832
- const gateDir = (0, import_node_path14.join)(root, ".gate");
14833
- const verityDir = (0, import_node_path14.join)(root, ".verity");
14834
- if ((0, import_node_fs20.existsSync)(gateDir) && !(0, import_node_fs20.existsSync)(verityDir)) {
15213
+ const gateDir = (0, import_node_path15.join)(root, ".gate");
15214
+ const verityDir = (0, import_node_path15.join)(root, ".verity");
15215
+ if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) {
14835
15216
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
14836
15217
  }
14837
- if ((0, import_node_fs20.existsSync)(gateDir) && (0, import_node_fs20.existsSync)(verityDir)) {
15218
+ if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
14838
15219
  return migrateProjectDirCarry(gateDir, verityDir, actions);
14839
15220
  }
14840
15221
  return false;
@@ -14855,13 +15236,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
14855
15236
  }
14856
15237
  }
14857
15238
  if (moved) {
14858
- if ((0, import_node_fs20.existsSync)(gateDir)) {
15239
+ if ((0, import_node_fs21.existsSync)(gateDir)) {
14859
15240
  const carried = carryLegacyContents(gateDir, verityDir);
14860
15241
  if (carried > 0) {
14861
15242
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
14862
15243
  }
14863
15244
  try {
14864
- (0, import_node_fs20.rmSync)(gateDir, { recursive: true, force: true });
15245
+ (0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
14865
15246
  } catch {
14866
15247
  }
14867
15248
  }
@@ -14877,18 +15258,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
14877
15258
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
14878
15259
  }
14879
15260
  try {
14880
- (0, import_node_fs20.rmSync)(gateDir, { recursive: true, force: true });
15261
+ (0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
14881
15262
  } catch {
14882
15263
  }
14883
15264
  return carried > 0;
14884
15265
  }
14885
15266
  function migrateGlobalCredentials(home, actions) {
14886
15267
  if (!home) return;
14887
- const gateCreds = (0, import_node_path14.join)(home, ".gate", "credentials");
14888
- const verityCreds = (0, import_node_path14.join)(home, ".verity", "credentials");
14889
- if (!(0, import_node_fs20.existsSync)(gateCreds)) return;
14890
- if (!(0, import_node_fs20.existsSync)(verityCreds)) {
14891
- (0, import_node_fs20.mkdirSync)((0, import_node_path14.join)(home, ".verity"), { recursive: true });
15268
+ const gateCreds = (0, import_node_path15.join)(home, ".gate", "credentials");
15269
+ const verityCreds = (0, import_node_path15.join)(home, ".verity", "credentials");
15270
+ if (!(0, import_node_fs21.existsSync)(gateCreds)) return;
15271
+ if (!(0, import_node_fs21.existsSync)(verityCreds)) {
15272
+ (0, import_node_fs21.mkdirSync)((0, import_node_path15.join)(home, ".verity"), { recursive: true });
14892
15273
  moveFile(gateCreds, verityCreds);
14893
15274
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
14894
15275
  return;
@@ -14910,8 +15291,8 @@ async function migrateLegacyHooks(root, actions) {
14910
15291
  }
14911
15292
  }
14912
15293
  async function migrateClaudeMd(root, actions) {
14913
- const claudeMd = (0, import_node_path14.join)(root, "CLAUDE.md");
14914
- const hadLegacyBlock = (0, import_node_fs20.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15294
+ const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
15295
+ const hadLegacyBlock = (0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
14915
15296
  if (!hadLegacyBlock) return;
14916
15297
  try {
14917
15298
  await ensureClaudeMdPointer(root);
@@ -14921,9 +15302,9 @@ async function migrateClaudeMd(root, actions) {
14921
15302
  }
14922
15303
  }
14923
15304
  function migrateStandardFile(root, actions) {
14924
- const gateMd = (0, import_node_path14.join)(root, "GATE.md");
14925
- const verityMd = (0, import_node_path14.join)(root, "VERITY.md");
14926
- if (!(0, import_node_fs20.existsSync)(gateMd) || (0, import_node_fs20.existsSync)(verityMd)) return;
15305
+ const gateMd = (0, import_node_path15.join)(root, "GATE.md");
15306
+ const verityMd = (0, import_node_path15.join)(root, "VERITY.md");
15307
+ if (!(0, import_node_fs21.existsSync)(gateMd) || (0, import_node_fs21.existsSync)(verityMd)) return;
14927
15308
  let moved = false;
14928
15309
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
14929
15310
  try {
@@ -14935,7 +15316,7 @@ function migrateStandardFile(root, actions) {
14935
15316
  if (!moved) moveFile(gateMd, verityMd);
14936
15317
  const content = readFileSyncSafe(verityMd);
14937
15318
  const refreshed = content.split("GATE.md").join("VERITY.md");
14938
- if (refreshed !== content) (0, import_node_fs20.writeFileSync)(verityMd, refreshed);
15319
+ if (refreshed !== content) (0, import_node_fs21.writeFileSync)(verityMd, refreshed);
14939
15320
  actions.push("Renamed GATE.md \u2192 VERITY.md");
14940
15321
  }
14941
15322
  function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
@@ -14969,14 +15350,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
14969
15350
  }
14970
15351
  if (toAppend.length > 0) {
14971
15352
  const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
14972
- (0, import_node_fs20.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
15353
+ (0, import_node_fs21.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
14973
15354
  }
14974
- (0, import_node_fs20.rmSync)(gateCreds, { force: true });
15355
+ (0, import_node_fs21.rmSync)(gateCreds, { force: true });
14975
15356
  return toAppend.length;
14976
15357
  }
14977
15358
  function readFileSyncSafe(path) {
14978
15359
  try {
14979
- return (0, import_node_fs20.readFileSync)(path, "utf-8");
15360
+ return (0, import_node_fs21.readFileSync)(path, "utf-8");
14980
15361
  } catch {
14981
15362
  return "";
14982
15363
  }
@@ -14991,35 +15372,35 @@ function hasStagedChanges(root) {
14991
15372
  }
14992
15373
  function moveDir(from, to) {
14993
15374
  try {
14994
- (0, import_node_fs20.renameSync)(from, to);
15375
+ (0, import_node_fs21.renameSync)(from, to);
14995
15376
  } catch (err) {
14996
15377
  if (err.code !== "EXDEV") throw err;
14997
- (0, import_node_fs20.cpSync)(from, to, { recursive: true });
14998
- (0, import_node_fs20.rmSync)(from, { recursive: true, force: true });
15378
+ (0, import_node_fs21.cpSync)(from, to, { recursive: true });
15379
+ (0, import_node_fs21.rmSync)(from, { recursive: true, force: true });
14999
15380
  }
15000
15381
  }
15001
15382
  function moveFile(from, to) {
15002
15383
  try {
15003
- (0, import_node_fs20.renameSync)(from, to);
15384
+ (0, import_node_fs21.renameSync)(from, to);
15004
15385
  } catch (err) {
15005
15386
  if (err.code !== "EXDEV") throw err;
15006
- (0, import_node_fs20.cpSync)(from, to);
15007
- (0, import_node_fs20.rmSync)(from, { force: true });
15387
+ (0, import_node_fs21.cpSync)(from, to);
15388
+ (0, import_node_fs21.rmSync)(from, { force: true });
15008
15389
  }
15009
15390
  }
15010
15391
  function carryLegacyContents(gateDir, verityDir) {
15011
15392
  let copied = 0;
15012
15393
  const walk = (relDir) => {
15013
- const srcDir = (0, import_node_path14.join)(gateDir, relDir);
15014
- for (const entry of (0, import_node_fs20.readdirSync)(srcDir)) {
15015
- const rel = relDir ? (0, import_node_path14.join)(relDir, entry) : entry;
15016
- const src = (0, import_node_path14.join)(gateDir, rel);
15017
- const dest = (0, import_node_path14.join)(verityDir, rel);
15018
- if ((0, import_node_fs20.statSync)(src).isDirectory()) {
15394
+ const srcDir = (0, import_node_path15.join)(gateDir, relDir);
15395
+ for (const entry of (0, import_node_fs21.readdirSync)(srcDir)) {
15396
+ const rel = relDir ? (0, import_node_path15.join)(relDir, entry) : entry;
15397
+ const src = (0, import_node_path15.join)(gateDir, rel);
15398
+ const dest = (0, import_node_path15.join)(verityDir, rel);
15399
+ if ((0, import_node_fs21.statSync)(src).isDirectory()) {
15019
15400
  walk(rel);
15020
- } else if (!(0, import_node_fs20.existsSync)(dest)) {
15021
- (0, import_node_fs20.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
15022
- (0, import_node_fs20.cpSync)(src, dest);
15401
+ } else if (!(0, import_node_fs21.existsSync)(dest)) {
15402
+ (0, import_node_fs21.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15403
+ (0, import_node_fs21.cpSync)(src, dest);
15023
15404
  copied++;
15024
15405
  }
15025
15406
  }
@@ -15028,22 +15409,22 @@ function carryLegacyContents(gateDir, verityDir) {
15028
15409
  return copied;
15029
15410
  }
15030
15411
  async function needsMigration(root = repoRoot()) {
15031
- const gateDir = (0, import_node_path14.join)(root, ".gate");
15032
- const verityDir = (0, import_node_path14.join)(root, ".verity");
15033
- if ((0, import_node_fs20.existsSync)(gateDir) && !(0, import_node_fs20.existsSync)(verityDir)) return true;
15034
- if ((0, import_node_fs20.existsSync)(gateDir) && (0, import_node_fs20.existsSync)(verityDir)) {
15035
- if ((0, import_node_fs20.existsSync)((0, import_node_path14.join)(gateDir, "credentials")) && !(0, import_node_fs20.existsSync)((0, import_node_path14.join)(verityDir, "credentials"))) {
15412
+ const gateDir = (0, import_node_path15.join)(root, ".gate");
15413
+ const verityDir = (0, import_node_path15.join)(root, ".verity");
15414
+ if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) return true;
15415
+ if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
15416
+ 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"))) {
15036
15417
  return true;
15037
15418
  }
15038
- if ((0, import_node_fs20.existsSync)((0, import_node_path14.join)(gateDir, "memory")) && !(0, import_node_fs20.existsSync)((0, import_node_path14.join)(verityDir, "memory"))) {
15419
+ 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"))) {
15039
15420
  return true;
15040
15421
  }
15041
15422
  }
15042
- const claudeMd = (0, import_node_path14.join)(root, "CLAUDE.md");
15043
- if ((0, import_node_fs20.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15423
+ const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
15424
+ if ((0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15044
15425
  return true;
15045
15426
  }
15046
- if ((0, import_node_fs20.existsSync)((0, import_node_path14.join)(root, "GATE.md")) && !(0, import_node_fs20.existsSync)((0, import_node_path14.join)(root, "VERITY.md"))) {
15427
+ 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"))) {
15047
15428
  return true;
15048
15429
  }
15049
15430
  if (await hasLegacyHooksAt(root)) return true;
@@ -15071,15 +15452,15 @@ function registerMigrateCommand(program2) {
15071
15452
  // src/commands/init.ts
15072
15453
  function resolveDataDir() {
15073
15454
  const candidates = [
15074
- (0, import_node_path15.join)(__dirname, "..", "data"),
15455
+ (0, import_node_path16.join)(__dirname, "..", "data"),
15075
15456
  // installed: node_modules/@codacy/verity-cli/data
15076
- (0, import_node_path15.join)(__dirname, "..", "..", "data"),
15457
+ (0, import_node_path16.join)(__dirname, "..", "..", "data"),
15077
15458
  // edge case: nested resolution
15078
- (0, import_node_path15.join)(process.cwd(), "cli", "data")
15459
+ (0, import_node_path16.join)(process.cwd(), "cli", "data")
15079
15460
  // local dev: running from repo root
15080
15461
  ];
15081
15462
  for (const candidate of candidates) {
15082
- if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(candidate, "skills"))) {
15463
+ if ((0, import_node_fs22.existsSync)((0, import_node_path16.join)(candidate, "skills"))) {
15083
15464
  return candidate;
15084
15465
  }
15085
15466
  }
@@ -15095,7 +15476,7 @@ function registerInitCommand(program2) {
15095
15476
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
15096
15477
  const force = opts.force ?? false;
15097
15478
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
15098
- const isProject = projectMarkers.some((m) => (0, import_node_fs21.existsSync)(m));
15479
+ const isProject = projectMarkers.some((m) => (0, import_node_fs22.existsSync)(m));
15099
15480
  if (!isProject) {
15100
15481
  printError("No project detected in the current directory.");
15101
15482
  printInfo('Run "verity init" from your project root.');
@@ -15158,21 +15539,21 @@ function registerInitCommand(program2) {
15158
15539
  console.log("");
15159
15540
  printInfo("Installing skills...");
15160
15541
  const dataDir = resolveDataDir();
15161
- const skillsSource = (0, import_node_path15.join)(dataDir, "skills");
15542
+ const skillsSource = (0, import_node_path16.join)(dataDir, "skills");
15162
15543
  const skillsDest = ".claude/skills";
15163
15544
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
15164
15545
  let skillsInstalled = 0;
15165
15546
  for (const skill of skills) {
15166
- const src = (0, import_node_path15.join)(skillsSource, skill);
15167
- const dest = (0, import_node_path15.join)(skillsDest, skill);
15168
- if (!(0, import_node_fs21.existsSync)(src)) {
15547
+ const src = (0, import_node_path16.join)(skillsSource, skill);
15548
+ const dest = (0, import_node_path16.join)(skillsDest, skill);
15549
+ if (!(0, import_node_fs22.existsSync)(src)) {
15169
15550
  printWarn(` Skill data not found: ${skill}`);
15170
15551
  continue;
15171
15552
  }
15172
- if ((0, import_node_fs21.existsSync)(dest) && !force) {
15173
- const srcSkill = (0, import_node_path15.join)(src, "SKILL.md");
15174
- const destSkill = (0, import_node_path15.join)(dest, "SKILL.md");
15175
- if ((0, import_node_fs21.existsSync)(destSkill)) {
15553
+ if ((0, import_node_fs22.existsSync)(dest) && !force) {
15554
+ const srcSkill = (0, import_node_path16.join)(src, "SKILL.md");
15555
+ const destSkill = (0, import_node_path16.join)(dest, "SKILL.md");
15556
+ if ((0, import_node_fs22.existsSync)(destSkill)) {
15176
15557
  try {
15177
15558
  const srcContent = await (0, import_promises12.readFile)(srcSkill, "utf-8");
15178
15559
  const destContent = await (0, import_promises12.readFile)(destSkill, "utf-8");
@@ -15208,7 +15589,7 @@ function registerInitCommand(program2) {
15208
15589
  } catch (err) {
15209
15590
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
15210
15591
  }
15211
- const globalVerityDir = (0, import_node_path15.join)(process.env.HOME ?? "", ".verity");
15592
+ const globalVerityDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".verity");
15212
15593
  await (0, import_promises12.mkdir)(globalVerityDir, { recursive: true });
15213
15594
  console.log("");
15214
15595
  printInfo("Verity initialized!");
@@ -15231,8 +15612,8 @@ function registerInitCommand(program2) {
15231
15612
  }
15232
15613
 
15233
15614
  // src/commands/uninstall.ts
15234
- var import_node_fs22 = require("node:fs");
15235
- var import_node_path16 = require("node:path");
15615
+ var import_node_fs23 = require("node:fs");
15616
+ var import_node_path17 = require("node:path");
15236
15617
  var SKILL_NAMES = [
15237
15618
  "verity-setup",
15238
15619
  "verity-analyze",
@@ -15251,11 +15632,11 @@ function registerUninstallCommand(program2) {
15251
15632
  const actions = [];
15252
15633
  const skillsRoot = projectPath(".claude/skills");
15253
15634
  for (const name of SKILL_NAMES) {
15254
- const dir = (0, import_node_path16.join)(skillsRoot, name);
15255
- if ((0, import_node_fs22.existsSync)(dir)) {
15635
+ const dir = (0, import_node_path17.join)(skillsRoot, name);
15636
+ if ((0, import_node_fs23.existsSync)(dir)) {
15256
15637
  actions.push({
15257
15638
  label: `Remove .claude/skills/${name}/`,
15258
- apply: () => (0, import_node_fs22.rmSync)(dir, { recursive: true, force: true })
15639
+ apply: () => (0, import_node_fs23.rmSync)(dir, { recursive: true, force: true })
15259
15640
  });
15260
15641
  }
15261
15642
  }
@@ -15269,24 +15650,24 @@ function registerUninstallCommand(program2) {
15269
15650
  });
15270
15651
  }
15271
15652
  const verityDir = projectPath(VERITY_DIR);
15272
- if ((0, import_node_fs22.existsSync)(verityDir)) {
15653
+ if ((0, import_node_fs23.existsSync)(verityDir)) {
15273
15654
  actions.push({
15274
15655
  label: `Remove ${VERITY_DIR}/`,
15275
- apply: () => (0, import_node_fs22.rmSync)(verityDir, { recursive: true, force: true })
15656
+ apply: () => (0, import_node_fs23.rmSync)(verityDir, { recursive: true, force: true })
15276
15657
  });
15277
15658
  }
15278
15659
  if (!keepVerityMd) {
15279
15660
  const verityMd = projectPath(VERITY_MD_FILE);
15280
- if ((0, import_node_fs22.existsSync)(verityMd)) {
15661
+ if ((0, import_node_fs23.existsSync)(verityMd)) {
15281
15662
  actions.push({
15282
15663
  label: `Remove ${VERITY_MD_FILE}`,
15283
- apply: () => (0, import_node_fs22.rmSync)(verityMd, { force: true })
15664
+ apply: () => (0, import_node_fs23.rmSync)(verityMd, { force: true })
15284
15665
  });
15285
15666
  }
15286
15667
  }
15287
15668
  const cleanupEmptyDir = (path) => {
15288
- if ((0, import_node_fs22.existsSync)(path) && (0, import_node_fs22.statSync)(path).isDirectory() && (0, import_node_fs22.readdirSync)(path).length === 0) {
15289
- (0, import_node_fs22.rmdirSync)(path);
15669
+ if ((0, import_node_fs23.existsSync)(path) && (0, import_node_fs23.statSync)(path).isDirectory() && (0, import_node_fs23.readdirSync)(path).length === 0) {
15670
+ (0, import_node_fs23.rmdirSync)(path);
15290
15671
  }
15291
15672
  };
15292
15673
  actions.push({
@@ -15297,11 +15678,11 @@ function registerUninstallCommand(program2) {
15297
15678
  }
15298
15679
  });
15299
15680
  const home = process.env.HOME ?? "";
15300
- const globalVerityDir = (0, import_node_path16.join)(home, ".verity");
15301
- if (purgeGlobal && (0, import_node_fs22.existsSync)(globalVerityDir)) {
15681
+ const globalVerityDir = (0, import_node_path17.join)(home, ".verity");
15682
+ if (purgeGlobal && (0, import_node_fs23.existsSync)(globalVerityDir)) {
15302
15683
  actions.push({
15303
15684
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
15304
- apply: () => (0, import_node_fs22.rmSync)(globalVerityDir, { recursive: true, force: true })
15685
+ apply: () => (0, import_node_fs23.rmSync)(globalVerityDir, { recursive: true, force: true })
15305
15686
  });
15306
15687
  }
15307
15688
  if (actions.length === 0) {
@@ -15495,8 +15876,8 @@ function registerTaskCommands(program2) {
15495
15876
  }
15496
15877
 
15497
15878
  // src/commands/reset.ts
15498
- var import_node_fs23 = require("node:fs");
15499
- var import_node_path17 = require("node:path");
15879
+ var import_node_fs24 = require("node:fs");
15880
+ var import_node_path18 = require("node:path");
15500
15881
  function registerResetCommand(program2) {
15501
15882
  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) => {
15502
15883
  const globals = program2.opts();
@@ -15533,11 +15914,11 @@ function registerResetCommand(program2) {
15533
15914
  }
15534
15915
  const cacheDir = projectPath(CACHE_DIR);
15535
15916
  let purged = 0;
15536
- if ((0, import_node_fs23.existsSync)(cacheDir)) {
15537
- for (const entry of (0, import_node_fs23.readdirSync)(cacheDir)) {
15917
+ if ((0, import_node_fs24.existsSync)(cacheDir)) {
15918
+ for (const entry of (0, import_node_fs24.readdirSync)(cacheDir)) {
15538
15919
  if (entry.startsWith("pending-")) {
15539
15920
  try {
15540
- (0, import_node_fs23.unlinkSync)((0, import_node_path17.join)(cacheDir, entry));
15921
+ (0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(cacheDir, entry));
15541
15922
  purged++;
15542
15923
  } catch {
15543
15924
  }
@@ -15552,19 +15933,19 @@ function registerResetCommand(program2) {
15552
15933
  projectPath(`${VERITY_DIR}/.last-analysis`)
15553
15934
  ];
15554
15935
  for (const file of filesToClear) {
15555
- if ((0, import_node_fs23.existsSync)(file)) {
15936
+ if ((0, import_node_fs24.existsSync)(file)) {
15556
15937
  try {
15557
- (0, import_node_fs23.writeFileSync)(file, "");
15938
+ (0, import_node_fs24.writeFileSync)(file, "");
15558
15939
  } catch {
15559
15940
  }
15560
15941
  }
15561
15942
  }
15562
15943
  if (opts.all) {
15563
15944
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
15564
- if ((0, import_node_fs23.existsSync)(logsDir)) {
15565
- for (const entry of (0, import_node_fs23.readdirSync)(logsDir)) {
15945
+ if ((0, import_node_fs24.existsSync)(logsDir)) {
15946
+ for (const entry of (0, import_node_fs24.readdirSync)(logsDir)) {
15566
15947
  try {
15567
- (0, import_node_fs23.unlinkSync)((0, import_node_path17.join)(logsDir, entry));
15948
+ (0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(logsDir, entry));
15568
15949
  } catch {
15569
15950
  }
15570
15951
  }
@@ -15802,7 +16183,7 @@ function registerRunCommand(program2) {
15802
16183
 
15803
16184
  // src/lib/telemetry.ts
15804
16185
  var import_promises13 = require("node:fs/promises");
15805
- var import_node_path18 = require("node:path");
16186
+ var import_node_path19 = require("node:path");
15806
16187
  var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
15807
16188
  var GITIGNORE_FILE = ".gitignore";
15808
16189
  var GITIGNORE_ENTRY = ".claude/settings.local.json";
@@ -15840,7 +16221,7 @@ async function readSettingsLocal() {
15840
16221
  }
15841
16222
  async function writeSettingsLocal(settings) {
15842
16223
  const file = projectPath(SETTINGS_LOCAL_FILE2);
15843
- await (0, import_promises13.mkdir)((0, import_node_path18.dirname)(file), { recursive: true });
16224
+ await (0, import_promises13.mkdir)((0, import_node_path19.dirname)(file), { recursive: true });
15844
16225
  await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
15845
16226
  }
15846
16227
  async function ensureGitignore() {
@@ -15936,7 +16317,7 @@ function registerTelemetryCommands(program2) {
15936
16317
  }
15937
16318
 
15938
16319
  // src/cli.ts
15939
- program.name("verity").description("CLI for Verity quality gate service").version("0.21.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16320
+ program.name("verity").description("CLI for Verity quality gate service").version("0.23.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
15940
16321
  registerAuthCommands(program);
15941
16322
  registerHooksCommands(program);
15942
16323
  registerIntentCommands(program);
@@ -15946,6 +16327,7 @@ registerStatusCommand(program);
15946
16327
  registerFeedbackCommand(program);
15947
16328
  registerAnalyzeCommand(program);
15948
16329
  registerReviewCommand(program);
16330
+ registerGuardCommand(program);
15949
16331
  registerInitCommand(program);
15950
16332
  registerUninstallCommand(program);
15951
16333
  registerTaskCommands(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "homepage": "https://verity.md",
6
6
  "repository": {
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "scripts": {
26
26
  "build": "node scripts/build.js",
27
- "test": "node --import tsx --test $(find tests -name '*.test.ts' | sort)",
27
+ "test": "node --import tsx --test $(find tests -name '*.test.ts' -not -path 'tests/e2e/*' | sort)",
28
+ "test:e2e": "node --import tsx --test --test-concurrency=1 tests/e2e/*.test.ts",
28
29
  "typecheck": "tsc --noEmit",
29
30
  "prepublishOnly": "npm run typecheck && npm run build"
30
31
  },