@cybedefend/vibedefend 1.4.7 → 1.5.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.
@@ -5764,26 +5764,34 @@ async function resolveProjectIdWithApi(cwd = process.cwd(), deps = {}) {
5764
5764
  const resolveSync = deps.resolveSync ?? resolveProjectId;
5765
5765
  const base = resolveSync(cwd);
5766
5766
  if (base.projectId) return base;
5767
+ let repository;
5768
+ const none = (inactiveReason) => ({
5769
+ ...base,
5770
+ inactiveReason,
5771
+ ...repository ? { repository } : {}
5772
+ });
5767
5773
  try {
5768
5774
  const getOrigin = deps.getOrigin ?? getOriginRemote;
5769
5775
  const originUrl = getOrigin(cwd);
5770
- if (!originUrl) return base;
5776
+ if (!originUrl) return none("no-git-origin");
5771
5777
  const remote = parseRemoteUrl(originUrl);
5772
- if (!remote || remote.provider === null) return base;
5778
+ if (!remote) return none("unsupported-remote");
5779
+ repository = `${remote.host}/${remote.slug}`;
5780
+ if (remote.provider === null) return none("unsupported-remote");
5773
5781
  const key = `${remote.provider}:${remote.host}:${remote.slug}`;
5774
5782
  const cached = getCachedRepoLink(key, deps.cacheDeps);
5775
5783
  if (cached.kind === "positive") {
5776
5784
  return { projectId: cached.projectId, apiBase: base.apiBase };
5777
5785
  }
5778
5786
  if (cached.kind === "negative") {
5779
- return base;
5787
+ return none("no-linked-project");
5780
5788
  }
5781
5789
  const config = (deps.loadConfig ?? loadRuntimeConfig)();
5782
- if (!config) return base;
5790
+ if (!config) return none("lookup-failed");
5783
5791
  const apiBase = base.apiBase ?? config.region.apiBase;
5784
5792
  const resolveTokenFn = deps.resolveTokenFn ?? resolveTokenWithRefresh;
5785
5793
  const token = await resolveTokenFn(config.region.mcpName);
5786
- if (!token) return base;
5794
+ if (!token) return none("not-signed-in");
5787
5795
  const resolveByRepo = deps.resolveByRepo ?? resolveProjectByRepository;
5788
5796
  const matches = await resolveByRepo(
5789
5797
  apiBase,
@@ -5791,7 +5799,7 @@ async function resolveProjectIdWithApi(cwd = process.cwd(), deps = {}) {
5791
5799
  { provider: remote.provider, slug: remote.slug, host: remote.host },
5792
5800
  { timeoutMs: deps.timeoutMs ?? RESOLVE_API_TIMEOUT_MS }
5793
5801
  );
5794
- if (matches === null) return base;
5802
+ if (matches === null) return none("lookup-failed");
5795
5803
  if (matches.length === 1 && matches[0].vibedefendEnabled !== false) {
5796
5804
  const m = matches[0];
5797
5805
  writeCachedRepoLink(
@@ -5802,9 +5810,9 @@ async function resolveProjectIdWithApi(cwd = process.cwd(), deps = {}) {
5802
5810
  return { projectId: m.projectId, apiBase: base.apiBase };
5803
5811
  }
5804
5812
  writeCachedRepoLink(key, { negative: true }, deps.cacheDeps);
5805
- return base;
5813
+ return none("no-linked-project");
5806
5814
  } catch {
5807
- return base;
5815
+ return none("lookup-failed");
5808
5816
  }
5809
5817
  }
5810
5818
 
@@ -6064,6 +6072,12 @@ async function fetchProposalsCount(opts) {
6064
6072
  return { total, raw: body };
6065
6073
  }
6066
6074
 
6075
+ // src/hooks/runtime/session-activation.ts
6076
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
6077
+ import { homedir as homedir7 } from "node:os";
6078
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, resolve as resolvePath2 } from "node:path";
6079
+ import { fileURLToPath } from "node:url";
6080
+
6067
6081
  // src/hooks/runtime/emit.ts
6068
6082
  var ENVELOPE_CLIENTS = {
6069
6083
  SessionStart: /* @__PURE__ */ new Set(["codex"]),
@@ -6084,6 +6098,10 @@ function emit(body, client) {
6084
6098
  }
6085
6099
  function emitContext(body, client, hookEventName) {
6086
6100
  if (!body) return;
6101
+ if (client === "cursor" && hookEventName === "SessionStart") {
6102
+ process.stdout.write(JSON.stringify({ additional_context: body }) + "\n");
6103
+ return;
6104
+ }
6087
6105
  if (ENVELOPE_CLIENTS[hookEventName].has(client)) {
6088
6106
  const payload = JSON.stringify({
6089
6107
  hookSpecificOutput: {
@@ -6096,6 +6114,25 @@ function emitContext(body, client, hookEventName) {
6096
6114
  }
6097
6115
  emit(body, client);
6098
6116
  }
6117
+ var SYSTEM_MESSAGE_CLIENTS = /* @__PURE__ */ new Set([
6118
+ "claude-code",
6119
+ "codex"
6120
+ ]);
6121
+ function emitSessionNotice(notice, client) {
6122
+ if (SYSTEM_MESSAGE_CLIENTS.has(client)) {
6123
+ process.stdout.write(
6124
+ JSON.stringify({
6125
+ systemMessage: notice.userMessage,
6126
+ hookSpecificOutput: {
6127
+ hookEventName: "SessionStart",
6128
+ additionalContext: notice.modelContext
6129
+ }
6130
+ }) + "\n"
6131
+ );
6132
+ return;
6133
+ }
6134
+ emitContext(notice.modelContext, client, "SessionStart");
6135
+ }
6099
6136
  function emitStop(body, client, reason) {
6100
6137
  if (!body) return;
6101
6138
  if (client === "claude-code") {
@@ -6110,6 +6147,143 @@ function emitStop(body, client, reason) {
6110
6147
  emit(body, client);
6111
6148
  }
6112
6149
 
6150
+ // src/hooks/runtime/session-activation.ts
6151
+ var SETTLED_REASONS = /* @__PURE__ */ new Set([
6152
+ "no-git-origin",
6153
+ "unsupported-remote",
6154
+ "no-linked-project"
6155
+ ]);
6156
+ function isSettledReason(reason) {
6157
+ return SETTLED_REASONS.has(reason);
6158
+ }
6159
+ var SESSIONS_CAP = 200;
6160
+ function defaultStoreFile() {
6161
+ const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join6(homedir7(), ".cybedefend");
6162
+ return join6(dir, "inactive-sessions.json");
6163
+ }
6164
+ function defaultInactiveSessionStore(file = defaultStoreFile(), now = Date.now) {
6165
+ function load() {
6166
+ if (!existsSync8(file)) return {};
6167
+ try {
6168
+ const parsed = JSON.parse(readFileSync7(file, "utf8"));
6169
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
6170
+ } catch {
6171
+ return {};
6172
+ }
6173
+ }
6174
+ function save(map) {
6175
+ try {
6176
+ mkdirSync5(dirname4(file), { recursive: true });
6177
+ writeFileSync4(file, JSON.stringify(map) + "\n", "utf8");
6178
+ } catch {
6179
+ }
6180
+ }
6181
+ return {
6182
+ get(sessionId) {
6183
+ const rec = load()[sessionId];
6184
+ if (!rec || typeof rec !== "object" || typeof rec.cwd !== "string" || typeof rec.reason !== "string") {
6185
+ return null;
6186
+ }
6187
+ return rec;
6188
+ },
6189
+ mark(sessionId, record) {
6190
+ const map = load();
6191
+ map[sessionId] = { ...record, at: now() };
6192
+ const entries = Object.entries(map);
6193
+ if (entries.length > SESSIONS_CAP) {
6194
+ entries.sort(([, a], [, b]) => (a.at ?? 0) - (b.at ?? 0));
6195
+ save(Object.fromEntries(entries.slice(-SESSIONS_CAP)));
6196
+ return;
6197
+ }
6198
+ save(map);
6199
+ },
6200
+ clear(sessionId) {
6201
+ const map = load();
6202
+ if (!(sessionId in map)) return;
6203
+ delete map[sessionId];
6204
+ save(map);
6205
+ }
6206
+ };
6207
+ }
6208
+ function sessionIdOf(stdin) {
6209
+ for (const key of ["session_id", "sessionId", "conversation_id", "trajectory_id"]) {
6210
+ const v = stdin[key];
6211
+ if (typeof v === "string" && v.length > 0) return v;
6212
+ }
6213
+ return null;
6214
+ }
6215
+ function sessionFolderOf(stdin, client, env = process.env) {
6216
+ const candidates = [];
6217
+ if (client === "cursor") {
6218
+ const roots = stdin.workspace_roots;
6219
+ if (Array.isArray(roots)) candidates.push(roots[0]);
6220
+ candidates.push(env.CURSOR_PROJECT_DIR);
6221
+ }
6222
+ candidates.push(stdin.cwd);
6223
+ for (const c of candidates) {
6224
+ const folder = absoluteFolder(c);
6225
+ if (folder) return folder;
6226
+ }
6227
+ return process.cwd();
6228
+ }
6229
+ function absoluteFolder(value) {
6230
+ if (typeof value !== "string" || value.length === 0) return null;
6231
+ let p = value;
6232
+ if (p.startsWith("file://")) {
6233
+ try {
6234
+ p = fileURLToPath(p);
6235
+ } catch {
6236
+ return null;
6237
+ }
6238
+ }
6239
+ return isAbsolute2(p) ? resolvePath2(p) : null;
6240
+ }
6241
+ function isSessionInactive(stdin, deps = {}) {
6242
+ try {
6243
+ const sessionId = sessionIdOf(stdin);
6244
+ if (!sessionId) return false;
6245
+ const record = (deps.store ?? defaultInactiveSessionStore()).get(sessionId);
6246
+ if (!record || !isSettledReason(record.reason)) return false;
6247
+ const cwd = deps.cwd ?? process.cwd();
6248
+ if (record.cwd !== cwd) return false;
6249
+ const resolveSync = deps.resolveSync ?? resolveProjectId;
6250
+ return !resolveSync(cwd).projectId;
6251
+ } catch {
6252
+ return false;
6253
+ }
6254
+ }
6255
+ function whyInactive(reason, repository) {
6256
+ const repo = repository ? `\`${repository}\`` : "this folder";
6257
+ switch (reason) {
6258
+ case "no-git-origin":
6259
+ return "this folder has no `.cybedefend/config.json` and no GitHub or GitLab `origin` remote";
6260
+ case "unsupported-remote":
6261
+ return `this folder has no \`.cybedefend/config.json\` and its git \`origin\`${repository ? ` (${repo})` : ""} is not on GitHub or GitLab`;
6262
+ case "no-linked-project":
6263
+ return `this folder has no \`.cybedefend/config.json\` and no CybeDefend project with VibeDefend enabled matches ${repo}`;
6264
+ case "not-signed-in":
6265
+ return `you are not signed in, so the CybeDefend project for ${repo} could not be looked up`;
6266
+ case "lookup-failed":
6267
+ return `the CybeDefend API did not answer, so the project for ${repo} could not be looked up`;
6268
+ }
6269
+ }
6270
+ function composeInactiveNotice(reason, repository, client) {
6271
+ const why = whyInactive(reason, repository);
6272
+ const settled = isSettledReason(reason);
6273
+ const userMessage = settled ? `\u{1F6E1}\uFE0F CybeDefend is off for this session: ${why}. Run \`vibedefend link\` in this folder to turn it on.` : reason === "not-signed-in" ? `\u{1F6E1}\uFE0F CybeDefend is off for now: ${why}. Run \`vibedefend login\` to turn it on.` : `\u{1F6E1}\uFE0F CybeDefend is off for now: ${why}. It will try again on the next prompt or edit.`;
6274
+ const scope = settled ? "- Its hooks stay off until this session ends: no rules fetch, no proposal inbox, no Action Guards, no end-of-session review." : "- Its hooks stay quiet until the project can be found.";
6275
+ const relay = SYSTEM_MESSAGE_CLIENTS.has(client) ? "- The user has already been shown this notice; do not repeat it." : "- Tell the user once, in one short line, that CybeDefend is off for this session and why.";
6276
+ const modelContext = [
6277
+ "## CybeDefend is not active in this session",
6278
+ `CybeDefend found no project for this folder: ${why}.`,
6279
+ scope,
6280
+ "- Do not call CybeDefend MCP tools, run the CybeDefend workflow or ask for a CybeDefend project id unless the user asks about CybeDefend.",
6281
+ relay,
6282
+ ""
6283
+ ].join("\n");
6284
+ return { userMessage, modelContext };
6285
+ }
6286
+
6113
6287
  // src/hooks/runtime/fetch-rules.ts
6114
6288
  function emitPreToolUseContext(body, client) {
6115
6289
  emitContext(body, client, "PreToolUse");
@@ -6136,7 +6310,9 @@ async function fetchRulesHook(deps = {}) {
6136
6310
  if (!event.filePath) return;
6137
6311
  const config = loadConfig();
6138
6312
  if (!config) return;
6139
- const { projectId, apiBase: configApiBase } = await resolveProject();
6313
+ const folder = sessionFolderOf(stdin, event.client);
6314
+ if (isSessionInactive(stdin, { store: deps.sessionStore, cwd: folder })) return;
6315
+ const { projectId, apiBase: configApiBase } = await resolveProject(folder);
6140
6316
  if (!projectId) return;
6141
6317
  const apiBase = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
6142
6318
  const token = resolveTok(config.region.mcpName, {
@@ -6146,7 +6322,7 @@ async function fetchRulesHook(deps = {}) {
6146
6322
  const intent = buildIntent(event);
6147
6323
  const files = event.filePaths.length > 0 ? event.filePaths : [event.filePath];
6148
6324
  const fileLabel = files.join(", ");
6149
- const sessionId = sessionIdOf(stdin);
6325
+ const sessionId = sessionIdOf2(stdin);
6150
6326
  const usageNotices = [];
6151
6327
  const onUsageSignal = (signal) => {
6152
6328
  const sentence = signal.kind === "blocked" ? usageNotifier.notifyBlock(sessionId, signal.block) : usageNotifier.notifyWarning(sessionId, signal.warning);
@@ -6190,7 +6366,7 @@ async function fetchRulesHook(deps = {}) {
6190
6366
  }
6191
6367
  if (blocks.length > 0) emitFn(blocks.join("\n"), event.client);
6192
6368
  }
6193
- function sessionIdOf(stdin) {
6369
+ function sessionIdOf2(stdin) {
6194
6370
  if (typeof stdin.session_id === "string" && stdin.session_id) return stdin.session_id;
6195
6371
  if (typeof stdin.sessionId === "string" && stdin.sessionId) return stdin.sessionId;
6196
6372
  return void 0;
@@ -6275,9 +6451,9 @@ var DIRECT_PROPOSE_DIRECTIVES = {
6275
6451
 
6276
6452
  // src/hooks/runtime/self-update-check.ts
6277
6453
  import { spawn } from "node:child_process";
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";
6454
+ import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
6455
+ import { homedir as homedir8 } from "node:os";
6456
+ import { join as join7 } from "node:path";
6281
6457
 
6282
6458
  // src/hooks/runtime/semver.ts
6283
6459
  function parseVersion(input) {
@@ -6304,8 +6480,8 @@ function isNewer(latest, current) {
6304
6480
  }
6305
6481
 
6306
6482
  // src/self-update.ts
6307
- import { fileURLToPath } from "node:url";
6308
- import { existsSync as existsSync8, realpathSync } from "node:fs";
6483
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6484
+ import { existsSync as existsSync9, realpathSync } from "node:fs";
6309
6485
  var GLOBAL_MODES = /* @__PURE__ */ new Set([
6310
6486
  "npm-global",
6311
6487
  "pnpm-global",
@@ -6333,14 +6509,14 @@ function detectInstallMode(scriptPath) {
6333
6509
  }
6334
6510
  function resolveScriptPath() {
6335
6511
  const fromArgv = process.argv[1];
6336
- if (fromArgv && existsSync8(fromArgv)) {
6512
+ if (fromArgv && existsSync9(fromArgv)) {
6337
6513
  try {
6338
6514
  return realpathSync(fromArgv);
6339
6515
  } catch {
6340
6516
  return fromArgv;
6341
6517
  }
6342
6518
  }
6343
- return fileURLToPath(import.meta.url);
6519
+ return fileURLToPath2(import.meta.url);
6344
6520
  }
6345
6521
 
6346
6522
  // src/hooks/runtime/self-update-check.ts
@@ -6355,9 +6531,9 @@ function isValidRegistryVersion(value) {
6355
6531
  }
6356
6532
  async function selfUpdateCheck(deps) {
6357
6533
  const now = deps.now ?? (() => Date.now());
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");
6534
+ const dir = deps.cybeDir ?? join7(process.env.CYBEDEFEND_HOME ?? homedir8(), ".cybedefend");
6535
+ const throttlePath = join7(dir, "update-check.json");
6536
+ const logPath = join7(dir, "last-update.log");
6361
6537
  const installMode = deps.installMode ?? (deps.runnerSource ? detectInstallMode(deps.runnerSource) : detectInstallMode(resolveScriptPath()));
6362
6538
  const priorFailure = takeUpdateLogNotice(logPath);
6363
6539
  const throttle = readThrottle(throttlePath);
@@ -6390,8 +6566,8 @@ function applyOrNotify(deps, installMode, latest, logPath) {
6390
6566
  }
6391
6567
  function readThrottle(path) {
6392
6568
  try {
6393
- if (!existsSync9(path)) return null;
6394
- const data = JSON.parse(readFileSync7(path, "utf8"));
6569
+ if (!existsSync10(path)) return null;
6570
+ const data = JSON.parse(readFileSync8(path, "utf8"));
6395
6571
  if (typeof data.lastCheckedAt !== "number") return null;
6396
6572
  return { lastCheckedAt: data.lastCheckedAt };
6397
6573
  } catch {
@@ -6401,16 +6577,16 @@ function readThrottle(path) {
6401
6577
  function writeThrottle(path, throttle) {
6402
6578
  try {
6403
6579
  ensureDirFor(path);
6404
- writeFileSync4(path, JSON.stringify(throttle) + "\n");
6580
+ writeFileSync5(path, JSON.stringify(throttle) + "\n");
6405
6581
  } catch {
6406
6582
  }
6407
6583
  }
6408
6584
  function takeUpdateLogNotice(logPath) {
6409
6585
  try {
6410
- if (!existsSync9(logPath)) return null;
6586
+ if (!existsSync10(logPath)) return null;
6411
6587
  let parsed = {};
6412
6588
  try {
6413
- parsed = JSON.parse(readFileSync7(logPath, "utf8"));
6589
+ parsed = JSON.parse(readFileSync8(logPath, "utf8"));
6414
6590
  } catch {
6415
6591
  parsed = {};
6416
6592
  }
@@ -6485,18 +6661,18 @@ function pmCommand(mode, spec) {
6485
6661
 
6486
6662
  // src/hooks/runtime/hook-refresh-trigger.ts
6487
6663
  import { spawn as spawn2 } from "node:child_process";
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";
6664
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
6665
+ import { homedir as homedir9 } from "node:os";
6666
+ import { dirname as dirname5, join as join8 } from "node:path";
6491
6667
  function maybeRefreshStaleWiring(deps) {
6492
6668
  try {
6493
6669
  const { config, runnerVersion } = deps;
6494
6670
  if (!config.runnerSource || config.installedVersion === runnerVersion) {
6495
6671
  return false;
6496
6672
  }
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");
6673
+ const cliEntry = join8(dirname5(dirname5(config.runnerSource)), "bin", "vibedefend.js");
6674
+ if (!(deps.exists ?? existsSync11)(cliEntry)) return false;
6675
+ const markerPath = deps.markerPath ?? join8(process.env.CYBEDEFEND_HOME ?? homedir9(), ".cybedefend", "hook-refresh.json");
6500
6676
  if (readMarkerVersion(markerPath) === runnerVersion) return false;
6501
6677
  (deps.spawnRefresh ?? defaultSpawnRefresh)(cliEntry);
6502
6678
  writeMarker(markerPath, runnerVersion);
@@ -6507,8 +6683,8 @@ function maybeRefreshStaleWiring(deps) {
6507
6683
  }
6508
6684
  function readMarkerVersion(path) {
6509
6685
  try {
6510
- if (!existsSync10(path)) return null;
6511
- const data = JSON.parse(readFileSync8(path, "utf8"));
6686
+ if (!existsSync11(path)) return null;
6687
+ const data = JSON.parse(readFileSync9(path, "utf8"));
6512
6688
  return typeof data.version === "string" ? data.version : null;
6513
6689
  } catch {
6514
6690
  return null;
@@ -6517,7 +6693,7 @@ function readMarkerVersion(path) {
6517
6693
  function writeMarker(path, version) {
6518
6694
  try {
6519
6695
  ensureDirFor(path);
6520
- writeFileSync5(path, JSON.stringify({ version, spawnedAt: Date.now() }) + "\n");
6696
+ writeFileSync6(path, JSON.stringify({ version, spawnedAt: Date.now() }) + "\n");
6521
6697
  } catch {
6522
6698
  }
6523
6699
  }
@@ -6531,18 +6707,18 @@ function defaultSpawnRefresh(cliEntry) {
6531
6707
  }
6532
6708
 
6533
6709
  // src/version.ts
6534
- import { readFileSync as readFileSync9 } from "node:fs";
6535
- import { fileURLToPath as fileURLToPath2 } from "node:url";
6536
- import { dirname as dirname5, join as join8 } from "node:path";
6710
+ import { readFileSync as readFileSync10 } from "node:fs";
6711
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
6712
+ import { dirname as dirname6, join as join9 } from "node:path";
6537
6713
  var PKG_NAME = "@cybedefend/vibedefend";
6538
6714
  function defaultPkgPath() {
6539
- return join8(dirname5(fileURLToPath2(import.meta.url)), "..", "package.json");
6715
+ return join9(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
6540
6716
  }
6541
6717
  function readPackageMeta(opts = {}) {
6542
6718
  if (typeof opts.bakedVersion === "string" && opts.bakedVersion.length > 0) {
6543
6719
  return { name: PKG_NAME, version: opts.bakedVersion };
6544
6720
  }
6545
- const readFile = opts.readFile ?? ((p) => readFileSync9(p, "utf8"));
6721
+ const readFile = opts.readFile ?? ((p) => readFileSync10(p, "utf8"));
6546
6722
  const pkgPath = opts.pkgPath ?? defaultPkgPath();
6547
6723
  try {
6548
6724
  return JSON.parse(readFile(pkgPath));
@@ -6551,13 +6727,22 @@ function readPackageMeta(opts = {}) {
6551
6727
  }
6552
6728
  }
6553
6729
  var pkg = readPackageMeta({
6554
- bakedVersion: true ? "1.4.7" : void 0
6730
+ bakedVersion: true ? "1.5.0" : void 0
6555
6731
  });
6556
6732
 
6557
6733
  // src/hooks/runtime/session-start.ts
6558
6734
  function emitSessionStartContext(body, client) {
6559
6735
  emitContext(body, client, "SessionStart");
6560
6736
  }
6737
+ function announceInactive(resolution, sessionId, cwd, store, client, emitNoticeFn) {
6738
+ const reason = resolution.inactiveReason ?? "no-linked-project";
6739
+ if (sessionId) {
6740
+ const prior = store.get(sessionId);
6741
+ if (prior && prior.cwd === cwd && prior.reason === reason) return;
6742
+ store.mark(sessionId, { cwd, reason });
6743
+ }
6744
+ emitNoticeFn(composeInactiveNotice(reason, resolution.repository, client), client);
6745
+ }
6561
6746
  async function sessionStartHook(deps = {}) {
6562
6747
  const readStdin = deps.readStdin ?? readStdinJson;
6563
6748
  const loadConfig = deps.loadConfig ?? loadRuntimeConfig;
@@ -6566,6 +6751,7 @@ async function sessionStartHook(deps = {}) {
6566
6751
  const fetchProposals = deps.fetchProposalsFn ?? fetchProposalsCount;
6567
6752
  const selfUpdateCheckFn = deps.selfUpdateCheckFn ?? selfUpdateCheck;
6568
6753
  const emitFn = deps.emitFn ?? emitSessionStartContext;
6754
+ const emitNoticeFn = deps.emitNoticeFn ?? emitSessionNotice;
6569
6755
  const stdin = await readStdin();
6570
6756
  const event = sniff(stdin);
6571
6757
  const config = loadConfig();
@@ -6574,8 +6760,16 @@ async function sessionStartHook(deps = {}) {
6574
6760
  config,
6575
6761
  runnerVersion: pkg.version
6576
6762
  });
6577
- const { projectId, apiBase: configApiBase } = await resolveProject();
6578
- if (!projectId) return;
6763
+ const folder = sessionFolderOf(stdin, event.client);
6764
+ const resolution = await resolveProject(folder);
6765
+ const { projectId, apiBase: configApiBase } = resolution;
6766
+ const sessionStore = deps.sessionStore ?? defaultInactiveSessionStore();
6767
+ const sessionId = sessionIdOf(stdin);
6768
+ if (!projectId) {
6769
+ announceInactive(resolution, sessionId, folder, sessionStore, event.client, emitNoticeFn);
6770
+ return;
6771
+ }
6772
+ if (sessionId) sessionStore.clear(sessionId);
6579
6773
  const apiBase = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
6580
6774
  if (process.env.CYBEDEFEND_DRY_RUN === "1") {
6581
6775
  emitFn(
@@ -6667,7 +6861,7 @@ async function sessionStartHook(deps = {}) {
6667
6861
  }
6668
6862
 
6669
6863
  // src/hooks/runtime/session-review.ts
6670
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
6864
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
6671
6865
  var COUNTED_TOOLS = /* @__PURE__ */ new Set([
6672
6866
  "Edit",
6673
6867
  "Write",
@@ -6709,6 +6903,10 @@ async function sessionReviewHook(deps = {}) {
6709
6903
  }
6710
6904
  if (editCount === 0) return;
6711
6905
  if (editCount < resolveReviewThreshold(config)) return;
6906
+ const folder = sessionFolderOf(stdin, event.client);
6907
+ if (isSessionInactive(stdin, { store: deps.sessionStore, cwd: folder })) return;
6908
+ const { projectId } = await (deps.resolveProject ?? resolveProjectIdWithApi)(folder);
6909
+ if (!projectId) return;
6712
6910
  const userPrompt = extractFirstUserPrompt(transcript);
6713
6911
  const proposalMode = resolveProposalMode(config);
6714
6912
  let body = composeReviewBody(
@@ -6719,8 +6917,7 @@ async function sessionReviewHook(deps = {}) {
6719
6917
  );
6720
6918
  const withDiffScan = config.hooks.enableDiffScan !== false;
6721
6919
  if (withDiffScan) {
6722
- const cwd = typeof stdin.cwd === "string" && stdin.cwd ? stdin.cwd : process.cwd();
6723
- body += "\n" + composeDiffScanBody(extractChangedFiles(transcript, void 0, cwd));
6920
+ body += "\n" + composeDiffScanBody(extractChangedFiles(transcript, void 0, folder));
6724
6921
  }
6725
6922
  emitFn(body, event.client, composeStopReason(withDiffScan, proposalMode));
6726
6923
  }
@@ -6751,9 +6948,9 @@ function loadTranscript(stdin, readFile) {
6751
6948
  return out;
6752
6949
  }
6753
6950
  function defaultReadTranscript(path) {
6754
- if (!existsSync11(path)) return null;
6951
+ if (!existsSync12(path)) return null;
6755
6952
  try {
6756
- return readFileSync10(path, "utf8");
6953
+ return readFileSync11(path, "utf8");
6757
6954
  } catch {
6758
6955
  return null;
6759
6956
  }
@@ -6986,6 +7183,10 @@ async function preCompactHook(deps = {}) {
6986
7183
  const config = loadConfig();
6987
7184
  if (!config) return;
6988
7185
  if (!config.hooks.enableSessionReview) return;
7186
+ const folder = sessionFolderOf(stdin, event.client);
7187
+ if (isSessionInactive(stdin, { store: deps.sessionStore, cwd: folder })) return;
7188
+ const { projectId } = await (deps.resolveProject ?? resolveProjectIdWithApi)(folder);
7189
+ if (!projectId) return;
6989
7190
  if (process.env.CYBEDEFEND_DRY_RUN === "1") {
6990
7191
  emitFn(
6991
7192
  `## \u{1F6E1}\uFE0F CybeDefend PreCompact (dry-run, client=${event.client})
@@ -7013,19 +7214,19 @@ async function preCompactHook(deps = {}) {
7013
7214
  }
7014
7215
 
7015
7216
  // src/hooks/runtime/user-prompt-submit.ts
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";
7019
- var SESSIONS_CAP = 100;
7217
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
7218
+ import { homedir as homedir10 } from "node:os";
7219
+ import { join as join10 } from "node:path";
7220
+ var SESSIONS_CAP2 = 100;
7020
7221
  function defaultSessionsFile() {
7021
- const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join9(homedir9(), ".cybedefend");
7022
- return join9(dir, "user-prompt-seen-sessions.json");
7222
+ const dir = process.env.CYBEDEFEND_BASELINE_DIR ?? join10(homedir10(), ".cybedefend");
7223
+ return join10(dir, "user-prompt-seen-sessions.json");
7023
7224
  }
7024
7225
  function defaultSessionTracker(file = defaultSessionsFile()) {
7025
7226
  function load() {
7026
- if (!existsSync12(file)) return [];
7227
+ if (!existsSync13(file)) return [];
7027
7228
  try {
7028
- const raw = readFileSync11(file, "utf8");
7229
+ const raw = readFileSync12(file, "utf8");
7029
7230
  const data = JSON.parse(raw);
7030
7231
  if (!Array.isArray(data.sessions)) return [];
7031
7232
  return data.sessions.filter((s) => typeof s === "string");
@@ -7040,10 +7241,10 @@ function defaultSessionTracker(file = defaultSessionsFile()) {
7040
7241
  async markSeen(sessionId) {
7041
7242
  const existing = load();
7042
7243
  if (existing.includes(sessionId)) return;
7043
- const next = [...existing, sessionId].slice(-SESSIONS_CAP);
7244
+ const next = [...existing, sessionId].slice(-SESSIONS_CAP2);
7044
7245
  try {
7045
- mkdirSync5(join9(file, ".."), { recursive: true });
7046
- writeFileSync6(file, JSON.stringify({ sessions: next }), "utf8");
7246
+ mkdirSync6(join10(file, ".."), { recursive: true });
7247
+ writeFileSync7(file, JSON.stringify({ sessions: next }), "utf8");
7047
7248
  } catch {
7048
7249
  }
7049
7250
  }
@@ -7065,7 +7266,9 @@ async function userPromptSubmitHook(deps = {}) {
7065
7266
  }
7066
7267
  const config = loadConfig();
7067
7268
  if (!config) return;
7068
- const { projectId, apiBase: configApiBase } = await resolveProject();
7269
+ const folder = sessionFolderOf(stdin, event.client);
7270
+ if (isSessionInactive(stdin, { store: deps.sessionStore, cwd: folder })) return;
7271
+ const { projectId, apiBase: configApiBase } = await resolveProject(folder);
7069
7272
  if (!projectId) return;
7070
7273
  const apiBase = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
7071
7274
  const token = resolveTok(config.region.mcpName, {
@@ -8114,10 +8317,10 @@ function nearMiss(req, rule) {
8114
8317
  }
8115
8318
 
8116
8319
  // src/hooks/runtime/guard-rules-cache.ts
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";
8320
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
8321
+ import { dirname as dirname7 } from "node:path";
8322
+ import { homedir as homedir11 } from "node:os";
8323
+ import { join as join11 } from "node:path";
8121
8324
 
8122
8325
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
8123
8326
  var external_exports = {};
@@ -12229,8 +12432,8 @@ function parseRuleFields(raw) {
12229
12432
  }
12230
12433
 
12231
12434
  // src/hooks/runtime/guard-rules-cache.ts
12232
- var DEFAULT_CACHE_FILE = join10(
12233
- homedir10(),
12435
+ var DEFAULT_CACHE_FILE = join11(
12436
+ homedir11(),
12234
12437
  ".cybedefend",
12235
12438
  "guards-cache.json"
12236
12439
  );
@@ -12366,9 +12569,9 @@ function makeGuardRulesCache(opts) {
12366
12569
  }
12367
12570
  }
12368
12571
  function readCache(projectId) {
12369
- if (!existsSync13(cacheFile)) return null;
12572
+ if (!existsSync14(cacheFile)) return null;
12370
12573
  try {
12371
- const raw = readFileSync12(cacheFile, "utf8");
12574
+ const raw = readFileSync13(cacheFile, "utf8");
12372
12575
  const data = JSON.parse(raw);
12373
12576
  if (data.projectId !== projectId) return null;
12374
12577
  return data;
@@ -12379,16 +12582,16 @@ function makeGuardRulesCache(opts) {
12379
12582
  function writeCache(entry) {
12380
12583
  try {
12381
12584
  ensureDirFor2(cacheFile);
12382
- writeFileSync7(cacheFile, JSON.stringify(entry), "utf8");
12585
+ writeFileSync8(cacheFile, JSON.stringify(entry), "utf8");
12383
12586
  } catch {
12384
12587
  }
12385
12588
  }
12386
12589
  return { refreshIfStale };
12387
12590
  }
12388
12591
  function ensureDirFor2(filePath) {
12389
- const dir = dirname6(filePath);
12390
- if (!existsSync13(dir)) {
12391
- mkdirSync6(dir, { recursive: true });
12592
+ const dir = dirname7(filePath);
12593
+ if (!existsSync14(dir)) {
12594
+ mkdirSync7(dir, { recursive: true });
12392
12595
  }
12393
12596
  }
12394
12597
  var DEFAULT_TIMEOUT_MS2 = 8e3;
@@ -12521,11 +12724,11 @@ function detectPromptInjection(text) {
12521
12724
  }
12522
12725
 
12523
12726
  // src/hooks/runtime/judge-cache.ts
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(),
12727
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync8 } from "node:fs";
12728
+ import { dirname as dirname8, join as join12 } from "node:path";
12729
+ import { homedir as homedir12 } from "node:os";
12730
+ var DEFAULT_JUDGE_CACHE_FILE = join12(
12731
+ homedir12(),
12529
12732
  ".cybedefend",
12530
12733
  "guards-judge-cache.json"
12531
12734
  );
@@ -12535,9 +12738,9 @@ function makeJudgeCache(opts = {}) {
12535
12738
  const ttlMs = opts.ttlMs ?? DEFAULT_JUDGE_TTL_MS;
12536
12739
  const now = opts.now ?? (() => Date.now());
12537
12740
  function readAll() {
12538
- if (!existsSync14(cacheFile)) return {};
12741
+ if (!existsSync15(cacheFile)) return {};
12539
12742
  try {
12540
- return JSON.parse(readFileSync13(cacheFile, "utf8"));
12743
+ return JSON.parse(readFileSync14(cacheFile, "utf8"));
12541
12744
  } catch {
12542
12745
  return {};
12543
12746
  }
@@ -12557,36 +12760,36 @@ function makeJudgeCache(opts = {}) {
12557
12760
  }
12558
12761
  all[key] = { res, at: t };
12559
12762
  ensureDirFor3(cacheFile);
12560
- writeFileSync8(cacheFile, JSON.stringify(all), "utf8");
12763
+ writeFileSync9(cacheFile, JSON.stringify(all), "utf8");
12561
12764
  } catch {
12562
12765
  }
12563
12766
  }
12564
12767
  return { get, set };
12565
12768
  }
12566
12769
  function ensureDirFor3(filePath) {
12567
- const dir = dirname7(filePath);
12568
- if (!existsSync14(dir)) {
12569
- mkdirSync7(dir, { recursive: true });
12770
+ const dir = dirname8(filePath);
12771
+ if (!existsSync15(dir)) {
12772
+ mkdirSync8(dir, { recursive: true });
12570
12773
  }
12571
12774
  }
12572
12775
 
12573
12776
  // src/hooks/runtime/guard-check.ts
12574
- import { homedir as homedir13 } from "node:os";
12575
- import { join as join13 } from "node:path";
12777
+ import { homedir as homedir14 } from "node:os";
12778
+ import { join as join14 } from "node:path";
12576
12779
 
12577
12780
  // src/hooks/runtime/guard-violations-buffer.ts
12578
12781
  import {
12579
12782
  appendFileSync,
12580
- existsSync as existsSync15,
12581
- readFileSync as readFileSync14,
12582
- writeFileSync as writeFileSync9,
12583
- mkdirSync as mkdirSync8
12783
+ existsSync as existsSync16,
12784
+ readFileSync as readFileSync15,
12785
+ writeFileSync as writeFileSync10,
12786
+ mkdirSync as mkdirSync9
12584
12787
  } from "node:fs";
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(),
12788
+ import { dirname as dirname9 } from "node:path";
12789
+ import { homedir as homedir13 } from "node:os";
12790
+ import { join as join13 } from "node:path";
12791
+ var DEFAULT_BUFFER_FILE = join13(
12792
+ homedir13(),
12590
12793
  ".cybedefend",
12591
12794
  "guards-violations-buffer.jsonl"
12592
12795
  );
@@ -12605,10 +12808,10 @@ function makeViolationsBuffer(opts = {}) {
12605
12808
  }
12606
12809
  }
12607
12810
  async function flushViolations2(ctx, postFn) {
12608
- if (!existsSync15(bufferFile)) return;
12811
+ if (!existsSync16(bufferFile)) return;
12609
12812
  let content;
12610
12813
  try {
12611
- content = readFileSync14(bufferFile, "utf8").trim();
12814
+ content = readFileSync15(bufferFile, "utf8").trim();
12612
12815
  } catch {
12613
12816
  return;
12614
12817
  }
@@ -12627,7 +12830,7 @@ function makeViolationsBuffer(opts = {}) {
12627
12830
  }
12628
12831
  if (records.length === 0) {
12629
12832
  try {
12630
- writeFileSync9(bufferFile, "", "utf8");
12833
+ writeFileSync10(bufferFile, "", "utf8");
12631
12834
  } catch {
12632
12835
  }
12633
12836
  return;
@@ -12657,10 +12860,10 @@ function makeViolationsBuffer(opts = {}) {
12657
12860
  }
12658
12861
  try {
12659
12862
  if (failed.length === 0) {
12660
- writeFileSync9(bufferFile, "", "utf8");
12863
+ writeFileSync10(bufferFile, "", "utf8");
12661
12864
  } else {
12662
12865
  const serialised = failed.map((r) => JSON.stringify(r)).join("\n") + "\n";
12663
- writeFileSync9(bufferFile, serialised, "utf8");
12866
+ writeFileSync10(bufferFile, serialised, "utf8");
12664
12867
  }
12665
12868
  } catch {
12666
12869
  }
@@ -12668,9 +12871,9 @@ function makeViolationsBuffer(opts = {}) {
12668
12871
  return { bufferViolation: bufferViolation2, flushViolations: flushViolations2 };
12669
12872
  }
12670
12873
  function ensureDirFor4(filePath) {
12671
- const dir = dirname8(filePath);
12672
- if (!existsSync15(dir)) {
12673
- mkdirSync8(dir, { recursive: true });
12874
+ const dir = dirname9(filePath);
12875
+ if (!existsSync16(dir)) {
12876
+ mkdirSync9(dir, { recursive: true });
12674
12877
  }
12675
12878
  }
12676
12879
  var _defaultBuffer = makeViolationsBuffer();
@@ -12696,11 +12899,11 @@ function appendDebugLog(entry) {
12696
12899
  try {
12697
12900
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
12698
12901
  if (!homeDir) return;
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");
12902
+ const { mkdirSync: mkdirSync10, appendFileSync: appendFileSync2 } = __require("node:fs");
12903
+ const { join: join15 } = __require("node:path");
12904
+ const dir = join15(homeDir, ".cybedefend");
12905
+ mkdirSync10(dir, { recursive: true });
12906
+ appendFileSync2(join15(dir, "hook-debug.log"), entry + "\n", "utf8");
12704
12907
  } catch {
12705
12908
  }
12706
12909
  }
@@ -13071,7 +13274,7 @@ function emitUnverifiedBanner(reason) {
13071
13274
  }
13072
13275
  }
13073
13276
  }
13074
- function sessionIdOf2(envelope) {
13277
+ function sessionIdOf3(envelope) {
13075
13278
  return typeof envelope.session_id === "string" ? envelope.session_id : typeof envelope.sessionId === "string" ? envelope.sessionId : void 0;
13076
13279
  }
13077
13280
  async function runPreToolUseGuardWith(opts) {
@@ -13091,7 +13294,7 @@ async function runPreToolUseGuardWith(opts) {
13091
13294
  }
13092
13295
  if (rulesOutcome.kind === "blocked") {
13093
13296
  const sentence = opts.usageNotifier?.notifyBlock(
13094
- sessionIdOf2(stdinEnvelope),
13297
+ sessionIdOf3(stdinEnvelope),
13095
13298
  rulesOutcome.block
13096
13299
  );
13097
13300
  if (sentence) emitAgentMessage("warn", sentence);
@@ -13101,7 +13304,7 @@ async function runPreToolUseGuardWith(opts) {
13101
13304
  const caller = sniff(stdinEnvelope);
13102
13305
  const partial = mapEnvelopeToGuardRequest(stdinEnvelope, caller.client);
13103
13306
  if (partial === null) return 0;
13104
- const sessionId = sessionIdOf2(stdinEnvelope);
13307
+ const sessionId = sessionIdOf3(stdinEnvelope);
13105
13308
  const guardRequest = {
13106
13309
  ...partial,
13107
13310
  projectId: projectContext.projectId,
@@ -13245,12 +13448,12 @@ function emitAgentMessage(verdict, message) {
13245
13448
  }
13246
13449
  function makeProductionJudgeDeps() {
13247
13450
  const cache = makeJudgeCache();
13248
- const warnDir = process.env.CYBEDEFEND_BASELINE_DIR ?? join13(homedir13(), ".cybedefend");
13451
+ const warnDir = process.env.CYBEDEFEND_BASELINE_DIR ?? join14(homedir14(), ".cybedefend");
13249
13452
  const warnTracker = defaultSessionTracker(
13250
- join13(warnDir, "guards-judge-warned-sessions.json")
13453
+ join14(warnDir, "guards-judge-warned-sessions.json")
13251
13454
  );
13252
13455
  const blockedTracker = defaultSessionTracker(
13253
- join13(warnDir, "guards-judge-blocked-sessions.json")
13456
+ join14(warnDir, "guards-judge-blocked-sessions.json")
13254
13457
  );
13255
13458
  return {
13256
13459
  usage: {
@@ -13279,7 +13482,9 @@ async function runPreToolUseGuard() {
13279
13482
  const envelope = await readStdinJson();
13280
13483
  const config = loadRuntimeConfig();
13281
13484
  if (!config) return 0;
13282
- const { projectId, apiBase: configApiBase } = await resolveProjectIdWithApi();
13485
+ const folder = sessionFolderOf(envelope, sniff(envelope).client);
13486
+ if (isSessionInactive(envelope, { cwd: folder })) return 0;
13487
+ const { projectId, apiBase: configApiBase } = await resolveProjectIdWithApi(folder);
13283
13488
  if (!projectId) return 0;
13284
13489
  const apiBaseResolved = process.env.CYBEDEFEND_API_BASE ?? configApiBase ?? config.region.apiBase;
13285
13490
  const token = await resolveTokenWithRefresh(