@codacy/verity-cli 0.27.2 → 0.28.1-experimental.9155758

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/verity.js CHANGED
@@ -10327,10 +10327,6 @@ var {
10327
10327
  // src/commands/auth.ts
10328
10328
  var import_node_child_process4 = require("node:child_process");
10329
10329
 
10330
- // src/lib/auth.ts
10331
- var import_promises = require("node:fs/promises");
10332
- var import_node_child_process2 = require("node:child_process");
10333
-
10334
10330
  // src/constants.ts
10335
10331
  var import_node_child_process = require("node:child_process");
10336
10332
  var import_node_path = require("node:path");
@@ -10347,7 +10343,6 @@ function repoRoot() {
10347
10343
  }
10348
10344
  var VERITY_DIR = ".verity";
10349
10345
  var CREDENTIALS_FILE = `${VERITY_DIR}/credentials`;
10350
- var GLOBAL_CREDENTIALS_FILE = `${process.env.HOME}/.verity/credentials`;
10351
10346
  var DEBOUNCE_FILE = `${VERITY_DIR}/.last-analysis`;
10352
10347
  var ITERATION_FILE = `${VERITY_DIR}/.iteration-count`;
10353
10348
  var HASH_FILE = `${VERITY_DIR}/.last-pass-hash`;
@@ -10473,7 +10468,7 @@ var SECURITY_PATTERNS = [
10473
10468
  /Dockerfile/
10474
10469
  ];
10475
10470
  var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10476
- var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
10471
+ var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
10477
10472
  var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
10478
10473
  var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
10479
10474
  var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
@@ -10551,6 +10546,13 @@ function rotateIfNeeded(file) {
10551
10546
  }
10552
10547
 
10553
10548
  // src/lib/api-client.ts
10549
+ function describeFetchError(err, url) {
10550
+ const message = err instanceof Error ? err.message : String(err);
10551
+ const cause = err?.cause;
10552
+ const causeBits = [cause?.code, cause?.hostname].filter(Boolean).join(" ");
10553
+ const detail = causeBits || (cause?.message && cause.message !== message ? cause.message : "");
10554
+ return `${message}${detail ? ` (${detail})` : ""} \u2014 could not reach ${url}`;
10555
+ }
10554
10556
  async function apiRequest(options) {
10555
10557
  const {
10556
10558
  method,
@@ -10606,7 +10608,7 @@ async function apiRequest(options) {
10606
10608
  const duration2 = Date.now() - startedAt;
10607
10609
  const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
10608
10610
  const category = isTimeout ? "timeout" : "network";
10609
- const error = isTimeout ? `Request timed out after ${timeout}ms` : `Network error: ${err.message}`;
10611
+ const error = isTimeout ? `Request timed out after ${timeout}ms (${url})` : `Network error: ${describeFetchError(err, url)}`;
10610
10612
  logHttpCall({ ...logBase, duration_ms: duration2, http_status: null, category, error });
10611
10613
  return { ok: false, error, category, http_status: null };
10612
10614
  }
@@ -10665,111 +10667,287 @@ function analyzeRequest(options) {
10665
10667
  });
10666
10668
  }
10667
10669
 
10668
- // src/lib/auth.ts
10669
- function parseIdentity(content) {
10670
- const idMatch = content.match(/^user_id:\s*(\d+)/m);
10671
- const emailMatch = content.match(/^email:\s*(\S+)/m);
10670
+ // src/lib/service-url.ts
10671
+ var import_promises2 = require("node:fs/promises");
10672
+
10673
+ // src/lib/credentials.ts
10674
+ var import_promises = require("node:fs/promises");
10675
+ var import_node_path2 = require("node:path");
10676
+ var import_node_child_process2 = require("node:child_process");
10677
+ function globalCredentialsPath() {
10678
+ return `${process.env.HOME}/.verity/credentials`;
10679
+ }
10680
+ function currentRemote() {
10681
+ try {
10682
+ return (0, import_node_child_process2.execSync)("git remote get-url origin", {
10683
+ encoding: "utf-8",
10684
+ stdio: ["pipe", "pipe", "pipe"]
10685
+ }).trim();
10686
+ } catch {
10687
+ return "";
10688
+ }
10689
+ }
10690
+ var TOKEN_RE = /token:\s*((?:gate_|verity_)[a-f0-9]+)/;
10691
+ function parseCredentialLine(line) {
10692
+ const idx = line.indexOf(" token:");
10693
+ const remote = idx === -1 ? "" : line.slice(0, idx).trim();
10694
+ const rest = idx === -1 ? line : line.slice(idx);
10695
+ const tokenMatch = rest.match(TOKEN_RE);
10696
+ if (!tokenMatch) return null;
10697
+ const serviceUrl = rest.match(/service_url:\s*(https?:\/\/\S+)/)?.[1];
10698
+ const userIdRaw = rest.match(/user_id:\s*(\d+)/)?.[1];
10699
+ const email = rest.match(/email:\s*(\S+)/)?.[1];
10672
10700
  return {
10673
- userId: idMatch ? Number(idMatch[1]) : void 0,
10674
- email: emailMatch ? emailMatch[1] : void 0
10701
+ remote,
10702
+ rec: {
10703
+ token: tokenMatch[1],
10704
+ serviceUrl,
10705
+ userId: userIdRaw != null ? Number(userIdRaw) : void 0,
10706
+ email
10707
+ }
10675
10708
  };
10676
10709
  }
10677
- async function resolveToken(flagToken) {
10678
- if (flagToken) {
10679
- return { ok: true, data: { token: flagToken, source: "flag" } };
10710
+ function encodeRemoteKey(remote) {
10711
+ return remote.replace(
10712
+ /[%\x00-\x20\x7f]/g,
10713
+ (c) => "%" + c.charCodeAt(0).toString(16).padStart(2, "0").toUpperCase()
10714
+ );
10715
+ }
10716
+ function serializeCredential(remote, rec) {
10717
+ const key = encodeRemoteKey(remote);
10718
+ const out = [key ? `${key} token: ${rec.token}` : `token: ${rec.token}`];
10719
+ if (rec.serviceUrl) out.push(`service_url: ${rec.serviceUrl}`);
10720
+ if (rec.userId != null) out.push(`user_id: ${rec.userId}`);
10721
+ if (rec.email) out.push(`email: ${rec.email}`);
10722
+ return out.join(" ");
10723
+ }
10724
+ async function readGlobalCredential(remote) {
10725
+ let content;
10726
+ try {
10727
+ content = await (0, import_promises.readFile)(globalCredentialsPath(), "utf-8");
10728
+ } catch {
10729
+ return null;
10680
10730
  }
10681
- const envToken = process.env.VERITY_TOKEN;
10682
- if (envToken) {
10683
- return { ok: true, data: { token: envToken, source: "env" } };
10731
+ const lines = content.split("\n");
10732
+ const key = encodeRemoteKey(remote);
10733
+ if (key) {
10734
+ let last = null;
10735
+ for (const line of lines) {
10736
+ const parsed = parseCredentialLine(line);
10737
+ if (parsed && parsed.remote === key) last = parsed.rec;
10738
+ }
10739
+ if (last) return last;
10740
+ }
10741
+ let plain = null;
10742
+ for (const line of lines) {
10743
+ const parsed = parseCredentialLine(line);
10744
+ if (parsed && parsed.remote === "") plain = parsed.rec;
10684
10745
  }
10746
+ return plain;
10747
+ }
10748
+ async function upsertGlobalCredential(remote, rec) {
10749
+ const path = globalCredentialsPath();
10750
+ await (0, import_promises.mkdir)((0, import_node_path2.dirname)(path), { recursive: true });
10751
+ let content = "";
10685
10752
  try {
10686
- const content = await (0, import_promises.readFile)(projectPath(CREDENTIALS_FILE), "utf-8");
10687
- const match = content.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
10688
- if (match) {
10689
- return { ok: true, data: { token: match[1], source: "local", ...parseIdentity(content) } };
10690
- }
10753
+ content = await (0, import_promises.readFile)(path, "utf-8");
10691
10754
  } catch {
10692
10755
  }
10693
- const globalCredentials = `${process.env.HOME}/.verity/credentials`;
10694
- try {
10695
- const content = await (0, import_promises.readFile)(globalCredentials, "utf-8");
10696
- let remote = "";
10697
- try {
10698
- remote = (0, import_node_child_process2.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10699
- } catch {
10700
- }
10701
- if (remote) {
10702
- const remoteLine = content.split("\n").find((l) => l.includes(remote));
10703
- if (remoteLine) {
10704
- const match = remoteLine.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
10705
- if (match) {
10706
- return { ok: true, data: { token: match[1], source: "global" } };
10707
- }
10756
+ const line = serializeCredential(remote, rec);
10757
+ const key = encodeRemoteKey(remote);
10758
+ const kept = [];
10759
+ let replaced = false;
10760
+ for (const existing of content ? content.split("\n") : []) {
10761
+ const parsed = parseCredentialLine(existing);
10762
+ const matches = parsed !== null && parsed.remote === key;
10763
+ if (matches) {
10764
+ if (!replaced) {
10765
+ kept.push(line);
10766
+ replaced = true;
10708
10767
  }
10768
+ continue;
10709
10769
  }
10710
- const plainMatch = content.match(/^token:\s*((?:gate_|verity_)[a-f0-9]+)/m);
10711
- if (plainMatch) {
10712
- return { ok: true, data: { token: plainMatch[1], source: "global" } };
10713
- }
10714
- } catch {
10770
+ kept.push(existing);
10715
10771
  }
10716
- return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
10717
- }
10718
- async function whoami(token, serviceUrl, verbose) {
10719
- return apiRequest({
10720
- method: "GET",
10721
- path: "/auth/whoami",
10722
- serviceUrl,
10723
- token,
10724
- verbose
10772
+ while (kept.length && kept[kept.length - 1].trim() === "") kept.pop();
10773
+ if (!replaced) kept.push(line);
10774
+ const body = kept.join("\n") + "\n";
10775
+ await (0, import_promises.writeFile)(path, body, { mode: 384 });
10776
+ await (0, import_promises.chmod)(path, 384).catch(() => {
10725
10777
  });
10726
10778
  }
10727
-
10728
- // src/lib/service-url.ts
10729
- var import_promises2 = require("node:fs/promises");
10730
- async function resolveServiceUrl(flagUrl) {
10731
- if (flagUrl) {
10732
- return { ok: true, data: flagUrl };
10779
+ function parseLocalCredentialFile(content) {
10780
+ const tokenMatch = content.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
10781
+ if (!tokenMatch) return null;
10782
+ const userIdMatch = content.match(/^user_id:\s*(\d+)/m);
10783
+ return {
10784
+ token: tokenMatch[1],
10785
+ serviceUrl: content.match(/service_url:\s*(https?:\/\/\S+)/)?.[1],
10786
+ userId: userIdMatch ? Number(userIdMatch[1]) : void 0,
10787
+ email: content.match(/^email:\s*(\S+)/m)?.[1]
10788
+ };
10789
+ }
10790
+ async function readLegacyLocalCredential() {
10791
+ try {
10792
+ return parseLocalCredentialFile(await (0, import_promises.readFile)(projectPath(CREDENTIALS_FILE), "utf-8"));
10793
+ } catch {
10794
+ return null;
10733
10795
  }
10734
- const envUrl = process.env.VERITY_SERVICE_URL;
10735
- if (envUrl) {
10736
- return { ok: true, data: envUrl };
10796
+ }
10797
+ async function foldLegacyLocalCredential(remoteArg) {
10798
+ let content;
10799
+ try {
10800
+ content = await (0, import_promises.readFile)(projectPath(CREDENTIALS_FILE), "utf-8");
10801
+ } catch {
10802
+ return false;
10803
+ }
10804
+ const local = parseLocalCredentialFile(content);
10805
+ if (!local) {
10806
+ await (0, import_promises.unlink)(projectPath(CREDENTIALS_FILE)).catch(() => {
10807
+ });
10808
+ return false;
10809
+ }
10810
+ const remote = remoteArg ?? currentRemote();
10811
+ if (!remote) {
10812
+ return false;
10737
10813
  }
10814
+ const existing = await readGlobalCredential(remote);
10815
+ const merged = {
10816
+ token: existing?.token ?? local.token,
10817
+ serviceUrl: existing?.serviceUrl ?? local.serviceUrl,
10818
+ userId: existing?.userId ?? local.userId,
10819
+ email: existing?.email ?? local.email
10820
+ };
10738
10821
  try {
10739
- const creds = await (0, import_promises2.readFile)(projectPath(CREDENTIALS_FILE), "utf-8");
10740
- const match = creds.match(/service_url:\s*(https?:\/\/[^\s]+)/);
10741
- if (match) {
10742
- return { ok: true, data: match[1] };
10743
- }
10822
+ await upsertGlobalCredential(remote, merged);
10744
10823
  } catch {
10824
+ return false;
10745
10825
  }
10826
+ await (0, import_promises.unlink)(projectPath(CREDENTIALS_FILE)).catch(() => {
10827
+ });
10828
+ return true;
10829
+ }
10830
+
10831
+ // src/lib/service-url.ts
10832
+ async function serviceUrlFromCredentials() {
10833
+ const rec = await readGlobalCredential(currentRemote());
10834
+ return rec?.serviceUrl ?? null;
10835
+ }
10836
+ async function serviceUrlFromVerityMd() {
10746
10837
  try {
10747
10838
  const content = await (0, import_promises2.readFile)(projectPath(VERITY_MD_FILE), "utf-8");
10748
- const boldMatch = content.match(/\*\*url\*\*/i);
10749
- if (boldMatch) {
10750
- const lineMatch = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
10751
- if (lineMatch) {
10752
- const urlMatch = lineMatch.match(/https:\/\/[^\s]+/);
10753
- if (urlMatch) {
10754
- return { ok: true, data: urlMatch[0] };
10755
- }
10756
- }
10839
+ const boldLine = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
10840
+ if (boldLine) {
10841
+ const urlMatch = boldLine.match(/https:\/\/[^\s]+/);
10842
+ if (urlMatch) return urlMatch[0];
10757
10843
  }
10758
10844
  const plainLine = content.split("\n").find((l) => /(?:url|service)\s*:/i.test(l));
10759
10845
  if (plainLine) {
10760
10846
  const urlMatch = plainLine.match(/https:\/\/[^\s]+/);
10761
- if (urlMatch) {
10762
- return { ok: true, data: urlMatch[0] };
10763
- }
10847
+ if (urlMatch) return urlMatch[0];
10764
10848
  }
10765
10849
  } catch {
10766
10850
  }
10851
+ return null;
10852
+ }
10853
+ async function resolveServiceUrlDetailed(flagUrl) {
10854
+ if (flagUrl) {
10855
+ return { ok: true, data: { url: flagUrl, source: "flag" } };
10856
+ }
10857
+ const envUrl = process.env.VERITY_SERVICE_URL;
10858
+ if (envUrl) {
10859
+ return { ok: true, data: { url: envUrl, source: "env" } };
10860
+ }
10861
+ const credsUrl = await serviceUrlFromCredentials();
10862
+ if (credsUrl) {
10863
+ return { ok: true, data: { url: credsUrl, source: "credentials" } };
10864
+ }
10865
+ const mdUrl = await serviceUrlFromVerityMd();
10866
+ if (mdUrl) {
10867
+ return { ok: true, data: { url: mdUrl, source: "verity_md" } };
10868
+ }
10767
10869
  return { ok: false, error: "No Verity service URL found. Run /verity-setup to configure." };
10768
10870
  }
10871
+ async function resolveServiceUrl(flagUrl) {
10872
+ const result = await resolveServiceUrlDetailed(flagUrl);
10873
+ return result.ok ? { ok: true, data: result.data.url } : result;
10874
+ }
10875
+ function isHealCandidate(resolved) {
10876
+ return (resolved.source === "credentials" || resolved.source === "verity_md") && resolved.url !== DEFAULT_SERVICE_URL;
10877
+ }
10878
+
10879
+ // src/lib/auth.ts
10880
+ async function resolveToken(flagToken) {
10881
+ if (flagToken) {
10882
+ return { ok: true, data: { token: flagToken, source: "flag" } };
10883
+ }
10884
+ const envToken = process.env.VERITY_TOKEN;
10885
+ if (envToken) {
10886
+ return { ok: true, data: { token: envToken, source: "env" } };
10887
+ }
10888
+ const rec = await readGlobalCredential(currentRemote());
10889
+ if (rec) {
10890
+ return {
10891
+ ok: true,
10892
+ data: { token: rec.token, source: "global", userId: rec.userId, email: rec.email }
10893
+ };
10894
+ }
10895
+ const local = await readLegacyLocalCredential();
10896
+ if (local) {
10897
+ return {
10898
+ ok: true,
10899
+ data: { token: local.token, source: "local", userId: local.userId, email: local.email }
10900
+ };
10901
+ }
10902
+ return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
10903
+ }
10904
+ async function whoami(token, serviceUrl, verbose) {
10905
+ return apiRequest({
10906
+ method: "GET",
10907
+ path: "/auth/whoami",
10908
+ serviceUrl,
10909
+ token,
10910
+ verbose,
10911
+ cmd: "whoami"
10912
+ });
10913
+ }
10914
+ async function probeService(serviceUrl, verbose) {
10915
+ const res = await apiRequest({
10916
+ method: "GET",
10917
+ path: "/auth/whoami",
10918
+ serviceUrl,
10919
+ verbose,
10920
+ timeout: 5e3,
10921
+ cmd: "probe"
10922
+ });
10923
+ if (res.ok || res.http_status != null) return { reachable: true };
10924
+ return { reachable: false, dnsDead: /\bENOTFOUND\b/.test(res.error), error: res.error };
10925
+ }
10926
+ async function maybeHealServiceUrl(resolution, verbose) {
10927
+ if (!isHealCandidate(resolution)) {
10928
+ return { serviceUrl: resolution.url, healed: false };
10929
+ }
10930
+ const probe = await probeService(resolution.url, verbose);
10931
+ if (probe.reachable) {
10932
+ return { serviceUrl: resolution.url, healed: false };
10933
+ }
10934
+ const from = resolution.source === "credentials" ? "~/.verity/credentials" : "VERITY.md";
10935
+ printWarn(`Your configured Verity service URL is unreachable: ${resolution.url}`);
10936
+ printInfo(` (${probe.error})`);
10937
+ if (probe.dnsDead && (await probeService(DEFAULT_SERVICE_URL, verbose)).reachable) {
10938
+ printInfo(` The hostname no longer exists \u2014 the URL in ${from} is stale (e.g. a retired preview backend).`);
10939
+ printInfo(` Falling back to the default Verity service: ${DEFAULT_SERVICE_URL}`);
10940
+ if (resolution.source === "verity_md") {
10941
+ printWarn(` Note: VERITY.md still contains the stale URL \u2014 update it to ${DEFAULT_SERVICE_URL} and commit.`);
10942
+ }
10943
+ return { serviceUrl: DEFAULT_SERVICE_URL, healed: true };
10944
+ }
10945
+ printInfo(" Continuing against the configured URL. If it is stale, log in against the default with:");
10946
+ printInfo(` VERITY_SERVICE_URL=${DEFAULT_SERVICE_URL} verity login`);
10947
+ return { serviceUrl: resolution.url, healed: false };
10948
+ }
10769
10949
 
10770
10950
  // src/lib/register.ts
10771
- var import_promises3 = require("node:fs/promises");
10772
- var import_node_path3 = require("node:path");
10773
10951
  var readline = __toESM(require("node:readline/promises"));
10774
10952
 
10775
10953
  // src/lib/provider-auth.ts
@@ -10879,7 +11057,7 @@ async function githubDeviceFlow() {
10879
11057
  // src/lib/git.ts
10880
11058
  var import_node_child_process3 = require("node:child_process");
10881
11059
  var import_node_fs2 = require("node:fs");
10882
- var import_node_path2 = require("node:path");
11060
+ var import_node_path3 = require("node:path");
10883
11061
  function resolveFile(relpath) {
10884
11062
  if ((0, import_node_fs2.existsSync)(relpath)) return relpath;
10885
11063
  if ((0, import_node_fs2.existsSync)(".claude/worktrees")) {
@@ -10887,7 +11065,7 @@ function resolveFile(relpath) {
10887
11065
  const entries = (0, import_node_fs2.readdirSync)(".claude/worktrees", { withFileTypes: true });
10888
11066
  for (const entry of entries) {
10889
11067
  if (!entry.isDirectory()) continue;
10890
- const candidate = (0, import_node_path2.join)(".claude/worktrees", entry.name, relpath);
11068
+ const candidate = (0, import_node_path3.join)(".claude/worktrees", entry.name, relpath);
10891
11069
  if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
10892
11070
  }
10893
11071
  } catch {
@@ -10928,7 +11106,7 @@ function readBaselineSha() {
10928
11106
  function writeBaselineSha(sha) {
10929
11107
  if (!SHA_RE.test(sha)) return;
10930
11108
  try {
10931
- (0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(BASELINE_SHA_FILE), { recursive: true });
11109
+ (0, import_node_fs2.mkdirSync)((0, import_node_path3.dirname)(BASELINE_SHA_FILE), { recursive: true });
10932
11110
  (0, import_node_fs2.writeFileSync)(BASELINE_SHA_FILE, sha);
10933
11111
  } catch {
10934
11112
  }
@@ -11022,7 +11200,7 @@ function getWorktreeFiles() {
11022
11200
  const entries = (0, import_node_fs2.readdirSync)(worktreeDir, { withFileTypes: true });
11023
11201
  for (const entry of entries) {
11024
11202
  if (!entry.isDirectory()) continue;
11025
- const wtDir = (0, import_node_path2.join)(worktreeDir, entry.name);
11203
+ const wtDir = (0, import_node_path3.join)(worktreeDir, entry.name);
11026
11204
  scanDir(wtDir, wtDir, fiveMinAgo, result);
11027
11205
  }
11028
11206
  } catch {
@@ -11033,12 +11211,12 @@ function scanDir(baseDir, dir, minMtime, result) {
11033
11211
  try {
11034
11212
  const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
11035
11213
  for (const entry of entries) {
11036
- const fullPath = (0, import_node_path2.join)(dir, entry.name);
11214
+ const fullPath = (0, import_node_path3.join)(dir, entry.name);
11037
11215
  if (entry.isDirectory()) {
11038
11216
  if (entry.name === "node_modules" || entry.name === ".git") continue;
11039
11217
  scanDir(baseDir, fullPath, minMtime, result);
11040
11218
  } else if (entry.isFile()) {
11041
- const ext = (0, import_node_path2.extname)(entry.name).slice(1);
11219
+ const ext = (0, import_node_path3.extname)(entry.name).slice(1);
11042
11220
  if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
11043
11221
  try {
11044
11222
  const stat3 = (0, import_node_fs2.statSync)(fullPath);
@@ -11055,13 +11233,13 @@ function scanDir(baseDir, dir, minMtime, result) {
11055
11233
  }
11056
11234
  function filterAnalyzable(files) {
11057
11235
  return files.filter((f) => {
11058
- const ext = (0, import_node_path2.extname)(f).slice(1);
11236
+ const ext = (0, import_node_path3.extname)(f).slice(1);
11059
11237
  return ANALYZABLE_EXTENSIONS.has(ext);
11060
11238
  });
11061
11239
  }
11062
11240
  function filterReviewable(files) {
11063
11241
  return files.filter((f) => {
11064
- const ext = (0, import_node_path2.extname)(f).slice(1);
11242
+ const ext = (0, import_node_path3.extname)(f).slice(1);
11065
11243
  if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
11066
11244
  if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
11067
11245
  const basename4 = f.split("/").pop() ?? "";
@@ -11185,7 +11363,8 @@ async function registerProject(opts) {
11185
11363
  serviceUrl: opts.serviceUrl,
11186
11364
  body: { project_name: opts.projectName, git_remote_url: opts.remote },
11187
11365
  extraHeaders: { "X-Provider-Token": providerToken },
11188
- verbose: opts.verbose
11366
+ verbose: opts.verbose,
11367
+ cmd: "register"
11189
11368
  });
11190
11369
  if (!result.ok) {
11191
11370
  return { ok: false, error: result.error };
@@ -11193,38 +11372,19 @@ async function registerProject(opts) {
11193
11372
  const { project_id, token, service_url, user } = result.data;
11194
11373
  const userId = result.data.user_id ?? user?.id;
11195
11374
  const email = user?.email;
11196
- const identityLines = (userId != null ? `user_id: ${userId}
11197
- ` : "") + (email ? `email: ${email}
11198
- ` : "");
11199
11375
  try {
11200
- await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
11201
- await (0, import_promises3.writeFile)(
11202
- CREDENTIALS_FILE,
11203
- `token: ${token}
11204
- service_url: ${service_url}
11205
- ${identityLines}`,
11206
- { mode: 384 }
11207
- );
11208
- await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
11376
+ await upsertGlobalCredential(opts.remote, {
11377
+ token,
11378
+ serviceUrl: service_url,
11379
+ userId: userId ?? void 0,
11380
+ email
11209
11381
  });
11210
11382
  } catch (err) {
11211
11383
  return {
11212
11384
  ok: false,
11213
- error: `Registered with the Verity service, but could not save credentials to ${CREDENTIALS_FILE}: ${err.message}. Check filesystem permissions and re-run "verity auth register".`
11385
+ error: `Registered with the Verity service, but could not save credentials to ~/.verity/credentials: ${err.message}. Check filesystem permissions and re-run "verity auth register".`
11214
11386
  };
11215
11387
  }
11216
- try {
11217
- await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
11218
- await (0, import_promises3.appendFile)(
11219
- GLOBAL_CREDENTIALS_FILE,
11220
- `${opts.remote} token: ${token}
11221
- `,
11222
- { mode: 384 }
11223
- );
11224
- await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
11225
- });
11226
- } catch {
11227
- }
11228
11388
  return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email, userId } };
11229
11389
  }
11230
11390
 
@@ -11321,14 +11481,18 @@ var import_node_path4 = require("node:path");
11321
11481
  function registerLoginCommand(program2) {
11322
11482
  program2.command("login").description("Log in to Verity (link your GitHub identity so runs and memory are saved)").option("--force", "Re-authenticate even if already logged in").action(async (opts) => {
11323
11483
  const globals = program2.opts();
11324
- const urlResult = await resolveServiceUrl(globals.serviceUrl);
11484
+ const urlResult = await resolveServiceUrlDetailed(globals.serviceUrl);
11325
11485
  if (!urlResult.ok) {
11326
11486
  printError(urlResult.error);
11327
11487
  process.exit(1);
11328
11488
  }
11329
- const serviceUrl = urlResult.data;
11489
+ const heal = await maybeHealServiceUrl(urlResult.data, globals.verbose);
11490
+ const serviceUrl = heal.serviceUrl;
11491
+ if (heal.healed) {
11492
+ printInfo(" Completing login re-registers this project and updates ~/.verity/credentials.");
11493
+ }
11330
11494
  const existing = await resolveToken(globals.token);
11331
- if (existing.ok && !opts.force) {
11495
+ if (existing.ok && !opts.force && !heal.healed) {
11332
11496
  if (existing.data.userId != null) {
11333
11497
  printInfo(`Already logged in as ${existing.data.email ?? `user #${existing.data.userId}`}. \u2713`);
11334
11498
  printInfo(" Re-authenticate with: verity login --force");
@@ -11373,11 +11537,11 @@ function registerLoginCommand(program2) {
11373
11537
  }
11374
11538
 
11375
11539
  // src/lib/hooks.ts
11376
- var import_promises5 = require("node:fs/promises");
11540
+ var import_promises4 = require("node:fs/promises");
11377
11541
  var import_node_path6 = require("node:path");
11378
11542
 
11379
11543
  // src/lib/json-file.ts
11380
- var import_promises4 = require("node:fs/promises");
11544
+ var import_promises3 = require("node:fs/promises");
11381
11545
  var import_node_path5 = require("node:path");
11382
11546
  function jsonSemanticEqual(a, b) {
11383
11547
  if (a === b) return true;
@@ -11411,7 +11575,7 @@ function detectJsonIndent(raw) {
11411
11575
  async function writeJsonFilePreservingStyle(file, value) {
11412
11576
  let currentRaw = null;
11413
11577
  try {
11414
- currentRaw = await (0, import_promises4.readFile)(file, "utf-8");
11578
+ currentRaw = await (0, import_promises3.readFile)(file, "utf-8");
11415
11579
  } catch {
11416
11580
  currentRaw = null;
11417
11581
  }
@@ -11424,8 +11588,8 @@ async function writeJsonFilePreservingStyle(file, value) {
11424
11588
  const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
11425
11589
  const next = JSON.stringify(value, null, indent) + "\n";
11426
11590
  if (next === currentRaw) return false;
11427
- await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(file), { recursive: true });
11428
- await (0, import_promises4.writeFile)(file, next);
11591
+ await (0, import_promises3.mkdir)((0, import_node_path5.dirname)(file), { recursive: true });
11592
+ await (0, import_promises3.writeFile)(file, next);
11429
11593
  return true;
11430
11594
  }
11431
11595
 
@@ -11507,7 +11671,7 @@ function globalSettingsFile() {
11507
11671
  }
11508
11672
  async function readSettings() {
11509
11673
  try {
11510
- const content = await (0, import_promises5.readFile)(CLAUDE_SETTINGS_FILE, "utf-8");
11674
+ const content = await (0, import_promises4.readFile)(CLAUDE_SETTINGS_FILE, "utf-8");
11511
11675
  return JSON.parse(content);
11512
11676
  } catch {
11513
11677
  return {};
@@ -11518,7 +11682,7 @@ async function readAllSettings() {
11518
11682
  const out = [];
11519
11683
  for (const f of files) {
11520
11684
  try {
11521
- out.push(JSON.parse(await (0, import_promises5.readFile)(f, "utf-8")));
11685
+ out.push(JSON.parse(await (0, import_promises4.readFile)(f, "utf-8")));
11522
11686
  } catch {
11523
11687
  }
11524
11688
  }
@@ -11547,7 +11711,7 @@ async function checkExternalVerityHooks() {
11547
11711
  for (const f of [SETTINGS_LOCAL_FILE, globalSettingsFile()]) {
11548
11712
  let settings;
11549
11713
  try {
11550
- settings = JSON.parse(await (0, import_promises5.readFile)(f, "utf-8"));
11714
+ settings = JSON.parse(await (0, import_promises4.readFile)(f, "utf-8"));
11551
11715
  } catch {
11552
11716
  continue;
11553
11717
  }
@@ -11585,7 +11749,7 @@ async function writeSettings(settings) {
11585
11749
  }
11586
11750
  async function readSettingsAt(root) {
11587
11751
  try {
11588
- return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path6.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11752
+ return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path6.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11589
11753
  } catch {
11590
11754
  return {};
11591
11755
  }
@@ -11827,10 +11991,10 @@ function registerHooksCommands(program2) {
11827
11991
  }
11828
11992
 
11829
11993
  // src/commands/intent.ts
11830
- var import_node_crypto3 = require("node:crypto");
11994
+ var import_node_crypto4 = require("node:crypto");
11831
11995
 
11832
11996
  // src/lib/conversation-buffer.ts
11833
- var import_promises6 = require("node:fs/promises");
11997
+ var import_promises5 = require("node:fs/promises");
11834
11998
  var import_node_fs3 = require("node:fs");
11835
11999
  var import_node_child_process6 = require("node:child_process");
11836
12000
  var import_node_crypto = require("node:crypto");
@@ -11842,7 +12006,7 @@ function bufferTmpPath() {
11842
12006
  }
11843
12007
  async function appendToConversationBuffer(prompt, sessionId) {
11844
12008
  try {
11845
- await (0, import_promises6.mkdir)(VERITY_DIR, { recursive: true });
12009
+ await (0, import_promises5.mkdir)(VERITY_DIR, { recursive: true });
11846
12010
  let sanitized = prompt.length > MAX_INTENT_CHARS ? prompt.slice(0, MAX_INTENT_CHARS) : prompt;
11847
12011
  sanitized = stripImageReferences(sanitized);
11848
12012
  const entry = {
@@ -11860,8 +12024,8 @@ async function appendToConversationBuffer(prompt, sessionId) {
11860
12024
  const capped = recent.slice(-CONVERSATION_MAX_ENTRIES);
11861
12025
  const content = capped.map((e) => JSON.stringify(e)).join("\n") + "\n";
11862
12026
  const tmpFile = bufferTmpPath();
11863
- await (0, import_promises6.writeFile)(tmpFile, content);
11864
- await (0, import_promises6.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
12027
+ await (0, import_promises5.writeFile)(tmpFile, content);
12028
+ await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11865
12029
  } catch {
11866
12030
  }
11867
12031
  }
@@ -11878,10 +12042,10 @@ async function readAndClearConversationBuffer(currentSessionId) {
11878
12042
  if (others.length > 0) {
11879
12043
  const remaining = others.map((e) => JSON.stringify(e)).join("\n") + "\n";
11880
12044
  const tmpFile = bufferTmpPath();
11881
- await (0, import_promises6.writeFile)(tmpFile, remaining);
11882
- await (0, import_promises6.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
12045
+ await (0, import_promises5.writeFile)(tmpFile, remaining);
12046
+ await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11883
12047
  } else {
11884
- await (0, import_promises6.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
12048
+ await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11885
12049
  });
11886
12050
  }
11887
12051
  if (mine.length > 0) {
@@ -11893,8 +12057,8 @@ async function readAndClearConversationBuffer(currentSessionId) {
11893
12057
  }
11894
12058
  if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
11895
12059
  try {
11896
- const content = await (0, import_promises6.readFile)(INTENT_FILE, "utf-8");
11897
- await (0, import_promises6.unlink)(INTENT_FILE).catch(() => {
12060
+ const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
12061
+ await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
11898
12062
  });
11899
12063
  const data = JSON.parse(content);
11900
12064
  if (data.prompt) {
@@ -11917,7 +12081,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
11917
12081
  }
11918
12082
  async function readBufferEntries() {
11919
12083
  try {
11920
- const content = await (0, import_promises6.readFile)(CONVERSATION_BUFFER_FILE, "utf-8");
12084
+ const content = await (0, import_promises5.readFile)(CONVERSATION_BUFFER_FILE, "utf-8");
11921
12085
  const entries = [];
11922
12086
  for (const line of content.split("\n")) {
11923
12087
  const trimmed = line.trim();
@@ -11946,8 +12110,21 @@ function getRecentCommitMessages() {
11946
12110
  }
11947
12111
  }
11948
12112
 
12113
+ // src/lib/context-identity.ts
12114
+ var import_node_crypto2 = require("node:crypto");
12115
+ function contextIdentity(token, sessionId) {
12116
+ const t = (token ?? "").trim();
12117
+ const s = (sessionId ?? "").trim();
12118
+ const userKey = t.length > 0 ? (0, import_node_crypto2.createHash)("sha256").update(t).digest("hex").slice(0, 12) : "anon";
12119
+ const sessionKey2 = s.length > 0 ? (0, import_node_crypto2.createHash)("sha256").update(s).digest("hex").slice(0, 16) : "_default";
12120
+ return { userKey, sessionKey: sessionKey2, bucket: `${userKey}/${sessionKey2}` };
12121
+ }
12122
+ function sessionScopeKey(token, sessionId) {
12123
+ return contextIdentity(token, sessionId).bucket;
12124
+ }
12125
+
11949
12126
  // src/lib/task-context-buffer.ts
11950
- var import_promises7 = require("node:fs/promises");
12127
+ var import_promises6 = require("node:fs/promises");
11951
12128
  var import_node_fs4 = require("node:fs");
11952
12129
  var import_node_path7 = require("node:path");
11953
12130
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
@@ -11990,7 +12167,7 @@ async function readTaskContextBuffer(taskId) {
11990
12167
  const filePath = bufferPath(taskId);
11991
12168
  if (!(0, import_node_fs4.existsSync)(filePath)) return null;
11992
12169
  try {
11993
- const content = await (0, import_promises7.readFile)(filePath, "utf-8");
12170
+ const content = await (0, import_promises6.readFile)(filePath, "utf-8");
11994
12171
  if (!content.trim()) return null;
11995
12172
  const lines = content.split("\n").filter((l) => l.trim());
11996
12173
  const formatted = [];
@@ -12023,15 +12200,15 @@ async function readTaskContextBuffer(taskId) {
12023
12200
  async function cleanupTaskContextBuffers() {
12024
12201
  try {
12025
12202
  if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
12026
- const files = await (0, import_promises7.readdir)(TASK_CONTEXT_DIR);
12203
+ const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
12027
12204
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
12028
12205
  for (const file of files) {
12029
12206
  if (!file.endsWith(".jsonl")) continue;
12030
12207
  const filePath = (0, import_node_path7.join)(TASK_CONTEXT_DIR, file);
12031
12208
  try {
12032
- const stats = await (0, import_promises7.stat)(filePath);
12209
+ const stats = await (0, import_promises6.stat)(filePath);
12033
12210
  if (stats.mtimeMs < cutoffMs) {
12034
- await (0, import_promises7.unlink)(filePath);
12211
+ await (0, import_promises6.unlink)(filePath);
12035
12212
  }
12036
12213
  } catch {
12037
12214
  }
@@ -12045,27 +12222,27 @@ function bufferPath(taskId) {
12045
12222
  }
12046
12223
  async function appendEntry(taskId, entry) {
12047
12224
  try {
12048
- await (0, import_promises7.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
12225
+ await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
12049
12226
  const filePath = bufferPath(taskId);
12050
12227
  if ((0, import_node_fs4.existsSync)(filePath)) {
12051
- const stats = await (0, import_promises7.stat)(filePath);
12228
+ const stats = await (0, import_promises6.stat)(filePath);
12052
12229
  if (stats.size >= MAX_BUFFER_BYTES) {
12053
- const content = await (0, import_promises7.readFile)(filePath, "utf-8");
12230
+ const content = await (0, import_promises6.readFile)(filePath, "utf-8");
12054
12231
  const lines = content.split("\n").filter((l) => l.trim());
12055
12232
  const keepFrom = Math.floor(lines.length * 0.25);
12056
12233
  const pruned = lines.slice(keepFrom).join("\n") + "\n";
12057
- await (0, import_promises7.writeFile)(filePath, pruned);
12234
+ await (0, import_promises6.writeFile)(filePath, pruned);
12058
12235
  }
12059
12236
  }
12060
12237
  const line = JSON.stringify(entry) + "\n";
12061
- const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
12062
- await (0, import_promises7.writeFile)(filePath, existing + line);
12238
+ const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
12239
+ await (0, import_promises6.writeFile)(filePath, existing + line);
12063
12240
  } catch {
12064
12241
  }
12065
12242
  }
12066
12243
 
12067
12244
  // src/lib/memory-retrieval.ts
12068
- var import_promises8 = require("node:fs/promises");
12245
+ var import_promises7 = require("node:fs/promises");
12069
12246
  var import_node_fs5 = require("node:fs");
12070
12247
  var import_node_path8 = require("node:path");
12071
12248
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
@@ -12168,11 +12345,11 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
12168
12345
  const domainDir = (0, import_node_path8.join)(memoryDir(), domain);
12169
12346
  if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12170
12347
  try {
12171
- const files = await (0, import_promises8.readdir)(domainDir);
12348
+ const files = await (0, import_promises7.readdir)(domainDir);
12172
12349
  for (const file of files) {
12173
12350
  if (!file.endsWith(".md")) continue;
12174
12351
  try {
12175
- const content = await (0, import_promises8.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
12352
+ const content = await (0, import_promises7.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
12176
12353
  const { fm, body } = parseFrontmatter(content);
12177
12354
  if (fm.status && fm.status !== "active") continue;
12178
12355
  nodes.push({
@@ -12230,10 +12407,10 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
12230
12407
  }
12231
12408
 
12232
12409
  // src/lib/memory-sync.ts
12233
- var import_promises9 = require("node:fs/promises");
12410
+ var import_promises8 = require("node:fs/promises");
12234
12411
  var import_node_fs6 = require("node:fs");
12235
12412
  var import_node_path9 = require("node:path");
12236
- var import_node_crypto2 = require("node:crypto");
12413
+ var import_node_crypto3 = require("node:crypto");
12237
12414
 
12238
12415
  // src/lib/glob-match.ts
12239
12416
  function globToRegex(glob) {
@@ -12301,18 +12478,18 @@ var memoryDir2 = () => projectPath(`${VERITY_DIR}/memory`);
12301
12478
  var DOMAINS2 = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations", "_archive"];
12302
12479
  var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
12303
12480
  async function ensureMemoryDir() {
12304
- await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
12481
+ await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
12305
12482
  for (const domain of DOMAINS2) {
12306
- await (0, import_promises9.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
12483
+ await (0, import_promises8.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
12307
12484
  }
12308
12485
  if (!(0, import_node_fs6.existsSync)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"))) {
12309
- await (0, import_promises9.writeFile)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12486
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12310
12487
  }
12311
12488
  if (!(0, import_node_fs6.existsSync)((0, import_node_path9.join)(memoryDir2(), "index.md"))) {
12312
- await (0, import_promises9.writeFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
12489
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
12313
12490
  }
12314
12491
  if (!(0, import_node_fs6.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md"))) {
12315
- await (0, import_promises9.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12492
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12316
12493
  }
12317
12494
  }
12318
12495
  async function buildManifest() {
@@ -12324,14 +12501,14 @@ async function buildManifest() {
12324
12501
  const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12325
12502
  if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
12326
12503
  try {
12327
- const files = await (0, import_promises9.readdir)(domainDir);
12504
+ const files = await (0, import_promises8.readdir)(domainDir);
12328
12505
  for (const file of files) {
12329
12506
  if (!file.endsWith(".md")) continue;
12330
12507
  const filePath = `${domain}/${file}`;
12331
12508
  const fullPath = (0, import_node_path9.join)(memoryDir2(), filePath);
12332
12509
  try {
12333
- const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
12334
- const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
12510
+ const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
12511
+ const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
12335
12512
  nodes.push({ path: filePath, content_hash: `sha256:${hash}` });
12336
12513
  } catch {
12337
12514
  }
@@ -12341,20 +12518,20 @@ async function buildManifest() {
12341
12518
  }
12342
12519
  let indexHash = null;
12343
12520
  try {
12344
- const indexContent = await (0, import_promises9.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
12345
- indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
12521
+ const indexContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
12522
+ indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
12346
12523
  } catch {
12347
12524
  }
12348
12525
  let logLength = 0;
12349
12526
  try {
12350
- const logContent = await (0, import_promises9.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
12527
+ const logContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
12351
12528
  logLength = logContent.split("\n").length;
12352
12529
  } catch {
12353
12530
  }
12354
12531
  return { schema_version: 1, nodes, index_hash: indexHash, log_length: logLength };
12355
12532
  }
12356
12533
  function hashContent(content) {
12357
- return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16)}`;
12534
+ return `sha256:${(0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16)}`;
12358
12535
  }
12359
12536
  async function readOnDiskNodes() {
12360
12537
  const out = /* @__PURE__ */ new Map();
@@ -12363,10 +12540,10 @@ async function readOnDiskNodes() {
12363
12540
  const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12364
12541
  if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
12365
12542
  try {
12366
- for (const file of await (0, import_promises9.readdir)(domainDir)) {
12543
+ for (const file of await (0, import_promises8.readdir)(domainDir)) {
12367
12544
  if (!file.endsWith(".md")) continue;
12368
12545
  try {
12369
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
12546
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
12370
12547
  } catch {
12371
12548
  }
12372
12549
  }
@@ -12378,7 +12555,7 @@ async function readOnDiskNodes() {
12378
12555
  async function readSyncBaseline() {
12379
12556
  const out = /* @__PURE__ */ new Map();
12380
12557
  try {
12381
- const parsed = JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
12558
+ const parsed = JSON.parse(await (0, import_promises8.readFile)(syncStateFile(), "utf-8"));
12382
12559
  if (Array.isArray(parsed?.nodes)) {
12383
12560
  for (const n of parsed.nodes) if (n?.path) out.set(n.path, n.hash ?? null);
12384
12561
  } else if (Array.isArray(parsed?.paths)) {
@@ -12394,12 +12571,12 @@ async function recordSyncedNodePaths() {
12394
12571
  const next = JSON.stringify({ schema: 2, nodes }) + "\n";
12395
12572
  let existing = "";
12396
12573
  try {
12397
- existing = await (0, import_promises9.readFile)(syncStateFile(), "utf-8");
12574
+ existing = await (0, import_promises8.readFile)(syncStateFile(), "utf-8");
12398
12575
  } catch {
12399
12576
  }
12400
12577
  if (existing === next) return;
12401
- await (0, import_promises9.mkdir)(projectPath(VERITY_DIR), { recursive: true });
12402
- await (0, import_promises9.writeFile)(syncStateFile(), next);
12578
+ await (0, import_promises8.mkdir)(projectPath(VERITY_DIR), { recursive: true });
12579
+ await (0, import_promises8.writeFile)(syncStateFile(), next);
12403
12580
  } catch {
12404
12581
  }
12405
12582
  }
@@ -12416,7 +12593,7 @@ async function computeEditedNodeUploads() {
12416
12593
  if (!(0, import_node_fs6.existsSync)(full)) continue;
12417
12594
  let content;
12418
12595
  try {
12419
- content = await (0, import_promises9.readFile)(full, "utf-8");
12596
+ content = await (0, import_promises8.readFile)(full, "utf-8");
12420
12597
  } catch {
12421
12598
  continue;
12422
12599
  }
@@ -12449,8 +12626,8 @@ async function applyMemoryWrites(writes, opts = {}) {
12449
12626
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
12450
12627
  for (const n of notes) logLines.push(` - ${n}`);
12451
12628
  try {
12452
- const existing = (0, import_node_fs6.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
12453
- await (0, import_promises9.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
12629
+ const existing = (0, import_node_fs6.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
12630
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
12454
12631
  } catch {
12455
12632
  }
12456
12633
  await recordSyncedNodePaths();
@@ -12470,7 +12647,7 @@ async function applyOneWrite(write, treePaths) {
12470
12647
  if ((0, import_node_fs6.existsSync)(fullPath)) {
12471
12648
  let existing = "";
12472
12649
  try {
12473
- existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
12650
+ existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
12474
12651
  } catch {
12475
12652
  }
12476
12653
  if (existing === content) return { written: false, notes };
@@ -12479,8 +12656,8 @@ async function applyOneWrite(write, treePaths) {
12479
12656
  return { written: false, notes };
12480
12657
  }
12481
12658
  }
12482
- await (0, import_promises9.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
12483
- await (0, import_promises9.writeFile)(fullPath, content);
12659
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
12660
+ await (0, import_promises8.writeFile)(fullPath, content);
12484
12661
  return { written: true, notes };
12485
12662
  }
12486
12663
  function groundFileGlobs(content, treePaths) {
@@ -12523,7 +12700,7 @@ async function regenerateIndex() {
12523
12700
  const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12524
12701
  if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
12525
12702
  try {
12526
- const files = await (0, import_promises9.readdir)(domainDir);
12703
+ const files = await (0, import_promises8.readdir)(domainDir);
12527
12704
  const mdFiles = files.filter((f) => f.endsWith(".md"));
12528
12705
  if (mdFiles.length === 0) continue;
12529
12706
  lines.push(`## ${domain}/ (${mdFiles.length})`);
@@ -12531,7 +12708,7 @@ async function regenerateIndex() {
12531
12708
  for (const file of mdFiles.sort()) {
12532
12709
  const slug = file.replace(/\.md$/, "");
12533
12710
  try {
12534
- const content = await (0, import_promises9.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
12711
+ const content = await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
12535
12712
  const title = pickFrontmatter(content, "title") ?? slug;
12536
12713
  const kind = pickFrontmatter(content, "kind") ?? "-";
12537
12714
  const confidence = pickFrontmatter(content, "confidence");
@@ -12558,11 +12735,11 @@ async function regenerateIndex() {
12558
12735
  const indexPath = (0, import_node_path9.join)(memoryDir2(), "index.md");
12559
12736
  let existing = null;
12560
12737
  try {
12561
- existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
12738
+ existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
12562
12739
  } catch {
12563
12740
  }
12564
12741
  if (existing === next) return;
12565
- await (0, import_promises9.writeFile)(indexPath, next);
12742
+ await (0, import_promises8.writeFile)(indexPath, next);
12566
12743
  }
12567
12744
  function pickFrontmatter(content, key) {
12568
12745
  const re = new RegExp(`^${key}:\\s*"?([^"\\n]+?)"?\\s*$`, "m");
@@ -12640,7 +12817,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
12640
12817
  const claudeMdPath = (0, import_node_path9.join)(cwd, "CLAUDE.md");
12641
12818
  let existing = "";
12642
12819
  if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
12643
- existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
12820
+ existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
12644
12821
  }
12645
12822
  let startTag = CLAUDE_MD_START;
12646
12823
  let endTag = CLAUDE_MD_END;
@@ -12696,7 +12873,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
12696
12873
  next = existing.replace(/\n*$/, "") + "\n\n" + block + "\n";
12697
12874
  }
12698
12875
  if (next === existing) return;
12699
- await (0, import_promises9.writeFile)(claudeMdPath, next);
12876
+ await (0, import_promises8.writeFile)(claudeMdPath, next);
12700
12877
  }
12701
12878
  function extractPreserveContent(interior) {
12702
12879
  for (const [start, end] of [
@@ -12799,7 +12976,10 @@ function registerIntentCommands(program2) {
12799
12976
  if (!prompt) {
12800
12977
  process.exit(0);
12801
12978
  }
12802
- await appendToConversationBuffer(prompt, event.session_id ?? "");
12979
+ const authForScope = await resolveToken(program2.opts().token);
12980
+ const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
12981
+ const scopeSession = event.session_id || process.env.CLAUDE_SESSION_ID || "";
12982
+ await appendToConversationBuffer(prompt, sessionScopeKey(scopeToken, scopeSession));
12803
12983
  try {
12804
12984
  await ensureMemoryDir();
12805
12985
  const injection = await retrieveForInjection(prompt);
@@ -12841,7 +13021,7 @@ async function fireClassify(prompt, sessionId) {
12841
13021
  logEvent("classify_skipped", { reason: "no_service_url", detail: urlResult.error });
12842
13022
  return;
12843
13023
  }
12844
- const promptHash = (0, import_node_crypto3.createHash)("sha256").update(prompt).digest("hex");
13024
+ const promptHash = (0, import_node_crypto4.createHash)("sha256").update(prompt).digest("hex");
12845
13025
  const result = await apiRequest({
12846
13026
  method: "POST",
12847
13027
  path: "/classify-task",
@@ -12879,7 +13059,7 @@ async function fireClassify(prompt, sessionId) {
12879
13059
  }
12880
13060
 
12881
13061
  // src/commands/standard.ts
12882
- var import_promises10 = require("node:fs/promises");
13062
+ var import_promises9 = require("node:fs/promises");
12883
13063
  var import_yaml = __toESM(require_dist());
12884
13064
  function registerStandardCommands(program2) {
12885
13065
  const standard = program2.command("standard").description("Manage the project Standard");
@@ -12897,7 +13077,7 @@ function registerStandardCommands(program2) {
12897
13077
  }
12898
13078
  let yamlContent;
12899
13079
  try {
12900
- yamlContent = await (0, import_promises10.readFile)(opts.file, "utf-8");
13080
+ yamlContent = await (0, import_promises9.readFile)(opts.file, "utf-8");
12901
13081
  } catch {
12902
13082
  printError(`Cannot read ${opts.file}`);
12903
13083
  process.exit(1);
@@ -12988,7 +13168,7 @@ function registerStandardCommands(program2) {
12988
13168
  }
12989
13169
 
12990
13170
  // src/commands/config.ts
12991
- var import_promises11 = require("node:fs/promises");
13171
+ var import_promises10 = require("node:fs/promises");
12992
13172
  function registerConfigCommands(program2) {
12993
13173
  const config = program2.command("config").description("Manage analysis configuration");
12994
13174
  config.command("push").description("Upload the analysis config to the service").option("--file <path>", "Path to config file", CODACY_CONFIG_FILE).action(async (opts) => {
@@ -13005,7 +13185,7 @@ function registerConfigCommands(program2) {
13005
13185
  }
13006
13186
  let content;
13007
13187
  try {
13008
- const raw = await (0, import_promises11.readFile)(opts.file, "utf-8");
13188
+ const raw = await (0, import_promises10.readFile)(opts.file, "utf-8");
13009
13189
  content = JSON.parse(raw);
13010
13190
  } catch {
13011
13191
  printError(`Cannot read or parse ${opts.file}`);
@@ -13499,10 +13679,10 @@ function collectCodeDelta(files, opts) {
13499
13679
 
13500
13680
  // src/lib/debounce.ts
13501
13681
  var import_node_fs9 = require("node:fs");
13502
- var import_node_crypto4 = require("node:crypto");
13682
+ var import_node_crypto5 = require("node:crypto");
13503
13683
  function scopedFile(base, sessionId) {
13504
13684
  if (!sessionId) return base;
13505
- return `${base}.${(0, import_node_crypto4.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
13685
+ return `${base}.${(0, import_node_crypto5.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
13506
13686
  }
13507
13687
  function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
13508
13688
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
@@ -13543,7 +13723,7 @@ function checkMtime(files, bypassForRecentCommits, sessionId) {
13543
13723
  return "No files modified since last analysis";
13544
13724
  }
13545
13725
  function computeContentHash(files) {
13546
- const hash = (0, import_node_crypto4.createHash)("sha1");
13726
+ const hash = (0, import_node_crypto5.createHash)("sha1");
13547
13727
  const sorted = [...files].sort();
13548
13728
  for (const f of sorted) {
13549
13729
  const resolved = resolveFile(f) ?? f;
@@ -13931,14 +14111,14 @@ function cleanStaleSnapshots(dir, keepSet) {
13931
14111
  // src/lib/baseline.ts
13932
14112
  var import_node_fs13 = require("node:fs");
13933
14113
  var import_node_path13 = require("node:path");
13934
- var import_node_crypto5 = require("node:crypto");
14114
+ var import_node_crypto6 = require("node:crypto");
13935
14115
  var BASELINE_VERSION = 1;
13936
14116
  var BASELINE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
13937
14117
  var MIRROR_MAX_BYTES = 2 * 1024 * 1024;
13938
14118
  var DEFAULT_SESSION_KEY = "_default";
13939
14119
  function sessionKey(sessionId) {
13940
14120
  if (!sessionId) return DEFAULT_SESSION_KEY;
13941
- return (0, import_node_crypto5.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
14121
+ return (0, import_node_crypto6.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
13942
14122
  }
13943
14123
  function sessionDir(key) {
13944
14124
  return (0, import_node_path13.join)(projectPath(BASELINE_DIR), key);
@@ -14120,13 +14300,57 @@ function pruneOldBaselines() {
14120
14300
  }
14121
14301
  }
14122
14302
 
14303
+ // src/lib/task-context.ts
14304
+ var import_node_child_process9 = require("node:child_process");
14305
+ var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
14306
+ var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
14307
+ function parseLinkedIssue(sources) {
14308
+ for (const c of sources.commits ?? []) {
14309
+ const m = CLOSING_RE.exec(c);
14310
+ if (m) return { issue: parseInt(m[2], 10), via: `commit:${m[1].toLowerCase()}` };
14311
+ }
14312
+ if (sources.branch) {
14313
+ const m = BRANCH_RE.exec(sources.branch);
14314
+ if (m) return { issue: parseInt(m[1], 10), via: "branch" };
14315
+ }
14316
+ return null;
14317
+ }
14318
+ function safeExec(cmd, timeout) {
14319
+ try {
14320
+ return (0, import_node_child_process9.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
14321
+ } catch {
14322
+ return "";
14323
+ }
14324
+ }
14325
+ function defaultGhFetch(issue) {
14326
+ const raw = safeExec(`gh issue view ${issue} --json title,body`, 5e3);
14327
+ if (!raw) return null;
14328
+ try {
14329
+ const j = JSON.parse(raw);
14330
+ return j.title ? { title: j.title, body: j.body ?? "" } : null;
14331
+ } catch {
14332
+ return null;
14333
+ }
14334
+ }
14335
+ function resolveTaskContext(opts) {
14336
+ const branch = opts?.branch ?? safeExec("git rev-parse --abbrev-ref HEAD", 3e3);
14337
+ const commits = opts?.commits ?? safeExec("git log -5 --format=%s%n%b", 3e3).split("\n").map((l) => l.trim()).filter(Boolean);
14338
+ const linked = parseLinkedIssue({ branch, commits });
14339
+ if (!linked) return null;
14340
+ const issue = (opts?.ghFetch ?? defaultGhFetch)(linked.issue);
14341
+ if (!issue) return null;
14342
+ const body = (issue.body ?? "").slice(0, 4e3).trim();
14343
+ const goal = `[#${linked.issue}] ${issue.title}${body ? "\n\n" + body : ""}`;
14344
+ return { number: linked.issue, title: issue.title, goal, via: linked.via };
14345
+ }
14346
+
14123
14347
  // src/lib/offline.ts
14124
14348
  var import_node_fs14 = require("node:fs");
14125
- var import_node_crypto6 = require("node:crypto");
14349
+ var import_node_crypto7 = require("node:crypto");
14126
14350
  function cacheRequest(body) {
14127
14351
  try {
14128
14352
  (0, import_node_fs14.mkdirSync)(CACHE_DIR, { recursive: true });
14129
- const suffix = (0, import_node_crypto6.randomBytes)(4).toString("hex");
14353
+ const suffix = (0, import_node_crypto7.randomBytes)(4).toString("hex");
14130
14354
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
14131
14355
  (0, import_node_fs14.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
14132
14356
  } catch {
@@ -14331,6 +14555,30 @@ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPromp
14331
14555
  }
14332
14556
  return "standard";
14333
14557
  }
14558
+ var FILE_MUTATE_RE = /(?:^|[\s|&;(`])(?:sed\s+-i|perl\s+-i|awk\b|tee\b|dd\b|cp\b|mv\b|ln\b|install\b|touch\b|patch\b|git\s+(?:apply|am)\b|cargo\s+build|go\s+generate|make\b|--write\b|--fix\b|--in-place\b)|>>?(?![&>])/i;
14559
+ var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
14560
+ var READ_ONLY_RE = /^\s*(?:git\s+(?:status|diff|log|show|branch|remote|config|rev-parse|ls-files|blame|describe)|ls|cat|head|tail|less|grep|rg|find|pwd|echo|printf|wc|which|type|tree|stat|file|env|printenv|date|whoami)\b/i;
14561
+ var CHAIN_RE = /&&|\||;|\$\(|\x60/;
14562
+ function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
14563
+ if (!actionSummary) return sessionAuthoredCode;
14564
+ if ((actionSummary.subagents ?? 0) > 0) return true;
14565
+ if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
14566
+ const commands = actionSummary.commands ?? [];
14567
+ if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
14568
+ if (sessionAuthoredCode) {
14569
+ const allSafe = commands.length > 0 && commands.every(
14570
+ (c) => !CHAIN_RE.test(c) && (GIT_PLUMBING_RE.test(c) || READ_ONLY_RE.test(c))
14571
+ );
14572
+ if (!allSafe) return true;
14573
+ }
14574
+ return false;
14575
+ }
14576
+ function scopeToAuthored(files, actionSummary) {
14577
+ if (!actionSummary) return { files, signal: "no-transcript" };
14578
+ const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
14579
+ if (touched.length === 0) return { files: [], signal: "none-authored" };
14580
+ return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
14581
+ }
14334
14582
  function narrowToAgentAuthored(files, actionSummary) {
14335
14583
  if (!actionSummary) return files;
14336
14584
  const touched = [
@@ -14533,6 +14781,7 @@ function buildSummary(lines) {
14533
14781
  searches++;
14534
14782
  break;
14535
14783
  case "Agent":
14784
+ case "Task":
14536
14785
  subagents++;
14537
14786
  break;
14538
14787
  case "WebFetch":
@@ -14585,21 +14834,50 @@ function addPath(set, rawPath) {
14585
14834
  function sanitizeCommand(rawCmd) {
14586
14835
  if (typeof rawCmd !== "string" || !rawCmd) return null;
14587
14836
  let cmd = rawCmd.split("\n")[0];
14837
+ let cut = -1;
14838
+ let marker = "";
14588
14839
  for (const sep of [" | ", " > ", " >> ", " 2>", " && ", " ; "]) {
14589
14840
  const idx = cmd.indexOf(sep);
14590
- if (idx > 0) cmd = cmd.slice(0, idx);
14841
+ if (idx > 0 && (cut === -1 || idx < cut)) {
14842
+ cut = idx;
14843
+ marker = sep.trim();
14844
+ }
14591
14845
  }
14846
+ if (cut > -1) cmd = cmd.slice(0, cut);
14592
14847
  if (cmd.length > MAX_COMMAND_CHARS) {
14593
14848
  cmd = cmd.slice(0, MAX_COMMAND_CHARS);
14594
14849
  }
14595
- return cmd.trim() || null;
14850
+ cmd = cmd.trim();
14851
+ if (marker) cmd = cmd ? `${cmd} ${marker}` : marker;
14852
+ return cmd || null;
14596
14853
  }
14597
14854
  function capArray(set, max) {
14598
14855
  return Array.from(set).slice(0, max);
14599
14856
  }
14600
14857
 
14858
+ // src/lib/run-mode.ts
14859
+ function parseAutonomousEnv(raw) {
14860
+ if (raw === void 0) return void 0;
14861
+ const v = raw.trim().toLowerCase();
14862
+ if (v === "") return void 0;
14863
+ if (v === "0" || v === "false" || v === "off" || v === "no") return false;
14864
+ return true;
14865
+ }
14866
+ function resolveRunMode(inputs = {}) {
14867
+ if (inputs.autonomousFlag === true) return "autonomous";
14868
+ if (inputs.autonomousFlag === false) return "interactive";
14869
+ const env = inputs.env ?? process.env;
14870
+ const envDecision = parseAutonomousEnv(env.VERITY_AUTONOMOUS);
14871
+ if (envDecision !== void 0) return envDecision ? "autonomous" : "interactive";
14872
+ const isTTY = inputs.isTTY ?? Boolean(process.stdin?.isTTY);
14873
+ return isTTY ? "interactive" : "autonomous";
14874
+ }
14875
+ function isExplicitlyAutonomous(env = process.env) {
14876
+ return parseAutonomousEnv(env.VERITY_AUTONOMOUS) === true || parseAutonomousEnv(env.CI) === true || parseAutonomousEnv(env.GITHUB_ACTIONS) === true;
14877
+ }
14878
+
14601
14879
  // src/lib/seed-runner.ts
14602
- var import_promises12 = require("node:fs/promises");
14880
+ var import_promises11 = require("node:fs/promises");
14603
14881
  var import_node_fs18 = require("node:fs");
14604
14882
  var import_node_path15 = require("node:path");
14605
14883
  var import_yaml2 = __toESM(require_dist());
@@ -14845,7 +15123,7 @@ async function runSeed(opts) {
14845
15123
  }
14846
15124
  let standardDoc;
14847
15125
  try {
14848
- const raw = await (0, import_promises12.readFile)(STANDARD_FILE, "utf-8");
15126
+ const raw = await (0, import_promises11.readFile)(STANDARD_FILE, "utf-8");
14849
15127
  standardDoc = (0, import_yaml2.parse)(raw);
14850
15128
  } catch {
14851
15129
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
@@ -14854,7 +15132,7 @@ async function runSeed(opts) {
14854
15132
  let readmeContent;
14855
15133
  if ((0, import_node_fs18.existsSync)("README.md")) {
14856
15134
  try {
14857
- readmeContent = await (0, import_promises12.readFile)("README.md", "utf-8");
15135
+ readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
14858
15136
  } catch {
14859
15137
  }
14860
15138
  }
@@ -14862,7 +15140,7 @@ async function runSeed(opts) {
14862
15140
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
14863
15141
  if ((0, import_node_fs18.existsSync)(p)) {
14864
15142
  try {
14865
- claudeMdContent = await (0, import_promises12.readFile)(p, "utf-8");
15143
+ claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
14866
15144
  break;
14867
15145
  } catch {
14868
15146
  }
@@ -14921,8 +15199,8 @@ async function runSeed(opts) {
14921
15199
  const filePathRel = res.data.file_path;
14922
15200
  const targetPath = (0, import_node_path15.join)(MEMORY_DIR, filePathRel);
14923
15201
  try {
14924
- await (0, import_promises12.mkdir)((0, import_node_path15.dirname)(targetPath), { recursive: true });
14925
- await (0, import_promises12.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
15202
+ await (0, import_promises11.mkdir)((0, import_node_path15.dirname)(targetPath), { recursive: true });
15203
+ await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
14926
15204
  created++;
14927
15205
  opts.onCreated?.(nodeId, filePathRel, c);
14928
15206
  } catch (err) {
@@ -15012,7 +15290,10 @@ async function runAnalyze(opts, globals) {
15012
15290
  }
15013
15291
  const { assistantMessage: assistantResponse, stopReason, transcriptPath, sessionId } = await readStopHookStdin();
15014
15292
  const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
15015
- const baselineSessionId = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
15293
+ const tokenResult = await resolveToken(globals.token);
15294
+ const scopeToken = tokenResult.ok ? tokenResult.data.token : void 0;
15295
+ const rawSessionId = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
15296
+ const baselineSessionId = sessionScopeKey(scopeToken, rawSessionId);
15016
15297
  const baseline = readBaseline(baselineSessionId);
15017
15298
  if (baseline) {
15018
15299
  logEvent("baseline_loaded", {
@@ -15030,7 +15311,7 @@ async function runAnalyze(opts, globals) {
15030
15311
  passAndExit("No analyzable files changed");
15031
15312
  }
15032
15313
  const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
15033
- const conversation = await readAndClearConversationBuffer(sessionId ?? void 0);
15314
+ const conversation = await readAndClearConversationBuffer(baselineSessionId);
15034
15315
  const specs = discoverSpecs();
15035
15316
  const plans = discoverPlans();
15036
15317
  const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
@@ -15044,7 +15325,6 @@ async function runAnalyze(opts, globals) {
15044
15325
  if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
15045
15326
  passAndExit("Reflection-prompt turn \u2014 skipping analysis");
15046
15327
  }
15047
- const tokenResult = await resolveToken(globals.token);
15048
15328
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
15049
15329
  if (!tokenResult.ok || !urlResult.ok) {
15050
15330
  localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
@@ -15138,8 +15418,11 @@ async function runAnalyze(opts, globals) {
15138
15418
  }
15139
15419
  contentHash = hashResult.hash;
15140
15420
  if (analysisMode !== "plan") {
15141
- const agentNarrowed = narrowToAgentAuthored(allForReview, actionSummary);
15142
- const baseForReview = agentNarrowed.length > 0 ? agentNarrowed : allForReview;
15421
+ const scoped = scopeToAuthored(allForReview, actionSummary);
15422
+ if (scoped.signal === "none-authored" && !hasNonEditAuthorship(actionSummary, sessionAuthoredCode)) {
15423
+ passAndExit("No agent-authored code this turn \u2014 working-tree changes were not authored by this session");
15424
+ }
15425
+ const baseForReview = scoped.signal === "authored" && scoped.files.length > 0 ? scoped.files : allForReview;
15143
15426
  const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
15144
15427
  if (!opts.skipStatic && isCodacyAvailable()) {
15145
15428
  let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
@@ -15269,7 +15552,9 @@ async function runAnalyze(opts, globals) {
15269
15552
  if (snapshotResult.has_snapshots && snapshotResult.diffs.length > 0) {
15270
15553
  requestBody.snapshot_diffs = snapshotResult.diffs;
15271
15554
  }
15272
- const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse;
15555
+ const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
15556
+ const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
15557
+ const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task;
15273
15558
  if (hasIntent) {
15274
15559
  const intentContext = {};
15275
15560
  if (conversation && conversation.prompts.length > 0) {
@@ -15284,6 +15569,10 @@ async function runAnalyze(opts, globals) {
15284
15569
  intentContext.recent_commits = conversation.recent_commits;
15285
15570
  }
15286
15571
  }
15572
+ if (w4Task && !intentContext.user_prompt) {
15573
+ intentContext.user_prompt = w4Task.goal;
15574
+ logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
15575
+ }
15287
15576
  if (assistantResponse) {
15288
15577
  const cap = analysisMode === "plan" ? MAX_ASSISTANT_RESPONSE_CHARS_PLAN : MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT;
15289
15578
  intentContext.assistant_response = assistantResponse.length > cap ? assistantResponse.slice(0, cap) : assistantResponse;
@@ -15576,7 +15865,10 @@ function registerBaselineCommands(program2) {
15576
15865
  }
15577
15866
  }
15578
15867
  }
15579
- const result = captureBaseline({ sessionId, source });
15868
+ const authForScope = await resolveToken(program2.opts().token);
15869
+ const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
15870
+ const scopeSession = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
15871
+ const result = captureBaseline({ sessionId: sessionScopeKey(scopeToken, scopeSession), source });
15580
15872
  logEvent("baseline_capture", {
15581
15873
  created: result.created,
15582
15874
  source: source ?? null,
@@ -16022,20 +16314,112 @@ function writeBlockMessage(moment, response) {
16022
16314
  var import_node_fs24 = require("node:fs");
16023
16315
  var import_promises13 = require("node:fs/promises");
16024
16316
  var import_node_path19 = require("node:path");
16025
- var import_node_child_process10 = require("node:child_process");
16317
+ var import_node_child_process11 = require("node:child_process");
16026
16318
  var readline2 = __toESM(require("node:readline/promises"));
16027
16319
 
16028
16320
  // src/commands/migrate.ts
16029
16321
  var import_node_fs23 = require("node:fs");
16030
16322
  var import_node_path18 = require("node:path");
16031
- var import_node_child_process9 = require("node:child_process");
16323
+ var import_node_child_process10 = require("node:child_process");
16324
+
16325
+ // src/lib/telemetry.ts
16326
+ var import_promises12 = require("node:fs/promises");
16327
+ var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
16328
+ var GITIGNORE_FILE = ".gitignore";
16329
+ var GITIGNORE_ENTRY = ".claude/settings.local.json";
16330
+ var OTEL_HEADERS_HELPER_CMD = "verity telemetry headers";
16331
+ var LEGACY_TELEMETRY_ENV_KEYS = ["OTEL_EXPORTER_OTLP_HEADERS"];
16332
+ function deriveOtlpEndpoint(serviceUrl) {
16333
+ return serviceUrl.replace(/\/+$/, "").replace(/\/v1$/, "") + "/v1/otlp";
16334
+ }
16335
+ function buildTelemetryEnv(serviceUrl) {
16336
+ return {
16337
+ CLAUDE_CODE_ENABLE_TELEMETRY: "1",
16338
+ OTEL_METRICS_EXPORTER: "otlp",
16339
+ OTEL_TRACES_EXPORTER: "otlp",
16340
+ CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
16341
+ OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
16342
+ OTEL_EXPORTER_OTLP_ENDPOINT: deriveOtlpEndpoint(serviceUrl),
16343
+ OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: "delta",
16344
+ OTEL_LOG_USER_PROMPTS: "0",
16345
+ OTEL_LOG_TOOL_DETAILS: "0",
16346
+ OTEL_LOG_TOOL_CONTENT: "0",
16347
+ OTEL_METRICS_INCLUDE_SESSION_ID: "true",
16348
+ OTEL_METRICS_INCLUDE_ACCOUNT_UUID: "false",
16349
+ OTEL_METRICS_INCLUDE_VERSION: "false",
16350
+ OTEL_METRIC_EXPORT_INTERVAL: "60000"
16351
+ };
16352
+ }
16353
+ var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv(""));
16354
+ async function readSettingsLocal() {
16355
+ try {
16356
+ return JSON.parse(await (0, import_promises12.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
16357
+ } catch {
16358
+ return {};
16359
+ }
16360
+ }
16361
+ async function writeSettingsLocal(settings) {
16362
+ await writeJsonFilePreservingStyle(projectPath(SETTINGS_LOCAL_FILE2), settings);
16363
+ }
16364
+ async function ensureGitignore() {
16365
+ const file = projectPath(GITIGNORE_FILE);
16366
+ let content = "";
16367
+ try {
16368
+ content = await (0, import_promises12.readFile)(file, "utf-8");
16369
+ } catch {
16370
+ }
16371
+ const lines = content.split("\n").map((l) => l.trim());
16372
+ if (lines.includes(GITIGNORE_ENTRY) || lines.includes(".claude/") || lines.includes(".claude")) {
16373
+ return;
16374
+ }
16375
+ const block = "# Verity telemetry \u2014 machine-local Claude Code settings\n" + GITIGNORE_ENTRY + "\n";
16376
+ const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
16377
+ await (0, import_promises12.writeFile)(file, next);
16378
+ }
16379
+ async function installTelemetry(serviceUrl) {
16380
+ const env = buildTelemetryEnv(serviceUrl);
16381
+ const settings = await readSettingsLocal();
16382
+ settings.env = { ...settings.env ?? {}, ...env };
16383
+ for (const key of LEGACY_TELEMETRY_ENV_KEYS) delete settings.env[key];
16384
+ settings.otelHeadersHelper = OTEL_HEADERS_HELPER_CMD;
16385
+ await writeSettingsLocal(settings);
16386
+ await ensureGitignore();
16387
+ return { ok: true, data: { endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT } };
16388
+ }
16389
+ async function checkTelemetry() {
16390
+ const env = (await readSettingsLocal()).env ?? {};
16391
+ const enabled = env.CLAUDE_CODE_ENABLE_TELEMETRY === "1" && !!env.OTEL_EXPORTER_OTLP_ENDPOINT;
16392
+ return { enabled, endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? null, settingsPath: SETTINGS_LOCAL_FILE2 };
16393
+ }
16394
+ async function uninstallTelemetry() {
16395
+ const settings = await readSettingsLocal();
16396
+ const hadHelper = settings.otelHeadersHelper === OTEL_HEADERS_HELPER_CMD;
16397
+ if (hadHelper) delete settings.otelHeadersHelper;
16398
+ if (!settings.env) {
16399
+ if (hadHelper) await writeSettingsLocal(settings);
16400
+ return { ok: true, data: { removed: 0 } };
16401
+ }
16402
+ let removed = 0;
16403
+ for (const key of VERITY_TELEMETRY_KEYS) {
16404
+ if (key in settings.env) {
16405
+ delete settings.env[key];
16406
+ removed++;
16407
+ }
16408
+ }
16409
+ for (const key of LEGACY_TELEMETRY_ENV_KEYS) delete settings.env[key];
16410
+ if (Object.keys(settings.env).length === 0) delete settings.env;
16411
+ await writeSettingsLocal(settings);
16412
+ return { ok: true, data: { removed } };
16413
+ }
16414
+
16415
+ // src/commands/migrate.ts
16032
16416
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
16033
16417
  function defaultNpmRemover(pkg) {
16034
- (0, import_node_child_process9.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
16418
+ (0, import_node_child_process10.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
16035
16419
  }
16036
16420
  function isGitTracked(cwd, relPath) {
16037
16421
  try {
16038
- (0, import_node_child_process9.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
16422
+ (0, import_node_child_process10.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
16039
16423
  return true;
16040
16424
  } catch {
16041
16425
  return false;
@@ -16043,7 +16427,7 @@ function isGitTracked(cwd, relPath) {
16043
16427
  }
16044
16428
  function isGitRepo(cwd) {
16045
16429
  try {
16046
- (0, import_node_child_process9.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
16430
+ (0, import_node_child_process10.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
16047
16431
  return true;
16048
16432
  } catch {
16049
16433
  return false;
@@ -16059,6 +16443,7 @@ async function runMigration(opts = {}) {
16059
16443
  await migrateLegacyHooks(root, actions);
16060
16444
  await migrateClaudeMd(root, actions);
16061
16445
  migrateStandardFile(root, actions);
16446
+ await migrateTelemetryHeaders(root, actions);
16062
16447
  removeLegacyPackage(movedProjectDir, npmRemover, actions);
16063
16448
  return { actions, migrated: actions.length > 0 };
16064
16449
  }
@@ -16082,7 +16467,7 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
16082
16467
  );
16083
16468
  }
16084
16469
  try {
16085
- (0, import_node_child_process9.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
16470
+ (0, import_node_child_process10.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
16086
16471
  actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
16087
16472
  moved = true;
16088
16473
  } catch {
@@ -16161,7 +16546,7 @@ function migrateStandardFile(root, actions) {
16161
16546
  let moved = false;
16162
16547
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
16163
16548
  try {
16164
- (0, import_node_child_process9.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
16549
+ (0, import_node_child_process10.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
16165
16550
  moved = true;
16166
16551
  } catch {
16167
16552
  }
@@ -16172,6 +16557,25 @@ function migrateStandardFile(root, actions) {
16172
16557
  if (refreshed !== content) (0, import_node_fs23.writeFileSync)(verityMd, refreshed);
16173
16558
  actions.push("Renamed GATE.md \u2192 VERITY.md");
16174
16559
  }
16560
+ async function migrateTelemetryHeaders(root, actions) {
16561
+ const file = (0, import_node_path18.join)(root, ".claude", "settings.local.json");
16562
+ if (!(0, import_node_fs23.existsSync)(file)) return;
16563
+ let settings;
16564
+ try {
16565
+ settings = JSON.parse(readFileSyncSafe(file) || "{}");
16566
+ } catch {
16567
+ return;
16568
+ }
16569
+ const env = settings.env;
16570
+ const hasInlineHeader = !!env && "OTEL_EXPORTER_OTLP_HEADERS" in env;
16571
+ const telemetryEnabled = env?.CLAUDE_CODE_ENABLE_TELEMETRY === "1";
16572
+ const needsHelper = telemetryEnabled && settings.otelHeadersHelper !== OTEL_HEADERS_HELPER_CMD;
16573
+ if (!hasInlineHeader && !needsHelper) return;
16574
+ if (hasInlineHeader && env) delete env["OTEL_EXPORTER_OTLP_HEADERS"];
16575
+ if (telemetryEnabled) settings.otelHeadersHelper = OTEL_HEADERS_HELPER_CMD;
16576
+ await writeJsonFilePreservingStyle(file, settings);
16577
+ actions.push("Moved the telemetry token out of .claude/settings.local.json (now resolved at runtime)");
16578
+ }
16175
16579
  function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
16176
16580
  if (!movedProjectDir) return;
16177
16581
  try {
@@ -16217,7 +16621,7 @@ function readFileSyncSafe(path) {
16217
16621
  }
16218
16622
  function hasStagedChanges(root) {
16219
16623
  try {
16220
- (0, import_node_child_process9.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
16624
+ (0, import_node_child_process10.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
16221
16625
  return false;
16222
16626
  } catch {
16223
16627
  return true;
@@ -16313,30 +16717,47 @@ async function promptYes(question) {
16313
16717
  rl.close();
16314
16718
  }
16315
16719
  }
16316
- async function runOptionalAuth(serviceUrl) {
16317
- const existing = await resolveToken();
16318
- if (existing.ok) {
16319
- const who = await whoami(existing.data.token, serviceUrl);
16320
- if (who.ok && who.data.logged_in) {
16321
- printInfo(`Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713 \u2014 runs & memory sync to Verity.`);
16322
- return;
16720
+ async function confirmExistingLogin(serviceUrl, opts) {
16721
+ const existing = await resolveToken(opts.token);
16722
+ if (!existing.ok) return "drive-login";
16723
+ const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
16724
+ if (who.ok && who.data.logged_in) {
16725
+ printInfo(`Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713 \u2014 runs & memory sync to Verity.`);
16726
+ return "handled";
16727
+ }
16728
+ if (!who.ok) {
16729
+ if (existing.data.userId != null) {
16730
+ printInfo(`Logged in as ${existing.data.email ?? `user #${existing.data.userId}`} (cached \u2014 could not reach the Verity service). \u2713`);
16731
+ } else {
16732
+ printInfo("Could not confirm your login state with the service; continuing with your existing token.");
16733
+ printInfo(` (${who.error})`);
16323
16734
  }
16324
- if (!who.ok) {
16325
- if (existing.data.userId != null) {
16326
- printInfo(`Logged in as ${existing.data.email ?? `user #${existing.data.userId}`} (cached \u2014 could not reach the Verity service). \u2713`);
16327
- } else {
16328
- printInfo("Could not confirm your login state with the service; continuing with your existing token.");
16329
- }
16330
- return;
16735
+ return "handled";
16736
+ }
16737
+ console.log("");
16738
+ printWarn("You are NOT logged in \u2014 this project has only an anonymous token.");
16739
+ printInfo(" The gate still runs, but no runs are saved and Verity keeps no memory of this project.");
16740
+ printInfo(" Log in below to unlock run history, trends, and cloud memory (strongly recommended).");
16741
+ return "drive-login";
16742
+ }
16743
+ async function runOptionalAuth(resolution, opts = {}) {
16744
+ let serviceUrl = resolution?.url ?? DEFAULT_SERVICE_URL;
16745
+ let healed = false;
16746
+ if (resolution) {
16747
+ const heal = await maybeHealServiceUrl(resolution, opts.verbose);
16748
+ serviceUrl = heal.serviceUrl;
16749
+ healed = heal.healed;
16750
+ if (healed) {
16751
+ printInfo(" Log in below to re-register this project and repair ~/.verity/credentials.");
16331
16752
  }
16332
- console.log("");
16333
- printWarn("You are NOT logged in \u2014 this project has only an anonymous token.");
16334
- printInfo(" The gate still runs, but no runs are saved and Verity keeps no memory of this project.");
16335
- printInfo(" Log in below to unlock run history, trends, and cloud memory (strongly recommended).");
16753
+ }
16754
+ if (!healed) {
16755
+ const state = await confirmExistingLogin(serviceUrl, opts);
16756
+ if (state === "handled") return;
16336
16757
  }
16337
16758
  let remote = "";
16338
16759
  try {
16339
- remote = (0, import_node_child_process10.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
16760
+ remote = (0, import_node_child_process11.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
16340
16761
  } catch {
16341
16762
  }
16342
16763
  const localOnlyNote = () => {
@@ -16369,7 +16790,7 @@ async function runOptionalAuth(serviceUrl) {
16369
16790
  }
16370
16791
  const projectName = parseRemote(remote)?.repo ?? (0, import_node_path19.basename)(process.cwd());
16371
16792
  printInfo("Authenticating with GitHub\u2026");
16372
- const result = await registerProject({ projectName, remote, serviceUrl });
16793
+ const result = await registerProject({ projectName, remote, serviceUrl, verbose: opts.verbose });
16373
16794
  if (result.ok) {
16374
16795
  const who = result.data.email ?? (result.data.userId != null ? `user #${result.data.userId}` : null);
16375
16796
  printInfo(`Logged in${who ? ` as ${who}` : ""} \u2713 \u2014 runs, history, and cloud memory now sync to Verity.`);
@@ -16432,30 +16853,30 @@ function registerInitCommand(program2) {
16432
16853
  }
16433
16854
  printInfo(` Node.js ${nodeVersion} \u2713`);
16434
16855
  try {
16435
- const gitVersion = (0, import_node_child_process10.execSync)("git --version", { encoding: "utf-8" }).trim();
16856
+ const gitVersion = (0, import_node_child_process11.execSync)("git --version", { encoding: "utf-8" }).trim();
16436
16857
  printInfo(` ${gitVersion} \u2713`);
16437
16858
  } catch {
16438
16859
  printError("git is required but not installed. Install from https://git-scm.com");
16439
16860
  process.exit(1);
16440
16861
  }
16441
16862
  try {
16442
- (0, import_node_child_process10.execSync)("which claude", { encoding: "utf-8" });
16863
+ (0, import_node_child_process11.execSync)("which claude", { encoding: "utf-8" });
16443
16864
  printInfo(" Claude Code \u2713");
16444
16865
  } catch {
16445
16866
  printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
16446
16867
  }
16447
16868
  try {
16448
- (0, import_node_child_process10.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
16869
+ (0, import_node_child_process11.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
16449
16870
  printInfo(" @codacy/analysis-cli \u2713");
16450
16871
  } catch {
16451
16872
  printInfo(" Installing @codacy/analysis-cli...");
16452
16873
  try {
16453
- (0, import_node_child_process10.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
16874
+ (0, import_node_child_process11.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
16454
16875
  printInfo(" @codacy/analysis-cli installed \u2713");
16455
16876
  } catch {
16456
16877
  try {
16457
16878
  printWarn(" Retrying with sudo...");
16458
- (0, import_node_child_process10.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
16879
+ (0, import_node_child_process11.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
16459
16880
  printInfo(" @codacy/analysis-cli installed \u2713");
16460
16881
  } catch {
16461
16882
  printWarn(" Could not install @codacy/analysis-cli automatically.");
@@ -16523,8 +16944,11 @@ function registerInitCommand(program2) {
16523
16944
  console.log("");
16524
16945
  try {
16525
16946
  const globals = program2.opts();
16526
- const urlResult = await resolveServiceUrl(globals.serviceUrl);
16527
- await runOptionalAuth(urlResult.ok ? urlResult.data : DEFAULT_SERVICE_URL);
16947
+ const urlResult = await resolveServiceUrlDetailed(globals.serviceUrl);
16948
+ await runOptionalAuth(urlResult.ok ? urlResult.data : null, {
16949
+ token: globals.token,
16950
+ verbose: globals.verbose
16951
+ });
16528
16952
  } catch (err) {
16529
16953
  printWarn(`Authentication step skipped: ${err.message}`);
16530
16954
  }
@@ -16894,24 +17318,6 @@ function registerResetCommand(program2) {
16894
17318
  });
16895
17319
  }
16896
17320
 
16897
- // src/lib/run-mode.ts
16898
- function parseAutonomousEnv(raw) {
16899
- if (raw === void 0) return void 0;
16900
- const v = raw.trim().toLowerCase();
16901
- if (v === "") return void 0;
16902
- if (v === "0" || v === "false" || v === "off" || v === "no") return false;
16903
- return true;
16904
- }
16905
- function resolveRunMode(inputs = {}) {
16906
- if (inputs.autonomousFlag === true) return "autonomous";
16907
- if (inputs.autonomousFlag === false) return "interactive";
16908
- const env = inputs.env ?? process.env;
16909
- const envDecision = parseAutonomousEnv(env.VERITY_AUTONOMOUS);
16910
- if (envDecision !== void 0) return envDecision ? "autonomous" : "interactive";
16911
- const isTTY = inputs.isTTY ?? Boolean(process.stdin?.isTTY);
16912
- return isTTY ? "interactive" : "autonomous";
16913
- }
16914
-
16915
17321
  // src/commands/reflect.ts
16916
17322
  function registerReflectCommand(program2) {
16917
17323
  program2.command("reflect").description("Capture learnings \u2014 auto-extract or submit a human reflection").option("--user-input <text>", "The reflection to record (the agent-drafted or user-confirmed text)").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--task-id <id>", "Task to reflect on (defaults to current task)").option("--autonomous", "Record the drafted reflection without a confirm step (auto-detected from TTY / VERITY_AUTONOMOUS when omitted)").action(async (opts) => {
@@ -17140,89 +17546,6 @@ function registerRunCommand(program2) {
17140
17546
  });
17141
17547
  }
17142
17548
 
17143
- // src/lib/telemetry.ts
17144
- var import_promises14 = require("node:fs/promises");
17145
- var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
17146
- var GITIGNORE_FILE = ".gitignore";
17147
- var GITIGNORE_ENTRY = ".claude/settings.local.json";
17148
- function deriveOtlpEndpoint(serviceUrl) {
17149
- return serviceUrl.replace(/\/+$/, "").replace(/\/v1$/, "") + "/v1/otlp";
17150
- }
17151
- function buildTelemetryEnv(serviceUrl, token) {
17152
- return {
17153
- CLAUDE_CODE_ENABLE_TELEMETRY: "1",
17154
- OTEL_METRICS_EXPORTER: "otlp",
17155
- OTEL_TRACES_EXPORTER: "otlp",
17156
- CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
17157
- OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
17158
- OTEL_EXPORTER_OTLP_ENDPOINT: deriveOtlpEndpoint(serviceUrl),
17159
- // <token> is whatever is in .verity/credentials — verity_ OR legacy gate_.
17160
- // The server hashes the raw token regardless of prefix.
17161
- OTEL_EXPORTER_OTLP_HEADERS: `Authorization=Bearer ${token}`,
17162
- OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: "delta",
17163
- OTEL_LOG_USER_PROMPTS: "0",
17164
- OTEL_LOG_TOOL_DETAILS: "0",
17165
- OTEL_LOG_TOOL_CONTENT: "0",
17166
- OTEL_METRICS_INCLUDE_SESSION_ID: "true",
17167
- OTEL_METRICS_INCLUDE_ACCOUNT_UUID: "false",
17168
- OTEL_METRICS_INCLUDE_VERSION: "false",
17169
- OTEL_METRIC_EXPORT_INTERVAL: "60000"
17170
- };
17171
- }
17172
- var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv("", ""));
17173
- async function readSettingsLocal() {
17174
- try {
17175
- return JSON.parse(await (0, import_promises14.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
17176
- } catch {
17177
- return {};
17178
- }
17179
- }
17180
- async function writeSettingsLocal(settings) {
17181
- await writeJsonFilePreservingStyle(projectPath(SETTINGS_LOCAL_FILE2), settings);
17182
- }
17183
- async function ensureGitignore() {
17184
- const file = projectPath(GITIGNORE_FILE);
17185
- let content = "";
17186
- try {
17187
- content = await (0, import_promises14.readFile)(file, "utf-8");
17188
- } catch {
17189
- }
17190
- const lines = content.split("\n").map((l) => l.trim());
17191
- if (lines.includes(GITIGNORE_ENTRY) || lines.includes(".claude/") || lines.includes(".claude")) {
17192
- return;
17193
- }
17194
- const block = "# Verity telemetry \u2014 holds your project token\n" + GITIGNORE_ENTRY + "\n";
17195
- const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
17196
- await (0, import_promises14.writeFile)(file, next);
17197
- }
17198
- async function installTelemetry(serviceUrl, token) {
17199
- const env = buildTelemetryEnv(serviceUrl, token);
17200
- const settings = await readSettingsLocal();
17201
- settings.env = { ...settings.env ?? {}, ...env };
17202
- await writeSettingsLocal(settings);
17203
- await ensureGitignore();
17204
- return { ok: true, data: { endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT } };
17205
- }
17206
- async function checkTelemetry() {
17207
- const env = (await readSettingsLocal()).env ?? {};
17208
- const enabled = env.CLAUDE_CODE_ENABLE_TELEMETRY === "1" && !!env.OTEL_EXPORTER_OTLP_ENDPOINT;
17209
- return { enabled, endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? null, settingsPath: SETTINGS_LOCAL_FILE2 };
17210
- }
17211
- async function uninstallTelemetry() {
17212
- const settings = await readSettingsLocal();
17213
- if (!settings.env) return { ok: true, data: { removed: 0 } };
17214
- let removed = 0;
17215
- for (const key of VERITY_TELEMETRY_KEYS) {
17216
- if (key in settings.env) {
17217
- delete settings.env[key];
17218
- removed++;
17219
- }
17220
- }
17221
- if (Object.keys(settings.env).length === 0) delete settings.env;
17222
- await writeSettingsLocal(settings);
17223
- return { ok: true, data: { removed } };
17224
- }
17225
-
17226
17549
  // src/commands/telemetry.ts
17227
17550
  function registerTelemetryCommands(program2) {
17228
17551
  const telemetry = program2.command("telemetry").description("Manage Claude Code OpenTelemetry export to Verity (cost & usage)");
@@ -17238,16 +17561,25 @@ function registerTelemetryCommands(program2) {
17238
17561
  printError(urlResult.error);
17239
17562
  process.exit(1);
17240
17563
  }
17241
- const result = await installTelemetry(urlResult.data, tokenResult.data.token);
17564
+ const result = await installTelemetry(urlResult.data);
17242
17565
  if (!result.ok) {
17243
17566
  printError(result.error);
17244
17567
  process.exit(1);
17245
17568
  }
17246
17569
  printInfo(`Telemetry enabled \u2192 ${result.data.endpoint}`);
17247
- printInfo(` wrote ${SETTINGS_LOCAL_FILE2} (gitignored \u2014 it holds your project token)`);
17570
+ printInfo(` wrote ${SETTINGS_LOCAL_FILE2} (no token stored \u2014 resolved at runtime via otelHeadersHelper)`);
17248
17571
  printInfo(" takes effect on your NEXT Claude Code session; first metrics appear within ~60s of activity");
17249
17572
  printInfo(" view cost & usage at /usage");
17250
17573
  });
17574
+ telemetry.command("headers").description("Print the OTLP Authorization header as JSON (used by Claude Code otelHeadersHelper)").action(async () => {
17575
+ const globals = program2.opts();
17576
+ const tokenResult = await resolveToken(globals.token);
17577
+ if (tokenResult.ok) {
17578
+ printJsonCompact({ Authorization: `Bearer ${tokenResult.data.token}` });
17579
+ } else {
17580
+ printJsonCompact({});
17581
+ }
17582
+ });
17251
17583
  telemetry.command("check").description("Show whether Claude Code telemetry export to Verity is enabled").option("--json", "Output status as JSON").action(async (opts) => {
17252
17584
  const status = await checkTelemetry();
17253
17585
  if (opts.json) {
@@ -17273,7 +17605,12 @@ function registerTelemetryCommands(program2) {
17273
17605
  }
17274
17606
 
17275
17607
  // src/cli.ts
17276
- program.name("verity").description("CLI for Verity quality gate service").version("0.27.2").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
17608
+ program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.9155758").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
17609
+ try {
17610
+ await foldLegacyLocalCredential();
17611
+ } catch {
17612
+ }
17613
+ });
17277
17614
  registerAuthCommands(program);
17278
17615
  registerLoginCommand(program);
17279
17616
  registerHooksCommands(program);
@@ -17295,4 +17632,8 @@ registerMemoryCommand(program);
17295
17632
  registerRunCommand(program);
17296
17633
  registerMigrateCommand(program);
17297
17634
  registerTelemetryCommands(program);
17298
- program.parse();
17635
+ program.parseAsync().catch((err) => {
17636
+ process.stderr.write(`[verity] ${err instanceof Error ? err.message : String(err)}
17637
+ `);
17638
+ process.exit(1);
17639
+ });