@cybedefend/vibedefend 1.4.5 → 1.4.7

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 (50) hide show
  1. package/dist/auth/auth.js +15 -2
  2. package/dist/auth/auth.js.map +1 -1
  3. package/dist/clients/claude-code.js +81 -30
  4. package/dist/clients/claude-code.js.map +1 -1
  5. package/dist/clients/codex.js +42 -15
  6. package/dist/clients/codex.js.map +1 -1
  7. package/dist/clients/cursor.js +8 -14
  8. package/dist/clients/cursor.js.map +1 -1
  9. package/dist/clients/mcp-url.js +42 -0
  10. package/dist/clients/mcp-url.js.map +1 -0
  11. package/dist/clients/unwire.js +1 -1
  12. package/dist/clients/unwire.js.map +1 -1
  13. package/dist/clients/vscode.js +9 -15
  14. package/dist/clients/vscode.js.map +1 -1
  15. package/dist/clients/windsurf.js +9 -14
  16. package/dist/clients/windsurf.js.map +1 -1
  17. package/dist/diagnostics.js +4 -2
  18. package/dist/diagnostics.js.map +1 -1
  19. package/dist/doctor.js +12 -2
  20. package/dist/doctor.js.map +1 -1
  21. package/dist/hook-runner.js +382 -136
  22. package/dist/hooks/install.js +2 -1
  23. package/dist/hooks/install.js.map +1 -1
  24. package/dist/hooks/runtime/api.js +29 -2
  25. package/dist/hooks/runtime/api.js.map +1 -1
  26. package/dist/hooks/runtime/fetch-rules.js +27 -1
  27. package/dist/hooks/runtime/fetch-rules.js.map +1 -1
  28. package/dist/hooks/runtime/guard-rules-cache.js +25 -3
  29. package/dist/hooks/runtime/guard-rules-cache.js.map +1 -1
  30. package/dist/hooks/runtime/judge.js +10 -2
  31. package/dist/hooks/runtime/judge.js.map +1 -1
  32. package/dist/hooks/runtime/resolve.js +12 -5
  33. package/dist/hooks/runtime/resolve.js.map +1 -1
  34. package/dist/hooks/runtime/usage-block.js +199 -0
  35. package/dist/hooks/runtime/usage-block.js.map +1 -0
  36. package/dist/install.js +95 -22
  37. package/dist/install.js.map +1 -1
  38. package/dist/json-file.js +115 -0
  39. package/dist/json-file.js.map +1 -0
  40. package/dist/link.js +5 -2
  41. package/dist/link.js.map +1 -1
  42. package/dist/login.js +12 -4
  43. package/dist/login.js.map +1 -1
  44. package/dist/refresh-hooks.js +1 -1
  45. package/dist/refresh-hooks.js.map +1 -1
  46. package/dist/status.js +5 -1
  47. package/dist/status.js.map +1 -1
  48. package/dist/utils.js +1 -20
  49. package/dist/utils.js.map +1 -1
  50. package/package.json +4 -2
@@ -4962,7 +4962,7 @@ function extractToolInputShape(input, client) {
4962
4962
  // src/hooks/runtime/resolve.ts
4963
4963
  import { execFileSync as execFileSync2 } from "node:child_process";
4964
4964
  import { createHash } from "node:crypto";
4965
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
4965
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
4966
4966
 
4967
4967
  // src/auth/auth-store.ts
4968
4968
  import { execFileSync } from "node:child_process";
@@ -5307,11 +5307,11 @@ async function resolveProjectByRepository(apiBase, token, q, deps = {}) {
5307
5307
  }
5308
5308
 
5309
5309
  // src/hooks/runtime/repo-link-cache.ts
5310
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
5310
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
5311
5311
  import { dirname as dirname2 } from "node:path";
5312
5312
 
5313
5313
  // src/utils.ts
5314
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
5314
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
5315
5315
  import { dirname, join as join2 } from "node:path";
5316
5316
  import { homedir as homedir3, platform } from "node:os";
5317
5317
 
@@ -5436,7 +5436,7 @@ function readRepoLinkCache(deps = {}) {
5436
5436
  const p = pathOf(deps);
5437
5437
  if (!existsSync3(p)) return {};
5438
5438
  try {
5439
- const raw = readFileSync3(p, "utf8");
5439
+ const raw = readFileSync2(p, "utf8");
5440
5440
  if (!raw.trim()) return {};
5441
5441
  const parsed = JSON.parse(raw);
5442
5442
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
@@ -5484,13 +5484,13 @@ function writeCachedRepoLink(key, entry, deps = {}) {
5484
5484
  const p = pathOf(deps);
5485
5485
  const dir = dirname2(p);
5486
5486
  if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
5487
- writeFileSync3(p, JSON.stringify(map, null, 2) + "\n");
5487
+ writeFileSync2(p, JSON.stringify(map, null, 2) + "\n");
5488
5488
  } catch {
5489
5489
  }
5490
5490
  }
5491
5491
 
5492
5492
  // src/hooks/runtime/config.ts
5493
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
5493
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
5494
5494
  import { homedir as homedir4 } from "node:os";
5495
5495
  import { join as join3 } from "node:path";
5496
5496
  function runtimeConfigPath() {
@@ -5500,7 +5500,7 @@ function loadRuntimeConfig(path) {
5500
5500
  const p = path ?? runtimeConfigPath();
5501
5501
  if (!existsSync4(p)) return null;
5502
5502
  try {
5503
- const raw = readFileSync4(p, "utf8");
5503
+ const raw = readFileSync3(p, "utf8");
5504
5504
  return JSON.parse(raw);
5505
5505
  } catch {
5506
5506
  return null;
@@ -5522,7 +5522,7 @@ async function readStdinJson() {
5522
5522
  }
5523
5523
 
5524
5524
  // src/hooks/runtime/trusted-api-base.ts
5525
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
5525
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
5526
5526
  import { homedir as homedir5 } from "node:os";
5527
5527
  import { join as join4 } from "node:path";
5528
5528
 
@@ -5571,7 +5571,7 @@ function normalizeOrigin(candidate) {
5571
5571
  function readUserRegionApiBases(path) {
5572
5572
  try {
5573
5573
  if (!existsSync5(path)) return [];
5574
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
5574
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
5575
5575
  if (!Array.isArray(parsed)) return [];
5576
5576
  return parsed.map(
5577
5577
  (entry) => entry && typeof entry === "object" ? entry.apiBase : null
@@ -5640,7 +5640,7 @@ function resolveProjectId(cwd = process.cwd(), deps = {}) {
5640
5640
  const configPath = `${cwd}/.cybedefend/config.json`;
5641
5641
  if (existsSync6(configPath)) {
5642
5642
  try {
5643
- const raw = readFileSync6(configPath, "utf8");
5643
+ const raw = readFileSync5(configPath, "utf8");
5644
5644
  const cfg = JSON.parse(raw);
5645
5645
  return {
5646
5646
  projectId: cfg.projectId ?? null,
@@ -5661,7 +5661,7 @@ function resolveToken(mcpName, opts = {}) {
5661
5661
  const platform2 = opts.platform ?? process.platform;
5662
5662
  const readKeychain = opts.readKeychain ?? readMacOSKeychain2;
5663
5663
  if (platform2 === "darwin") {
5664
- return resolveTokenMacOS(mcpName, readKeychain) ?? resolveTokenCodexMacOS(mcpName, opts.mcpUrl, readKeychain);
5664
+ return resolveTokenMacOS(mcpName, opts.mcpUrl, readKeychain) ?? resolveTokenCodexMacOS(mcpName, opts.mcpUrl, readKeychain);
5665
5665
  }
5666
5666
  if (platform2 === "linux") return resolveTokenLinux(mcpName);
5667
5667
  return null;
@@ -5692,16 +5692,18 @@ function readMacOSKeychain2(service, account) {
5692
5692
  return null;
5693
5693
  }
5694
5694
  }
5695
- function resolveTokenMacOS(mcpName, readKeychain) {
5695
+ function resolveTokenMacOS(mcpName, mcpUrl, readKeychain) {
5696
5696
  const blob = readKeychain("Claude Code-credentials");
5697
5697
  if (!blob) return null;
5698
5698
  try {
5699
5699
  const data = JSON.parse(blob);
5700
5700
  const oauth = data.mcpOAuth ?? {};
5701
5701
  for (const entry of Object.values(oauth)) {
5702
- if (entry?.serverName === mcpName && entry.accessToken) {
5703
- return entry.accessToken;
5702
+ if (entry?.serverName !== mcpName || !entry.accessToken) continue;
5703
+ if (mcpUrl && typeof entry.serverUrl === "string" && entry.serverUrl !== mcpUrl) {
5704
+ continue;
5704
5705
  }
5706
+ return entry.accessToken;
5705
5707
  }
5706
5708
  } catch {
5707
5709
  return blob || null;
@@ -5806,7 +5808,165 @@ async function resolveProjectIdWithApi(cwd = process.cwd(), deps = {}) {
5806
5808
  }
5807
5809
  }
5808
5810
 
5811
+ // src/hooks/runtime/usage-block.ts
5812
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
5813
+ import { homedir as homedir6 } from "node:os";
5814
+ import { dirname as dirname3, join as join5 } from "node:path";
5815
+ var BLOCKED_CODE = "VIBEDEFEND_BLOCKED";
5816
+ var USAGE_WARNING_HEADER = "x-vibedefend-usage-warning";
5817
+ var UNITS = ["injection", "judgment", "diff_scan"];
5818
+ var REASONS = [
5819
+ "free_tier_exhausted",
5820
+ "cap_reached",
5821
+ "unpaid",
5822
+ "product_inactive"
5823
+ ];
5824
+ var WARNINGS = [
5825
+ "approaching_limit",
5826
+ "limit_reached"
5827
+ ];
5828
+ function pick(allowed, value) {
5829
+ return allowed.find((v) => v === value);
5830
+ }
5831
+ async function readVibedefendBlock(res) {
5832
+ if (res.status !== 402) return null;
5833
+ let body;
5834
+ try {
5835
+ body = await res.json();
5836
+ } catch {
5837
+ return null;
5838
+ }
5839
+ if (!body || typeof body !== "object") return null;
5840
+ const root = body;
5841
+ const data = root.data && typeof root.data === "object" ? root.data : root;
5842
+ const source = data.code === BLOCKED_CODE ? data : root;
5843
+ if (source.code !== BLOCKED_CODE) return null;
5844
+ return {
5845
+ reason: pick(REASONS, source.reason) ?? "unknown",
5846
+ unit: pick(UNITS, source.unit) ?? "unknown"
5847
+ };
5848
+ }
5849
+ function readUsageWarning(res) {
5850
+ let raw;
5851
+ try {
5852
+ raw = res.headers?.get?.(USAGE_WARNING_HEADER);
5853
+ } catch {
5854
+ return null;
5855
+ }
5856
+ if (typeof raw !== "string") return null;
5857
+ const parts = raw.trim().split(":");
5858
+ if (parts.length !== 2) return null;
5859
+ const unit = pick(UNITS, parts[0]);
5860
+ const warning = pick(WARNINGS, parts[1]);
5861
+ if (!unit || !warning) return null;
5862
+ return { unit, warning };
5863
+ }
5864
+ var UNIT_LABEL = {
5865
+ injection: "Security rules injection",
5866
+ judgment: "Action Guards judgments",
5867
+ diff_scan: "Diff scans",
5868
+ unknown: "Part of VibeDefend"
5869
+ };
5870
+ var UNIT_EFFECT = {
5871
+ injection: "no security or business rules are added to your edits",
5872
+ judgment: "Action Guards are off for this session",
5873
+ diff_scan: "no diff scan can be started",
5874
+ unknown: "part of VibeDefend is paused"
5875
+ };
5876
+ function blockSentence(block) {
5877
+ const label = UNIT_LABEL[block.unit];
5878
+ const effect = UNIT_EFFECT[block.unit];
5879
+ switch (block.reason) {
5880
+ case "free_tier_exhausted":
5881
+ return `VibeDefend paused: ${label} used up this month's free tier, so ${effect}. An administrator of your organisation can add a card in the VibeDefend usage page to continue.`;
5882
+ case "cap_reached":
5883
+ return `VibeDefend paused: ${label} reached the monthly spending cap of your organisation, so ${effect}. An administrator can raise the cap in the VibeDefend usage page.`;
5884
+ case "unpaid":
5885
+ return "VibeDefend paused: your organisation's last VibeDefend invoice is unpaid, so rules, Action Guards and diff scans are off. An administrator can update the card in the VibeDefend usage page.";
5886
+ case "product_inactive":
5887
+ return "VibeDefend is not active for your organisation, so rules, Action Guards and diff scans are off. An administrator can activate it in the CybeDefend dashboard.";
5888
+ default:
5889
+ return `VibeDefend paused: ${label} is blocked for your organisation, so ${effect}. An administrator can check the VibeDefend usage page.`;
5890
+ }
5891
+ }
5892
+ function warningSentence(w) {
5893
+ const label = UNIT_LABEL[w.unit];
5894
+ if (w.warning === "approaching_limit") {
5895
+ return `VibeDefend: ${label} has used 80 % of this month's allowance of your organisation. An administrator can follow it in the VibeDefend usage page.`;
5896
+ }
5897
+ return `VibeDefend: ${label} has reached this month's limit of your organisation; the next requests will be refused. An administrator can add a card or raise the cap in the VibeDefend usage page.`;
5898
+ }
5899
+ var ORG_WIDE_REASONS = /* @__PURE__ */ new Set([
5900
+ "unpaid",
5901
+ "product_inactive"
5902
+ ]);
5903
+ function blockKey(block) {
5904
+ return ORG_WIDE_REASONS.has(block.reason) ? `block:${block.reason}` : `block:${block.reason}:${block.unit}`;
5905
+ }
5906
+ var NOTICES_CAP = 300;
5907
+ function defaultUsageNoticesFile() {
5908
+ const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join5(homedir6(), ".cybedefend");
5909
+ return join5(dir, "usage-notices-sessions.json");
5910
+ }
5911
+ function makeUsageNotifier(opts = {}) {
5912
+ const file = opts.file ?? defaultUsageNoticesFile();
5913
+ const write = opts.write ?? ((line) => {
5914
+ try {
5915
+ process.stderr.write(line);
5916
+ } catch {
5917
+ }
5918
+ });
5919
+ function load() {
5920
+ try {
5921
+ if (!existsSync7(file)) return [];
5922
+ const data = JSON.parse(readFileSync6(file, "utf8"));
5923
+ return Array.isArray(data.seen) ? data.seen.filter((s) => typeof s === "string") : [];
5924
+ } catch {
5925
+ return [];
5926
+ }
5927
+ }
5928
+ function once(sessionId, key, sentence) {
5929
+ if (sessionId) {
5930
+ const marker = `${sessionId}|${key}`;
5931
+ const seen = load();
5932
+ if (seen.includes(marker)) return null;
5933
+ try {
5934
+ mkdirSync4(dirname3(file), { recursive: true });
5935
+ writeFileSync3(
5936
+ file,
5937
+ JSON.stringify({ seen: [...seen, marker].slice(-NOTICES_CAP) }),
5938
+ "utf8"
5939
+ );
5940
+ } catch {
5941
+ }
5942
+ }
5943
+ write(`[VibeDefend] ${sentence}
5944
+ `);
5945
+ return sentence;
5946
+ }
5947
+ return {
5948
+ notifyBlock: (sessionId, block) => once(sessionId, blockKey(block), blockSentence(block)),
5949
+ notifyWarning: (sessionId, w) => once(sessionId, `warning:${w.unit}:${w.warning}`, warningSentence(w))
5950
+ };
5951
+ }
5952
+
5809
5953
  // src/hooks/runtime/api.ts
5954
+ async function reportUsageSignal(res, listener) {
5955
+ const emit2 = (signal) => {
5956
+ try {
5957
+ listener?.(signal);
5958
+ } catch {
5959
+ }
5960
+ };
5961
+ if (!res.ok) {
5962
+ const block = await readVibedefendBlock(res);
5963
+ if (block) emit2({ kind: "blocked", block });
5964
+ return false;
5965
+ }
5966
+ const warning = readUsageWarning(res);
5967
+ if (warning) emit2({ kind: "warning", warning });
5968
+ return true;
5969
+ }
5810
5970
  var DEFAULT_TIMEOUT_MS = 1e4;
5811
5971
  async function fetchBusinessRules(opts) {
5812
5972
  const f = opts.fetchImpl ?? globalThis.fetch;
@@ -5830,7 +5990,7 @@ async function fetchBusinessRules(opts) {
5830
5990
  } catch {
5831
5991
  return null;
5832
5992
  }
5833
- if (!res.ok) return null;
5993
+ if (!await reportUsageSignal(res, opts.onUsageSignal)) return null;
5834
5994
  let body;
5835
5995
  try {
5836
5996
  body = await res.json();
@@ -5865,7 +6025,7 @@ async function fetchSecurityRules(opts) {
5865
6025
  } catch {
5866
6026
  return null;
5867
6027
  }
5868
- if (!res.ok) return null;
6028
+ if (!await reportUsageSignal(res, opts.onUsageSignal)) return null;
5869
6029
  let body;
5870
6030
  try {
5871
6031
  body = await res.json();
@@ -5969,6 +6129,7 @@ async function fetchRulesHook(deps = {}) {
5969
6129
  const fetchRules = deps.fetchRulesFn ?? fetchBusinessRules;
5970
6130
  const fetchSecRules = deps.fetchSecurityRulesFn ?? fetchSecurityRules;
5971
6131
  const emitFn = deps.emitFn ?? emitPreToolUseContext;
6132
+ const usageNotifier = deps.usageNotifier ?? makeUsageNotifier();
5972
6133
  const stdin = await readStdin();
5973
6134
  const event = sniff(stdin);
5974
6135
  if (!INTERESTED_TOOLS.has(event.toolName)) return;
@@ -5985,13 +6146,20 @@ async function fetchRulesHook(deps = {}) {
5985
6146
  const intent = buildIntent(event);
5986
6147
  const files = event.filePaths.length > 0 ? event.filePaths : [event.filePath];
5987
6148
  const fileLabel = files.join(", ");
6149
+ const sessionId = sessionIdOf(stdin);
6150
+ const usageNotices = [];
6151
+ const onUsageSignal = (signal) => {
6152
+ const sentence = signal.kind === "blocked" ? usageNotifier.notifyBlock(sessionId, signal.block) : usageNotifier.notifyWarning(sessionId, signal.warning);
6153
+ if (sentence) usageNotices.push(sentence);
6154
+ };
5988
6155
  const fetchOpts = {
5989
6156
  apiBase,
5990
6157
  projectId,
5991
6158
  token,
5992
6159
  files,
5993
6160
  intent,
5994
- topK: 5
6161
+ topK: 5,
6162
+ onUsageSignal
5995
6163
  };
5996
6164
  const [businessResult, securityResult] = await Promise.all([
5997
6165
  fetchRules(fetchOpts),
@@ -6015,8 +6183,18 @@ async function fetchRulesHook(deps = {}) {
6015
6183
  )
6016
6184
  );
6017
6185
  }
6186
+ if (usageNotices.length > 0) {
6187
+ blocks.push(
6188
+ ["## CybeDefend VibeDefend usage", ...usageNotices.map((n) => `- ${n}`), ""].join("\n")
6189
+ );
6190
+ }
6018
6191
  if (blocks.length > 0) emitFn(blocks.join("\n"), event.client);
6019
6192
  }
6193
+ function sessionIdOf(stdin) {
6194
+ if (typeof stdin.session_id === "string" && stdin.session_id) return stdin.session_id;
6195
+ if (typeof stdin.sessionId === "string" && stdin.sessionId) return stdin.sessionId;
6196
+ return void 0;
6197
+ }
6020
6198
  function buildIntent(event) {
6021
6199
  const limit = parseInt(process.env.CYBEDEFEND_INTENT_BYTES ?? "800", 10);
6022
6200
  const snippet = event.newContent.slice(0, Number.isFinite(limit) ? limit : 800).replace(/\s+/g, " ").trim();
@@ -6097,9 +6275,9 @@ var DIRECT_PROPOSE_DIRECTIVES = {
6097
6275
 
6098
6276
  // src/hooks/runtime/self-update-check.ts
6099
6277
  import { spawn } from "node:child_process";
6100
- import { existsSync as existsSync8, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
6101
- import { homedir as homedir6 } from "node:os";
6102
- import { join as join5 } from "node:path";
6278
+ import { existsSync as existsSync9, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
6279
+ import { homedir as homedir7 } from "node:os";
6280
+ import { join as join6 } from "node:path";
6103
6281
 
6104
6282
  // src/hooks/runtime/semver.ts
6105
6283
  function parseVersion(input) {
@@ -6127,7 +6305,7 @@ function isNewer(latest, current) {
6127
6305
 
6128
6306
  // src/self-update.ts
6129
6307
  import { fileURLToPath } from "node:url";
6130
- import { existsSync as existsSync7, realpathSync } from "node:fs";
6308
+ import { existsSync as existsSync8, realpathSync } from "node:fs";
6131
6309
  var GLOBAL_MODES = /* @__PURE__ */ new Set([
6132
6310
  "npm-global",
6133
6311
  "pnpm-global",
@@ -6155,7 +6333,7 @@ function detectInstallMode(scriptPath) {
6155
6333
  }
6156
6334
  function resolveScriptPath() {
6157
6335
  const fromArgv = process.argv[1];
6158
- if (fromArgv && existsSync7(fromArgv)) {
6336
+ if (fromArgv && existsSync8(fromArgv)) {
6159
6337
  try {
6160
6338
  return realpathSync(fromArgv);
6161
6339
  } catch {
@@ -6177,9 +6355,9 @@ function isValidRegistryVersion(value) {
6177
6355
  }
6178
6356
  async function selfUpdateCheck(deps) {
6179
6357
  const now = deps.now ?? (() => Date.now());
6180
- const dir = deps.cybeDir ?? join5(process.env.CYBEDEFEND_HOME ?? homedir6(), ".cybedefend");
6181
- const throttlePath = join5(dir, "update-check.json");
6182
- const logPath = join5(dir, "last-update.log");
6358
+ const dir = deps.cybeDir ?? join6(process.env.CYBEDEFEND_HOME ?? homedir7(), ".cybedefend");
6359
+ const throttlePath = join6(dir, "update-check.json");
6360
+ const logPath = join6(dir, "last-update.log");
6183
6361
  const installMode = deps.installMode ?? (deps.runnerSource ? detectInstallMode(deps.runnerSource) : detectInstallMode(resolveScriptPath()));
6184
6362
  const priorFailure = takeUpdateLogNotice(logPath);
6185
6363
  const throttle = readThrottle(throttlePath);
@@ -6212,7 +6390,7 @@ function applyOrNotify(deps, installMode, latest, logPath) {
6212
6390
  }
6213
6391
  function readThrottle(path) {
6214
6392
  try {
6215
- if (!existsSync8(path)) return null;
6393
+ if (!existsSync9(path)) return null;
6216
6394
  const data = JSON.parse(readFileSync7(path, "utf8"));
6217
6395
  if (typeof data.lastCheckedAt !== "number") return null;
6218
6396
  return { lastCheckedAt: data.lastCheckedAt };
@@ -6229,7 +6407,7 @@ function writeThrottle(path, throttle) {
6229
6407
  }
6230
6408
  function takeUpdateLogNotice(logPath) {
6231
6409
  try {
6232
- if (!existsSync8(logPath)) return null;
6410
+ if (!existsSync9(logPath)) return null;
6233
6411
  let parsed = {};
6234
6412
  try {
6235
6413
  parsed = JSON.parse(readFileSync7(logPath, "utf8"));
@@ -6307,18 +6485,18 @@ function pmCommand(mode, spec) {
6307
6485
 
6308
6486
  // src/hooks/runtime/hook-refresh-trigger.ts
6309
6487
  import { spawn as spawn2 } from "node:child_process";
6310
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
6311
- import { homedir as homedir7 } from "node:os";
6312
- import { dirname as dirname3, join as join6 } from "node:path";
6488
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
6489
+ import { homedir as homedir8 } from "node:os";
6490
+ import { dirname as dirname4, join as join7 } from "node:path";
6313
6491
  function maybeRefreshStaleWiring(deps) {
6314
6492
  try {
6315
6493
  const { config, runnerVersion } = deps;
6316
6494
  if (!config.runnerSource || config.installedVersion === runnerVersion) {
6317
6495
  return false;
6318
6496
  }
6319
- const cliEntry = join6(dirname3(dirname3(config.runnerSource)), "bin", "vibedefend.js");
6320
- if (!(deps.exists ?? existsSync9)(cliEntry)) return false;
6321
- const markerPath = deps.markerPath ?? join6(process.env.CYBEDEFEND_HOME ?? homedir7(), ".cybedefend", "hook-refresh.json");
6497
+ const cliEntry = join7(dirname4(dirname4(config.runnerSource)), "bin", "vibedefend.js");
6498
+ if (!(deps.exists ?? existsSync10)(cliEntry)) return false;
6499
+ const markerPath = deps.markerPath ?? join7(process.env.CYBEDEFEND_HOME ?? homedir8(), ".cybedefend", "hook-refresh.json");
6322
6500
  if (readMarkerVersion(markerPath) === runnerVersion) return false;
6323
6501
  (deps.spawnRefresh ?? defaultSpawnRefresh)(cliEntry);
6324
6502
  writeMarker(markerPath, runnerVersion);
@@ -6329,7 +6507,7 @@ function maybeRefreshStaleWiring(deps) {
6329
6507
  }
6330
6508
  function readMarkerVersion(path) {
6331
6509
  try {
6332
- if (!existsSync9(path)) return null;
6510
+ if (!existsSync10(path)) return null;
6333
6511
  const data = JSON.parse(readFileSync8(path, "utf8"));
6334
6512
  return typeof data.version === "string" ? data.version : null;
6335
6513
  } catch {
@@ -6355,10 +6533,10 @@ function defaultSpawnRefresh(cliEntry) {
6355
6533
  // src/version.ts
6356
6534
  import { readFileSync as readFileSync9 } from "node:fs";
6357
6535
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6358
- import { dirname as dirname4, join as join7 } from "node:path";
6536
+ import { dirname as dirname5, join as join8 } from "node:path";
6359
6537
  var PKG_NAME = "@cybedefend/vibedefend";
6360
6538
  function defaultPkgPath() {
6361
- return join7(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json");
6539
+ return join8(dirname5(fileURLToPath2(import.meta.url)), "..", "package.json");
6362
6540
  }
6363
6541
  function readPackageMeta(opts = {}) {
6364
6542
  if (typeof opts.bakedVersion === "string" && opts.bakedVersion.length > 0) {
@@ -6373,7 +6551,7 @@ function readPackageMeta(opts = {}) {
6373
6551
  }
6374
6552
  }
6375
6553
  var pkg = readPackageMeta({
6376
- bakedVersion: true ? "1.4.5" : void 0
6554
+ bakedVersion: true ? "1.4.7" : void 0
6377
6555
  });
6378
6556
 
6379
6557
  // src/hooks/runtime/session-start.ts
@@ -6489,7 +6667,7 @@ async function sessionStartHook(deps = {}) {
6489
6667
  }
6490
6668
 
6491
6669
  // src/hooks/runtime/session-review.ts
6492
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
6670
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
6493
6671
  var COUNTED_TOOLS = /* @__PURE__ */ new Set([
6494
6672
  "Edit",
6495
6673
  "Write",
@@ -6573,7 +6751,7 @@ function loadTranscript(stdin, readFile) {
6573
6751
  return out;
6574
6752
  }
6575
6753
  function defaultReadTranscript(path) {
6576
- if (!existsSync10(path)) return null;
6754
+ if (!existsSync11(path)) return null;
6577
6755
  try {
6578
6756
  return readFileSync10(path, "utf8");
6579
6757
  } catch {
@@ -6835,17 +7013,17 @@ async function preCompactHook(deps = {}) {
6835
7013
  }
6836
7014
 
6837
7015
  // src/hooks/runtime/user-prompt-submit.ts
6838
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
6839
- import { homedir as homedir8 } from "node:os";
6840
- import { join as join8 } from "node:path";
7016
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
7017
+ import { homedir as homedir9 } from "node:os";
7018
+ import { join as join9 } from "node:path";
6841
7019
  var SESSIONS_CAP = 100;
6842
7020
  function defaultSessionsFile() {
6843
- const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join8(homedir8(), ".cybedefend");
6844
- return join8(dir, "user-prompt-seen-sessions.json");
7021
+ const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join9(homedir9(), ".cybedefend");
7022
+ return join9(dir, "user-prompt-seen-sessions.json");
6845
7023
  }
6846
7024
  function defaultSessionTracker(file = defaultSessionsFile()) {
6847
7025
  function load() {
6848
- if (!existsSync11(file)) return [];
7026
+ if (!existsSync12(file)) return [];
6849
7027
  try {
6850
7028
  const raw = readFileSync11(file, "utf8");
6851
7029
  const data = JSON.parse(raw);
@@ -6864,7 +7042,7 @@ function defaultSessionTracker(file = defaultSessionsFile()) {
6864
7042
  if (existing.includes(sessionId)) return;
6865
7043
  const next = [...existing, sessionId].slice(-SESSIONS_CAP);
6866
7044
  try {
6867
- mkdirSync4(join8(file, ".."), { recursive: true });
7045
+ mkdirSync5(join9(file, ".."), { recursive: true });
6868
7046
  writeFileSync6(file, JSON.stringify({ sessions: next }), "utf8");
6869
7047
  } catch {
6870
7048
  }
@@ -7936,10 +8114,10 @@ function nearMiss(req, rule) {
7936
8114
  }
7937
8115
 
7938
8116
  // src/hooks/runtime/guard-rules-cache.ts
7939
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "node:fs";
7940
- import { dirname as dirname5 } from "node:path";
7941
- import { homedir as homedir9 } from "node:os";
7942
- import { join as join9 } from "node:path";
8117
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "node:fs";
8118
+ import { dirname as dirname6 } from "node:path";
8119
+ import { homedir as homedir10 } from "node:os";
8120
+ import { join as join10 } from "node:path";
7943
8121
 
7944
8122
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
7945
8123
  var external_exports = {};
@@ -12051,8 +12229,8 @@ function parseRuleFields(raw) {
12051
12229
  }
12052
12230
 
12053
12231
  // src/hooks/runtime/guard-rules-cache.ts
12054
- var DEFAULT_CACHE_FILE = join9(
12055
- homedir9(),
12232
+ var DEFAULT_CACHE_FILE = join10(
12233
+ homedir10(),
12056
12234
  ".cybedefend",
12057
12235
  "guards-cache.json"
12058
12236
  );
@@ -12132,14 +12310,26 @@ function makeGuardRulesCache(opts) {
12132
12310
  const entry = readCache(ctx.projectId);
12133
12311
  const ageSec = entry ? (Date.now() - entry.fetchedAt) / 1e3 : Infinity;
12134
12312
  if (entry && ageSec < staleSec) {
12313
+ if (entry.blocked) return { kind: "blocked", block: entry.blocked };
12135
12314
  return { kind: "rules", rules: entry.rules, llmJudge: entry.llmJudge };
12136
12315
  }
12137
12316
  try {
12138
12317
  const result = await fetchFn({
12139
12318
  ...ctx,
12140
- etag: entry?.etag ?? null
12319
+ etag: entry?.blocked ? null : entry?.etag ?? null
12141
12320
  });
12142
- if (result.notModified && entry) {
12321
+ if (result.blocked) {
12322
+ writeCache({
12323
+ projectId: ctx.projectId,
12324
+ rules: [],
12325
+ etag: null,
12326
+ fetchedAt: Date.now(),
12327
+ llmJudge: null,
12328
+ blocked: result.blocked
12329
+ });
12330
+ return { kind: "blocked", block: result.blocked };
12331
+ }
12332
+ if (result.notModified && entry && !entry.blocked) {
12143
12333
  writeCache({
12144
12334
  ...entry,
12145
12335
  fetchedAt: Date.now()
@@ -12161,7 +12351,7 @@ function makeGuardRulesCache(opts) {
12161
12351
  return { kind: "rules", rules: fresh.rules, llmJudge: fresh.llmJudge };
12162
12352
  } catch (e) {
12163
12353
  const msg = e instanceof Error ? e.message : String(e);
12164
- if (entry?.rules?.length) {
12354
+ if (entry?.rules?.length && !entry.blocked) {
12165
12355
  const ageSec2 = Math.round((Date.now() - entry.fetchedAt) / 1e3);
12166
12356
  process.stderr.write(
12167
12357
  `[VibeDefend] Action Guards: rule refresh failed (${msg}). Using cached rules (age=${ageSec2}s).
@@ -12176,7 +12366,7 @@ function makeGuardRulesCache(opts) {
12176
12366
  }
12177
12367
  }
12178
12368
  function readCache(projectId) {
12179
- if (!existsSync12(cacheFile)) return null;
12369
+ if (!existsSync13(cacheFile)) return null;
12180
12370
  try {
12181
12371
  const raw = readFileSync12(cacheFile, "utf8");
12182
12372
  const data = JSON.parse(raw);
@@ -12196,9 +12386,9 @@ function makeGuardRulesCache(opts) {
12196
12386
  return { refreshIfStale };
12197
12387
  }
12198
12388
  function ensureDirFor2(filePath) {
12199
- const dir = dirname5(filePath);
12200
- if (!existsSync12(dir)) {
12201
- mkdirSync5(dir, { recursive: true });
12389
+ const dir = dirname6(filePath);
12390
+ if (!existsSync13(dir)) {
12391
+ mkdirSync6(dir, { recursive: true });
12202
12392
  }
12203
12393
  }
12204
12394
  var DEFAULT_TIMEOUT_MS2 = 8e3;
@@ -12221,6 +12411,10 @@ async function fetchEffectiveRulesFromGateway(ctx, opts = {}) {
12221
12411
  return { rules: [], etag: ctx.etag ?? null, notModified: true };
12222
12412
  }
12223
12413
  if (!res.ok) {
12414
+ const blocked = await readVibedefendBlock(res);
12415
+ if (blocked) {
12416
+ return { rules: [], etag: null, notModified: false, blocked };
12417
+ }
12224
12418
  throw new Error(`HTTP ${res.status} from effective-rules endpoint`);
12225
12419
  }
12226
12420
  const body = await res.json();
@@ -12241,6 +12435,9 @@ function parseLlmJudgeHint(raw) {
12241
12435
  }
12242
12436
 
12243
12437
  // src/hooks/runtime/judge.ts
12438
+ function isJudgeBlocked(res) {
12439
+ return res !== null && "blocked" in res;
12440
+ }
12244
12441
  var DEFAULT_TIMEOUT_MS3 = 1500;
12245
12442
  async function callJudge(input, opts = {}) {
12246
12443
  const f = opts.fetchImpl ?? globalThis.fetch;
@@ -12264,7 +12461,11 @@ async function callJudge(input, opts = {}) {
12264
12461
  }),
12265
12462
  signal: AbortSignal.timeout(timeoutMs)
12266
12463
  });
12267
- if (!res.ok) return null;
12464
+ if (!res.ok) {
12465
+ const blocked = await readVibedefendBlock(res);
12466
+ return blocked ? { blocked } : null;
12467
+ }
12468
+ const usageWarning = readUsageWarning(res);
12268
12469
  const body = await res.json();
12269
12470
  const data = body.data ?? body;
12270
12471
  const decision = data["decision"];
@@ -12274,7 +12475,8 @@ async function callJudge(input, opts = {}) {
12274
12475
  reason: typeof data["reason"] === "string" ? data["reason"] : "",
12275
12476
  model: typeof data["model"] === "string" ? data["model"] : "",
12276
12477
  quotaRemaining: typeof data["quota_remaining"] === "number" ? data["quota_remaining"] : -1,
12277
- quotaExceeded: Boolean(data["quota_exceeded"])
12478
+ quotaExceeded: Boolean(data["quota_exceeded"]),
12479
+ ...usageWarning ? { usageWarning } : {}
12278
12480
  };
12279
12481
  } catch {
12280
12482
  return null;
@@ -12319,11 +12521,11 @@ function detectPromptInjection(text) {
12319
12521
  }
12320
12522
 
12321
12523
  // src/hooks/runtime/judge-cache.ts
12322
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "node:fs";
12323
- import { dirname as dirname6, join as join10 } from "node:path";
12324
- import { homedir as homedir10 } from "node:os";
12325
- var DEFAULT_JUDGE_CACHE_FILE = join10(
12326
- homedir10(),
12524
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
12525
+ import { dirname as dirname7, join as join11 } from "node:path";
12526
+ import { homedir as homedir11 } from "node:os";
12527
+ var DEFAULT_JUDGE_CACHE_FILE = join11(
12528
+ homedir11(),
12327
12529
  ".cybedefend",
12328
12530
  "guards-judge-cache.json"
12329
12531
  );
@@ -12333,7 +12535,7 @@ function makeJudgeCache(opts = {}) {
12333
12535
  const ttlMs = opts.ttlMs ?? DEFAULT_JUDGE_TTL_MS;
12334
12536
  const now = opts.now ?? (() => Date.now());
12335
12537
  function readAll() {
12336
- if (!existsSync13(cacheFile)) return {};
12538
+ if (!existsSync14(cacheFile)) return {};
12337
12539
  try {
12338
12540
  return JSON.parse(readFileSync13(cacheFile, "utf8"));
12339
12541
  } catch {
@@ -12362,29 +12564,29 @@ function makeJudgeCache(opts = {}) {
12362
12564
  return { get, set };
12363
12565
  }
12364
12566
  function ensureDirFor3(filePath) {
12365
- const dir = dirname6(filePath);
12366
- if (!existsSync13(dir)) {
12367
- mkdirSync6(dir, { recursive: true });
12567
+ const dir = dirname7(filePath);
12568
+ if (!existsSync14(dir)) {
12569
+ mkdirSync7(dir, { recursive: true });
12368
12570
  }
12369
12571
  }
12370
12572
 
12371
12573
  // src/hooks/runtime/guard-check.ts
12372
- import { homedir as homedir12 } from "node:os";
12373
- import { join as join12 } from "node:path";
12574
+ import { homedir as homedir13 } from "node:os";
12575
+ import { join as join13 } from "node:path";
12374
12576
 
12375
12577
  // src/hooks/runtime/guard-violations-buffer.ts
12376
12578
  import {
12377
12579
  appendFileSync,
12378
- existsSync as existsSync14,
12580
+ existsSync as existsSync15,
12379
12581
  readFileSync as readFileSync14,
12380
12582
  writeFileSync as writeFileSync9,
12381
- mkdirSync as mkdirSync7
12583
+ mkdirSync as mkdirSync8
12382
12584
  } from "node:fs";
12383
- import { dirname as dirname7 } from "node:path";
12384
- import { homedir as homedir11 } from "node:os";
12385
- import { join as join11 } from "node:path";
12386
- var DEFAULT_BUFFER_FILE = join11(
12387
- homedir11(),
12585
+ import { dirname as dirname8 } from "node:path";
12586
+ import { homedir as homedir12 } from "node:os";
12587
+ import { join as join12 } from "node:path";
12588
+ var DEFAULT_BUFFER_FILE = join12(
12589
+ homedir12(),
12388
12590
  ".cybedefend",
12389
12591
  "guards-violations-buffer.jsonl"
12390
12592
  );
@@ -12403,7 +12605,7 @@ function makeViolationsBuffer(opts = {}) {
12403
12605
  }
12404
12606
  }
12405
12607
  async function flushViolations2(ctx, postFn) {
12406
- if (!existsSync14(bufferFile)) return;
12608
+ if (!existsSync15(bufferFile)) return;
12407
12609
  let content;
12408
12610
  try {
12409
12611
  content = readFileSync14(bufferFile, "utf8").trim();
@@ -12466,9 +12668,9 @@ function makeViolationsBuffer(opts = {}) {
12466
12668
  return { bufferViolation: bufferViolation2, flushViolations: flushViolations2 };
12467
12669
  }
12468
12670
  function ensureDirFor4(filePath) {
12469
- const dir = dirname7(filePath);
12470
- if (!existsSync14(dir)) {
12471
- mkdirSync7(dir, { recursive: true });
12671
+ const dir = dirname8(filePath);
12672
+ if (!existsSync15(dir)) {
12673
+ mkdirSync8(dir, { recursive: true });
12472
12674
  }
12473
12675
  }
12474
12676
  var _defaultBuffer = makeViolationsBuffer();
@@ -12494,11 +12696,11 @@ function appendDebugLog(entry) {
12494
12696
  try {
12495
12697
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
12496
12698
  if (!homeDir) return;
12497
- const { mkdirSync: mkdirSync8, appendFileSync: appendFileSync2 } = __require("node:fs");
12498
- const { join: join13 } = __require("node:path");
12499
- const dir = join13(homeDir, ".cybedefend");
12500
- mkdirSync8(dir, { recursive: true });
12501
- appendFileSync2(join13(dir, "hook-debug.log"), entry + "\n", "utf8");
12699
+ const { mkdirSync: mkdirSync9, appendFileSync: appendFileSync2 } = __require("node:fs");
12700
+ const { join: join14 } = __require("node:path");
12701
+ const dir = join14(homeDir, ".cybedefend");
12702
+ mkdirSync9(dir, { recursive: true });
12703
+ appendFileSync2(join14(dir, "hook-debug.log"), entry + "\n", "utf8");
12502
12704
  } catch {
12503
12705
  }
12504
12706
  }
@@ -12669,6 +12871,28 @@ function stringifyTarget(t) {
12669
12871
  return `git ${t.subcommand}`;
12670
12872
  }
12671
12873
  }
12874
+ async function consultJudge(judge, sessionId, cacheKey, input) {
12875
+ const cached = judge.cacheGet(cacheKey);
12876
+ if (cached) return { kind: "result", res: cached };
12877
+ if (judge.usage?.isBlocked(sessionId)) return { kind: "blocked" };
12878
+ const answer = await judge.callJudge(input);
12879
+ if (isJudgeBlocked(answer)) {
12880
+ if (judge.usage) {
12881
+ judge.usage.markBlocked(sessionId);
12882
+ judge.usage.notifier.notifyBlock(sessionId, answer.blocked);
12883
+ } else {
12884
+ judge.warnOnce(sessionId, blockSentence(answer.blocked));
12885
+ }
12886
+ return { kind: "blocked" };
12887
+ }
12888
+ if (!answer) return { kind: "result", res: null };
12889
+ const { usageWarning, ...res } = answer;
12890
+ if (usageWarning) {
12891
+ judge.usage?.notifier.notifyWarning(sessionId, usageWarning);
12892
+ }
12893
+ judge.cacheSet(cacheKey, res);
12894
+ return { kind: "result", res };
12895
+ }
12672
12896
  async function runJudgeStep(args) {
12673
12897
  const {
12674
12898
  judge,
@@ -12697,30 +12921,30 @@ async function runJudgeStep(args) {
12697
12921
  const actionHash = hashTarget(targetStr);
12698
12922
  const ruleIds = llmRules.map((r) => r.id).sort().join(",");
12699
12923
  const cacheKey = `${actionHash}|${ruleIds}|confirm`;
12700
- let res = judge.cacheGet(cacheKey);
12701
- if (!res) {
12702
- res = await judge.callJudge({
12703
- apiBaseResolved: projectContext.apiBaseResolved,
12704
- token: projectContext.token,
12705
- projectId: request.projectId,
12706
- tool: request.tool,
12707
- agent: request.agent,
12708
- sessionId,
12709
- phase: "confirm",
12710
- // Judge over the FULL (schema-bounded 2048) redacted action — the default
12711
- // redactTarget cap of 256 (right for the DB-stored violation summary) would
12712
- // truncate a long dangerous action and risk an overturn on a partial view.
12713
- actionSummary: redactTarget(targetStr, 2048),
12714
- actionHash,
12715
- rules: llmRules.map((r) => ({
12716
- id: r.id,
12717
- name: r.name ?? r.id,
12718
- description: r.description ?? "",
12719
- severity: r.severity
12720
- }))
12721
- });
12722
- if (res) judge.cacheSet(cacheKey, res);
12924
+ const consultation = await consultJudge(judge, sessionId, cacheKey, {
12925
+ apiBaseResolved: projectContext.apiBaseResolved,
12926
+ token: projectContext.token,
12927
+ projectId: request.projectId,
12928
+ tool: request.tool,
12929
+ agent: request.agent,
12930
+ sessionId,
12931
+ phase: "confirm",
12932
+ // Judge over the FULL (schema-bounded 2048) redacted action — the default
12933
+ // redactTarget cap of 256 (right for the DB-stored violation summary) would
12934
+ // truncate a long dangerous action and risk an overturn on a partial view.
12935
+ actionSummary: redactTarget(targetStr, 2048),
12936
+ actionHash,
12937
+ rules: llmRules.map((r) => ({
12938
+ id: r.id,
12939
+ name: r.name ?? r.id,
12940
+ description: r.description ?? "",
12941
+ severity: r.severity
12942
+ }))
12943
+ });
12944
+ if (consultation.kind === "blocked") {
12945
+ return { engine: "llm", outcome: "quota_exceeded" };
12723
12946
  }
12947
+ const res = consultation.res;
12724
12948
  if (!res) {
12725
12949
  return {
12726
12950
  engine: "llm",
@@ -12783,27 +13007,25 @@ async function runCatchJudgeStep(args) {
12783
13007
  const actionHash = hashTarget(targetStr);
12784
13008
  const ruleIds = near.map((r) => r.id).sort();
12785
13009
  const cacheKey = `${actionHash}|${ruleIds.join(",")}|catch`;
12786
- let res = judge.cacheGet(cacheKey);
12787
- if (!res) {
12788
- res = await judge.callJudge({
12789
- apiBaseResolved: projectContext.apiBaseResolved,
12790
- token: projectContext.token,
12791
- projectId: request.projectId,
12792
- tool: request.tool,
12793
- agent: request.agent,
12794
- sessionId,
12795
- phase: "catch",
12796
- actionSummary: redactTarget(targetStr, 2048),
12797
- actionHash,
12798
- rules: near.map((r) => ({
12799
- id: r.id,
12800
- name: r.name ?? r.id,
12801
- description: r.description ?? "",
12802
- severity: r.severity
12803
- }))
12804
- });
12805
- if (res) judge.cacheSet(cacheKey, res);
12806
- }
13010
+ const consultation = await consultJudge(judge, sessionId, cacheKey, {
13011
+ apiBaseResolved: projectContext.apiBaseResolved,
13012
+ token: projectContext.token,
13013
+ projectId: request.projectId,
13014
+ tool: request.tool,
13015
+ agent: request.agent,
13016
+ sessionId,
13017
+ phase: "catch",
13018
+ actionSummary: redactTarget(targetStr, 2048),
13019
+ actionHash,
13020
+ rules: near.map((r) => ({
13021
+ id: r.id,
13022
+ name: r.name ?? r.id,
13023
+ description: r.description ?? "",
13024
+ severity: r.severity
13025
+ }))
13026
+ });
13027
+ if (consultation.kind === "blocked") return void 0;
13028
+ const res = consultation.res;
12807
13029
  if (!res) return void 0;
12808
13030
  if (res.quotaExceeded) {
12809
13031
  judge.warnOnce(
@@ -12849,6 +13071,9 @@ function emitUnverifiedBanner(reason) {
12849
13071
  }
12850
13072
  }
12851
13073
  }
13074
+ function sessionIdOf2(envelope) {
13075
+ return typeof envelope.session_id === "string" ? envelope.session_id : typeof envelope.sessionId === "string" ? envelope.sessionId : void 0;
13076
+ }
12852
13077
  async function runPreToolUseGuardWith(opts) {
12853
13078
  const {
12854
13079
  stdinEnvelope,
@@ -12864,11 +13089,19 @@ async function runPreToolUseGuardWith(opts) {
12864
13089
  );
12865
13090
  return 0;
12866
13091
  }
13092
+ if (rulesOutcome.kind === "blocked") {
13093
+ const sentence = opts.usageNotifier?.notifyBlock(
13094
+ sessionIdOf2(stdinEnvelope),
13095
+ rulesOutcome.block
13096
+ );
13097
+ if (sentence) emitAgentMessage("warn", sentence);
13098
+ return 0;
13099
+ }
12867
13100
  const rules = rulesOutcome.rules;
12868
13101
  const caller = sniff(stdinEnvelope);
12869
13102
  const partial = mapEnvelopeToGuardRequest(stdinEnvelope, caller.client);
12870
13103
  if (partial === null) return 0;
12871
- const sessionId = typeof stdinEnvelope.session_id === "string" ? stdinEnvelope.session_id : typeof stdinEnvelope.sessionId === "string" ? stdinEnvelope.sessionId : void 0;
13104
+ const sessionId = sessionIdOf2(stdinEnvelope);
12872
13105
  const guardRequest = {
12873
13106
  ...partial,
12874
13107
  projectId: projectContext.projectId,
@@ -13012,11 +13245,21 @@ function emitAgentMessage(verdict, message) {
13012
13245
  }
13013
13246
  function makeProductionJudgeDeps() {
13014
13247
  const cache = makeJudgeCache();
13015
- const warnDir = process.env.CYBEDEFEND_BASELINE_DIR ?? join12(homedir12(), ".cybedefend");
13248
+ const warnDir = process.env.CYBEDEFEND_BASELINE_DIR ?? join13(homedir13(), ".cybedefend");
13016
13249
  const warnTracker = defaultSessionTracker(
13017
- join12(warnDir, "guards-judge-warned-sessions.json")
13250
+ join13(warnDir, "guards-judge-warned-sessions.json")
13251
+ );
13252
+ const blockedTracker = defaultSessionTracker(
13253
+ join13(warnDir, "guards-judge-blocked-sessions.json")
13018
13254
  );
13019
13255
  return {
13256
+ usage: {
13257
+ notifier: makeUsageNotifier(),
13258
+ isBlocked: (sessionId) => sessionId ? blockedTracker.hasSeen(sessionId) : false,
13259
+ markBlocked: (sessionId) => {
13260
+ if (sessionId) void blockedTracker.markSeen(sessionId);
13261
+ }
13262
+ },
13020
13263
  callJudge,
13021
13264
  cacheGet: (k) => cache.get(k),
13022
13265
  cacheSet: (k, r) => cache.set(k, r),
@@ -13074,7 +13317,10 @@ async function runPreToolUseGuard() {
13074
13317
  judge: makeProductionJudgeDeps(),
13075
13318
  // v1.1 catch — OFF by default; opt in with CYBEDEFEND_JUDGE_CATCH=1 once
13076
13319
  // the call-rate gate is validated in the field.
13077
- judgeCatch: process.env.CYBEDEFEND_JUDGE_CATCH === "1"
13320
+ judgeCatch: process.env.CYBEDEFEND_JUDGE_CATCH === "1",
13321
+ // The refusal sentence goes out through the agent-message channels
13322
+ // (stderr included), so this notifier itself writes nothing.
13323
+ usageNotifier: makeUsageNotifier({ write: () => void 0 })
13078
13324
  });
13079
13325
  try {
13080
13326
  await buffer.flushViolations(