@codacy/verity-cli 0.28.1-experimental.a57c8d9 → 0.28.1-experimental.dbd87b1

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
@@ -10570,135 +10570,6 @@ function rotateIfNeeded(file) {
10570
10570
  }
10571
10571
  }
10572
10572
 
10573
- // src/lib/api-client.ts
10574
- function describeFetchError(err, url) {
10575
- const message = err instanceof Error ? err.message : String(err);
10576
- const cause = err?.cause;
10577
- const causeBits = [cause?.code, cause?.hostname].filter(Boolean).join(" ");
10578
- const detail = causeBits || (cause?.message && cause.message !== message ? cause.message : "");
10579
- return `${message}${detail ? ` (${detail})` : ""} \u2014 could not reach ${url}`;
10580
- }
10581
- async function apiRequest(options) {
10582
- const {
10583
- method,
10584
- path,
10585
- serviceUrl,
10586
- token,
10587
- body,
10588
- verbose,
10589
- timeout = 9e4,
10590
- cmd = "unknown",
10591
- retry = false,
10592
- encodeBody = false,
10593
- extraHeaders
10594
- } = options;
10595
- const url = `${serviceUrl}${path}`;
10596
- const headers = {
10597
- "Content-Type": "application/json"
10598
- };
10599
- if (token) {
10600
- headers["Authorization"] = `Bearer ${token}`;
10601
- }
10602
- if (extraHeaders) {
10603
- Object.assign(headers, extraHeaders);
10604
- }
10605
- const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
10606
- if (testMockScenario) {
10607
- headers["X-Verity-Mock-Scenario"] = testMockScenario;
10608
- }
10609
- const testMockFailure = process.env.VERITY_TEST_MOCK_FAILURE;
10610
- if (testMockFailure) {
10611
- headers["X-Verity-Mock-Failure"] = testMockFailure;
10612
- }
10613
- printVerbose(`${method} ${url}`, verbose);
10614
- let serializedBody;
10615
- if (body !== void 0 && body !== null) {
10616
- const innerJson = JSON.stringify(body);
10617
- if (encodeBody) {
10618
- const payload = Buffer.from(innerJson, "utf8").toString("base64");
10619
- serializedBody = JSON.stringify({ encoding: "base64", payload });
10620
- } else {
10621
- serializedBody = innerJson;
10622
- }
10623
- printVerbose(`Body: ${serializedBody.slice(0, 500)}`, verbose);
10624
- }
10625
- const startedAt = Date.now();
10626
- const bodyBytes = serializedBody ? Buffer.byteLength(serializedBody) : 0;
10627
- const logBase = { cmd, method, url, body_bytes: bodyBytes, retry, encoded: encodeBody };
10628
- let response;
10629
- try {
10630
- response = await fetch(url, {
10631
- method,
10632
- headers,
10633
- body: serializedBody,
10634
- signal: AbortSignal.timeout(timeout)
10635
- });
10636
- } catch (err) {
10637
- const duration2 = Date.now() - startedAt;
10638
- const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
10639
- const category = isTimeout ? "timeout" : "network";
10640
- const error = isTimeout ? `Request timed out after ${timeout}ms (${url})` : `Network error: ${describeFetchError(err, url)}`;
10641
- logHttpCall({ ...logBase, duration_ms: duration2, http_status: null, category, error });
10642
- return { ok: false, error, category, http_status: null };
10643
- }
10644
- let data;
10645
- try {
10646
- data = response.status === 204 || response.headers.get("content-length") === "0" ? {} : await response.json();
10647
- } catch {
10648
- const duration2 = Date.now() - startedAt;
10649
- const error = `Invalid JSON response (HTTP ${response.status})`;
10650
- logHttpCall({
10651
- ...logBase,
10652
- duration_ms: duration2,
10653
- http_status: response.status,
10654
- category: "invalid_json",
10655
- error
10656
- });
10657
- return { ok: false, error, category: "invalid_json", http_status: response.status };
10658
- }
10659
- printVerbose(`Response ${response.status}: ${JSON.stringify(data).slice(0, 500)}`, verbose);
10660
- const duration = Date.now() - startedAt;
10661
- if (!response.ok) {
10662
- const apiErr = data;
10663
- const code = apiErr?.error?.code ?? "UNKNOWN";
10664
- const message = apiErr?.error?.message ?? `HTTP ${response.status}`;
10665
- const category = response.status >= 500 ? "http_5xx" : "http_4xx";
10666
- const error = `${code}: ${message}`;
10667
- logHttpCall({
10668
- ...logBase,
10669
- duration_ms: duration,
10670
- http_status: response.status,
10671
- category,
10672
- error
10673
- });
10674
- return { ok: false, error, category, http_status: response.status };
10675
- }
10676
- logHttpCall({
10677
- ...logBase,
10678
- duration_ms: duration,
10679
- http_status: response.status,
10680
- category: "ok"
10681
- });
10682
- return { ok: true, data };
10683
- }
10684
- function analyzeRequest(options) {
10685
- return apiRequest({
10686
- method: "POST",
10687
- path: "/analyze",
10688
- serviceUrl: options.serviceUrl,
10689
- token: options.token,
10690
- body: options.body,
10691
- timeout: options.timeout,
10692
- cmd: options.cmd,
10693
- verbose: options.verbose,
10694
- retry: options.retry,
10695
- encodeBody: true
10696
- });
10697
- }
10698
-
10699
- // src/lib/service-url.ts
10700
- var import_promises2 = require("node:fs/promises");
10701
-
10702
10573
  // src/lib/credentials.ts
10703
10574
  var import_promises = require("node:fs/promises");
10704
10575
  var import_node_path2 = require("node:path");
@@ -10805,6 +10676,37 @@ async function upsertGlobalCredential(remote, rec) {
10805
10676
  await (0, import_promises.chmod)(path, 384).catch(() => {
10806
10677
  });
10807
10678
  }
10679
+ async function removeSupersededUserCredentials(loginServiceUrl, loginUserId) {
10680
+ if (loginUserId == null) return 0;
10681
+ const path = globalCredentialsPath();
10682
+ let content;
10683
+ try {
10684
+ content = await (0, import_promises.readFile)(path, "utf-8");
10685
+ } catch {
10686
+ return 0;
10687
+ }
10688
+ const kept = [];
10689
+ let removed = 0;
10690
+ for (const line of content.split("\n")) {
10691
+ const parsed = parseCredentialLine(line);
10692
+ const superseded = parsed !== null && parsed.remote !== "" && parsed.rec.userId === loginUserId && (!parsed.rec.serviceUrl || parsed.rec.serviceUrl === loginServiceUrl);
10693
+ if (superseded) {
10694
+ removed++;
10695
+ continue;
10696
+ }
10697
+ kept.push(line);
10698
+ }
10699
+ if (removed === 0) return 0;
10700
+ while (kept.length && kept[kept.length - 1].trim() === "") kept.pop();
10701
+ try {
10702
+ await (0, import_promises.writeFile)(path, kept.join("\n") + "\n", { mode: 384 });
10703
+ await (0, import_promises.chmod)(path, 384).catch(() => {
10704
+ });
10705
+ } catch {
10706
+ return -1;
10707
+ }
10708
+ return removed;
10709
+ }
10808
10710
  function parseLocalCredentialFile(content) {
10809
10711
  const tokenMatch = content.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
10810
10712
  if (!tokenMatch) return null;
@@ -10857,457 +10759,623 @@ async function foldLegacyLocalCredential(remoteArg) {
10857
10759
  return true;
10858
10760
  }
10859
10761
 
10860
- // src/lib/service-url.ts
10861
- async function serviceUrlFromCredentials() {
10862
- const rec = await readGlobalCredential(currentRemote());
10863
- return rec?.serviceUrl ?? null;
10762
+ // src/lib/git.ts
10763
+ var import_node_child_process3 = require("node:child_process");
10764
+ var import_node_fs3 = require("node:fs");
10765
+ var import_node_path3 = require("node:path");
10766
+ function resolveFile(relpath) {
10767
+ return (0, import_node_fs3.existsSync)(relpath) ? relpath : null;
10864
10768
  }
10865
- async function serviceUrlFromVerityMd() {
10769
+ function execGit(cmd) {
10866
10770
  try {
10867
- const content = await (0, import_promises2.readFile)(projectPath(VERITY_MD_FILE), "utf-8");
10868
- const boldLine = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
10869
- if (boldLine) {
10870
- const urlMatch = boldLine.match(/https:\/\/[^\s]+/);
10871
- if (urlMatch) return urlMatch[0];
10872
- }
10873
- const plainLine = content.split("\n").find((l) => /(?:url|service)\s*:/i.test(l));
10874
- if (plainLine) {
10875
- const urlMatch = plainLine.match(/https:\/\/[^\s]+/);
10876
- if (urlMatch) return urlMatch[0];
10877
- }
10771
+ return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10878
10772
  } catch {
10773
+ return "";
10879
10774
  }
10880
- return null;
10881
10775
  }
10882
- async function resolveServiceUrlDetailed(flagUrl) {
10883
- if (flagUrl) {
10884
- return { ok: true, data: { url: flagUrl, source: "flag" } };
10776
+ function splitLines(s) {
10777
+ return s.split("\n").filter((l) => l.length > 0);
10778
+ }
10779
+ var SHA_RE = /^[0-9a-f]{40}$/;
10780
+ function readBaselineSha() {
10781
+ if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
10782
+ let sha;
10783
+ try {
10784
+ sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
10785
+ } catch {
10786
+ return null;
10885
10787
  }
10886
- const envUrl = process.env.VERITY_SERVICE_URL;
10887
- if (envUrl) {
10888
- return { ok: true, data: { url: envUrl, source: "env" } };
10788
+ if (!SHA_RE.test(sha)) return null;
10789
+ const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
10790
+ if (!reachable) {
10791
+ try {
10792
+ (0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
10793
+ } catch {
10794
+ }
10795
+ return null;
10889
10796
  }
10890
- const credsUrl = await serviceUrlFromCredentials();
10891
- if (credsUrl) {
10892
- return { ok: true, data: { url: credsUrl, source: "credentials" } };
10797
+ return sha;
10798
+ }
10799
+ function writeBaselineSha(sha) {
10800
+ if (!SHA_RE.test(sha)) return;
10801
+ try {
10802
+ (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(BASELINE_SHA_FILE), { recursive: true });
10803
+ (0, import_node_fs3.writeFileSync)(BASELINE_SHA_FILE, sha);
10804
+ } catch {
10893
10805
  }
10894
- const mdUrl = await serviceUrlFromVerityMd();
10895
- if (mdUrl) {
10896
- return { ok: true, data: { url: mdUrl, source: "verity_md" } };
10806
+ }
10807
+ function getChangedFiles() {
10808
+ const sets = /* @__PURE__ */ new Set();
10809
+ let hasRecentCommitFiles = false;
10810
+ for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
10811
+ for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
10812
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
10813
+ const baseline = readBaselineSha();
10814
+ if (baseline) {
10815
+ const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
10816
+ if (committed.length > 0) {
10817
+ hasRecentCommitFiles = true;
10818
+ for (const f of committed) sets.add(f);
10819
+ }
10820
+ } else {
10821
+ const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
10822
+ const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
10823
+ const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
10824
+ const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
10825
+ if (commitAge < 120 && !hasUnstaged && !hasStaged) {
10826
+ const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
10827
+ if (recentFiles.length > 0) {
10828
+ hasRecentCommitFiles = true;
10829
+ for (const f of recentFiles) sets.add(f);
10830
+ }
10831
+ }
10897
10832
  }
10898
- return { ok: false, error: "No Verity service URL found. Run /verity-setup to configure." };
10833
+ const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10834
+ return { files: filtered, hasRecentCommitFiles };
10899
10835
  }
10900
- async function resolveServiceUrl(flagUrl) {
10901
- const result = await resolveServiceUrlDetailed(flagUrl);
10902
- return result.ok ? { ok: true, data: result.data.url } : result;
10836
+ function getStagedFiles() {
10837
+ return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10903
10838
  }
10904
- function isHealCandidate(resolved) {
10905
- return (resolved.source === "credentials" || resolved.source === "verity_md") && resolved.url !== DEFAULT_SERVICE_URL;
10839
+ function getDirtyFiles() {
10840
+ const set = /* @__PURE__ */ new Set();
10841
+ for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
10842
+ for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
10843
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
10844
+ return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10906
10845
  }
10907
-
10908
- // src/lib/auth.ts
10909
- async function resolveToken(flagToken) {
10910
- if (flagToken) {
10911
- return { ok: true, data: { token: flagToken, source: "flag" } };
10912
- }
10913
- const envToken = process.env.VERITY_TOKEN;
10914
- if (envToken) {
10915
- return { ok: true, data: { token: envToken, source: "env" } };
10846
+ function showContentAtRef(ref, repoRelPath) {
10847
+ if (!ref || ref === "no-git") return null;
10848
+ const normalizedPath = repoRelPath.replace(/\\/g, "/");
10849
+ try {
10850
+ return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
10851
+ encoding: "utf-8",
10852
+ maxBuffer: 64 * 1024 * 1024,
10853
+ stdio: ["pipe", "pipe", "pipe"]
10854
+ });
10855
+ } catch {
10856
+ return null;
10916
10857
  }
10917
- const rec = await readGlobalCredential(currentRemote());
10918
- if (rec) {
10919
- return {
10920
- ok: true,
10921
- data: { token: rec.token, source: "global", userId: rec.userId, email: rec.email }
10922
- };
10858
+ }
10859
+ function getPushRangeFiles() {
10860
+ const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10861
+ const resolvers = [
10862
+ () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
10863
+ () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
10864
+ () => {
10865
+ const branch = execGit("git rev-parse --abbrev-ref HEAD");
10866
+ return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
10867
+ }
10868
+ ];
10869
+ for (const resolve2 of resolvers) {
10870
+ const range = resolve2();
10871
+ if (range) return { files: diff(range), range };
10923
10872
  }
10924
- const local = await readLegacyLocalCredential();
10925
- if (local) {
10926
- return {
10927
- ok: true,
10928
- data: { token: local.token, source: "local", userId: local.userId, email: local.email }
10929
- };
10873
+ const baseline = readBaselineSha();
10874
+ if (baseline) {
10875
+ const files = diff(`${baseline}..HEAD`);
10876
+ if (files.length > 0) return { files, range: `${baseline}..HEAD` };
10930
10877
  }
10931
- return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
10878
+ const last = diff("HEAD~1..HEAD");
10879
+ return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
10932
10880
  }
10933
- async function whoami(token, serviceUrl, verbose) {
10934
- return apiRequest({
10935
- method: "GET",
10936
- path: "/auth/whoami",
10937
- serviceUrl,
10938
- token,
10939
- verbose,
10940
- cmd: "whoami"
10881
+ function getPushRangeMessages() {
10882
+ const { range } = getPushRangeFiles();
10883
+ if (!range) return "";
10884
+ return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
10885
+ }
10886
+ function filterAnalyzable(files) {
10887
+ return files.filter((f) => {
10888
+ const ext = (0, import_node_path3.extname)(f).slice(1);
10889
+ return ANALYZABLE_EXTENSIONS.has(ext);
10941
10890
  });
10942
10891
  }
10943
- async function probeService(serviceUrl, verbose) {
10944
- const res = await apiRequest({
10945
- method: "GET",
10946
- path: "/auth/whoami",
10947
- serviceUrl,
10948
- verbose,
10949
- timeout: 5e3,
10950
- cmd: "probe"
10892
+ function filterReviewable(files) {
10893
+ return files.filter((f) => {
10894
+ const ext = (0, import_node_path3.extname)(f).slice(1);
10895
+ if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
10896
+ if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
10897
+ const basename4 = f.split("/").pop() ?? "";
10898
+ if (REVIEWABLE_FILENAMES.has(basename4)) return true;
10899
+ if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
10900
+ return false;
10951
10901
  });
10952
- if (res.ok || res.http_status != null) return { reachable: true };
10953
- return { reachable: false, dnsDead: /\bENOTFOUND\b/.test(res.error), error: res.error };
10954
10902
  }
10955
- async function maybeHealServiceUrl(resolution, verbose) {
10956
- if (!isHealCandidate(resolution)) {
10957
- return { serviceUrl: resolution.url, healed: false };
10958
- }
10959
- const probe = await probeService(resolution.url, verbose);
10960
- if (probe.reachable) {
10961
- return { serviceUrl: resolution.url, healed: false };
10962
- }
10963
- const from = resolution.source === "credentials" ? "~/.verity/credentials" : "VERITY.md";
10964
- printWarn(`Your configured Verity service URL is unreachable: ${resolution.url}`);
10965
- printInfo(` (${probe.error})`);
10966
- if (probe.dnsDead && (await probeService(DEFAULT_SERVICE_URL, verbose)).reachable) {
10967
- printInfo(` The hostname no longer exists \u2014 the URL in ${from} is stale (e.g. a retired preview backend).`);
10968
- printInfo(` Falling back to the default Verity service: ${DEFAULT_SERVICE_URL}`);
10969
- if (resolution.source === "verity_md") {
10970
- printWarn(` Note: VERITY.md still contains the stale URL \u2014 update it to ${DEFAULT_SERVICE_URL} and commit.`);
10971
- }
10972
- return { serviceUrl: DEFAULT_SERVICE_URL, healed: true };
10973
- }
10974
- printInfo(" Continuing against the configured URL. If it is stale, log in against the default with:");
10975
- printInfo(` VERITY_SERVICE_URL=${DEFAULT_SERVICE_URL} verity login`);
10976
- return { serviceUrl: resolution.url, healed: false };
10903
+ function filterSecurity(files) {
10904
+ return files.filter(
10905
+ (f) => SECURITY_PATTERNS.some((p) => p.test(f))
10906
+ );
10977
10907
  }
10978
-
10979
- // src/lib/register.ts
10980
- var readline = __toESM(require("node:readline/promises"));
10981
-
10982
- // src/lib/provider-auth.ts
10983
- var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
10984
- var form = (fields) => new URLSearchParams(fields).toString();
10985
- async function githubAccountId(owner) {
10908
+ function getCurrentCommit() {
10909
+ return execGit("git rev-parse HEAD") || "no-git";
10910
+ }
10911
+ function getCurrentBranch() {
10912
+ const b = execGit("git rev-parse --abbrev-ref HEAD");
10913
+ return !b || b === "HEAD" ? null : b;
10914
+ }
10915
+ function commitResolves(sha) {
10916
+ if (!sha) return false;
10986
10917
  try {
10987
- const res = await fetch(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
10988
- headers: {
10989
- Accept: "application/vnd.github+json",
10990
- "X-GitHub-Api-Version": "2022-11-28",
10991
- "User-Agent": "verity-cli"
10992
- }
10918
+ (0, import_node_child_process3.execSync)(`git merge-base --is-ancestor ${JSON.stringify(sha)} HEAD`, {
10919
+ stdio: ["pipe", "pipe", "pipe"]
10993
10920
  });
10994
- if (!res.ok) return null;
10995
- const body = await res.json();
10996
- return typeof body.id === "number" ? body.id : null;
10921
+ return true;
10997
10922
  } catch {
10998
- return null;
10923
+ return false;
10999
10924
  }
11000
10925
  }
11001
- async function githubCanSeeRepo(owner, repo, token) {
11002
- let res;
10926
+ function commitsSincePaths(sha, paths) {
10927
+ if (!sha) return null;
11003
10928
  try {
11004
- res = await fetch(
11005
- `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
11006
- {
11007
- headers: {
11008
- Authorization: `Bearer ${token}`,
11009
- Accept: "application/vnd.github+json",
11010
- "X-GitHub-Api-Version": "2022-11-28",
11011
- "User-Agent": "verity-cli"
11012
- }
11013
- }
11014
- );
11015
- } catch (err) {
11016
- return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
10929
+ const scope = paths.length > 0 ? ` -- ${paths.slice(0, 50).map((p) => JSON.stringify(p)).join(" ")}` : "";
10930
+ const out = (0, import_node_child_process3.execSync)(`git rev-list --count ${JSON.stringify(sha)}..HEAD${scope}`, {
10931
+ encoding: "utf-8",
10932
+ stdio: ["pipe", "pipe", "pipe"]
10933
+ }).trim();
10934
+ const n = Number.parseInt(out, 10);
10935
+ return Number.isFinite(n) ? n : null;
10936
+ } catch {
10937
+ return null;
11017
10938
  }
11018
- if (res.status === 404) return { ok: true, data: false };
11019
- if (res.ok) return { ok: true, data: true };
11020
- return { ok: false, error: `GitHub repo lookup failed (HTTP ${res.status})` };
11021
10939
  }
11022
- async function githubDeviceFlow() {
11023
- const override = process.env.VERITY_PROVIDER_TOKEN;
11024
- if (override) return { ok: true, data: override };
11025
- let dc;
11026
- try {
11027
- const res = await fetch(GITHUB_DEVICE_CODE_URL, {
11028
- method: "POST",
11029
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
11030
- body: form({ client_id: GITHUB_CLIENT_ID })
11031
- });
11032
- if (!res.ok) {
11033
- return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
11034
- }
11035
- dc = await res.json();
11036
- } catch (err) {
11037
- return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
11038
- }
11039
- if (!dc.device_code || !dc.user_code) {
11040
- return {
11041
- ok: false,
11042
- error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
11043
- };
11044
- }
11045
- printInfo("");
11046
- printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
11047
- printInfo(`And enter the code: ${dc.user_code}`);
11048
- printInfo("Waiting for authorization\u2026");
11049
- const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
11050
- let interval = dc.interval || 5;
11051
- while (Date.now() < deadline) {
11052
- await sleep(interval * 1e3);
11053
- let data;
11054
- try {
11055
- const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
11056
- method: "POST",
11057
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
11058
- body: form({
11059
- client_id: GITHUB_CLIENT_ID,
11060
- device_code: dc.device_code,
11061
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
11062
- })
11063
- });
11064
- data = await res.json().catch(() => ({}));
11065
- } catch {
11066
- continue;
11067
- }
11068
- if (data.access_token) return { ok: true, data: data.access_token };
11069
- switch (data.error) {
11070
- case "authorization_pending":
11071
- break;
11072
- case "slow_down":
11073
- interval += 5;
11074
- break;
11075
- case "access_denied":
11076
- return { ok: false, error: "Authorization was denied on GitHub." };
11077
- case "expired_token":
11078
- return { ok: false, error: "The authorization code expired. Re-run register." };
11079
- default:
11080
- if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
11081
- }
10940
+ function detectProvider(host) {
10941
+ const h = host.toLowerCase();
10942
+ if (h.includes("github")) return "github";
10943
+ if (h.includes("gitlab")) return "gitlab";
10944
+ if (h.includes("bitbucket")) return "bitbucket";
10945
+ return "unknown";
10946
+ }
10947
+ function parseRemote(raw) {
10948
+ if (!raw || typeof raw !== "string") return null;
10949
+ let s = raw.trim();
10950
+ if (!s) return null;
10951
+ let host = "";
10952
+ let path = "";
10953
+ const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
10954
+ if (scp) {
10955
+ host = scp[1];
10956
+ path = scp[2];
10957
+ } else {
10958
+ s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
10959
+ s = s.replace(/^[^/@]+@/, "");
10960
+ const slash = s.indexOf("/");
10961
+ if (slash === -1) return null;
10962
+ host = s.slice(0, slash);
10963
+ path = s.slice(slash + 1);
11082
10964
  }
11083
- return { ok: false, error: "Timed out waiting for GitHub authorization." };
10965
+ host = host.toLowerCase().trim();
10966
+ path = path.replace(/\/+$/, "").replace(/\.git$/, "");
10967
+ if (!host || !path) return null;
10968
+ const segments = path.split("/").filter(Boolean);
10969
+ if (segments.length < 2) return null;
10970
+ const owner = segments[0];
10971
+ const repo = segments[segments.length - 1];
10972
+ if (!owner || !repo) return null;
10973
+ return {
10974
+ host,
10975
+ owner,
10976
+ repo,
10977
+ provider: detectProvider(host),
10978
+ orgUrl: `https://${host}/${owner}`,
10979
+ orgName: owner
10980
+ };
11084
10981
  }
11085
-
11086
- // src/lib/git.ts
11087
- var import_node_child_process3 = require("node:child_process");
11088
- var import_node_fs3 = require("node:fs");
11089
- var import_node_path3 = require("node:path");
11090
- function resolveFile(relpath) {
11091
- return (0, import_node_fs3.existsSync)(relpath) ? relpath : null;
10982
+ function listTrackedFiles() {
10983
+ const set = /* @__PURE__ */ new Set();
10984
+ for (const f of splitLines(execGit("git ls-files"))) set.add(f);
10985
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
10986
+ return Array.from(set);
11092
10987
  }
11093
- function execGit(cmd) {
11094
- try {
11095
- return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
11096
- } catch {
11097
- return "";
11098
- }
10988
+ function sanitizeRemote(remote) {
10989
+ return remote.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/]*@/, "$1");
11099
10990
  }
11100
- function splitLines(s) {
11101
- return s.split("\n").filter((l) => l.length > 0);
10991
+
10992
+ // src/lib/api-client.ts
10993
+ var cachedRemote = null;
10994
+ function requestRemote() {
10995
+ const override = process.env.VERITY_REMOTE_OVERRIDE;
10996
+ if (override) return sanitizeRemote(override.trim());
10997
+ if (cachedRemote === null) cachedRemote = sanitizeRemote(currentRemote());
10998
+ return cachedRemote;
11102
10999
  }
11103
- var SHA_RE = /^[0-9a-f]{40}$/;
11104
- function readBaselineSha() {
11105
- if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
11106
- let sha;
11107
- try {
11108
- sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
11109
- } catch {
11110
- return null;
11000
+ function describeFetchError(err, url) {
11001
+ const message = err instanceof Error ? err.message : String(err);
11002
+ const cause = err?.cause;
11003
+ const causeBits = [cause?.code, cause?.hostname].filter(Boolean).join(" ");
11004
+ const detail = causeBits || (cause?.message && cause.message !== message ? cause.message : "");
11005
+ return `${message}${detail ? ` (${detail})` : ""} \u2014 could not reach ${url}`;
11006
+ }
11007
+ async function apiRequest(options) {
11008
+ const {
11009
+ method,
11010
+ path,
11011
+ serviceUrl,
11012
+ token,
11013
+ body,
11014
+ verbose,
11015
+ timeout = 9e4,
11016
+ cmd = "unknown",
11017
+ retry = false,
11018
+ encodeBody = false,
11019
+ extraHeaders
11020
+ } = options;
11021
+ const url = `${serviceUrl}${path}`;
11022
+ const headers = {
11023
+ "Content-Type": "application/json"
11024
+ };
11025
+ if (token) {
11026
+ headers["Authorization"] = `Bearer ${token}`;
11111
11027
  }
11112
- if (!SHA_RE.test(sha)) return null;
11113
- const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
11114
- if (!reachable) {
11115
- try {
11116
- (0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
11117
- } catch {
11028
+ const remote = requestRemote();
11029
+ if (remote) {
11030
+ headers["X-Verity-Remote"] = remote;
11031
+ }
11032
+ if (extraHeaders) {
11033
+ Object.assign(headers, extraHeaders);
11034
+ }
11035
+ const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
11036
+ if (testMockScenario) {
11037
+ headers["X-Verity-Mock-Scenario"] = testMockScenario;
11038
+ }
11039
+ const testMockFailure = process.env.VERITY_TEST_MOCK_FAILURE;
11040
+ if (testMockFailure) {
11041
+ headers["X-Verity-Mock-Failure"] = testMockFailure;
11042
+ }
11043
+ printVerbose(`${method} ${url}`, verbose);
11044
+ let serializedBody;
11045
+ if (body !== void 0 && body !== null) {
11046
+ const innerJson = JSON.stringify(body);
11047
+ if (encodeBody) {
11048
+ const payload = Buffer.from(innerJson, "utf8").toString("base64");
11049
+ serializedBody = JSON.stringify({ encoding: "base64", payload });
11050
+ } else {
11051
+ serializedBody = innerJson;
11118
11052
  }
11119
- return null;
11053
+ printVerbose(`Body: ${serializedBody.slice(0, 500)}`, verbose);
11120
11054
  }
11121
- return sha;
11122
- }
11123
- function writeBaselineSha(sha) {
11124
- if (!SHA_RE.test(sha)) return;
11055
+ const startedAt = Date.now();
11056
+ const bodyBytes = serializedBody ? Buffer.byteLength(serializedBody) : 0;
11057
+ const logBase = { cmd, method, url, body_bytes: bodyBytes, retry, encoded: encodeBody };
11058
+ let response;
11125
11059
  try {
11126
- (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(BASELINE_SHA_FILE), { recursive: true });
11127
- (0, import_node_fs3.writeFileSync)(BASELINE_SHA_FILE, sha);
11060
+ response = await fetch(url, {
11061
+ method,
11062
+ headers,
11063
+ body: serializedBody,
11064
+ signal: AbortSignal.timeout(timeout)
11065
+ });
11066
+ } catch (err) {
11067
+ const duration2 = Date.now() - startedAt;
11068
+ const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
11069
+ const category = isTimeout ? "timeout" : "network";
11070
+ const error = isTimeout ? `Request timed out after ${timeout}ms (${url})` : `Network error: ${describeFetchError(err, url)}`;
11071
+ logHttpCall({ ...logBase, duration_ms: duration2, http_status: null, category, error });
11072
+ return { ok: false, error, category, http_status: null };
11073
+ }
11074
+ let data;
11075
+ try {
11076
+ data = response.status === 204 || response.headers.get("content-length") === "0" ? {} : await response.json();
11128
11077
  } catch {
11078
+ const duration2 = Date.now() - startedAt;
11079
+ const error = `Invalid JSON response (HTTP ${response.status})`;
11080
+ logHttpCall({
11081
+ ...logBase,
11082
+ duration_ms: duration2,
11083
+ http_status: response.status,
11084
+ category: "invalid_json",
11085
+ error
11086
+ });
11087
+ return { ok: false, error, category: "invalid_json", http_status: response.status };
11129
11088
  }
11130
- }
11131
- function getChangedFiles() {
11132
- const sets = /* @__PURE__ */ new Set();
11133
- let hasRecentCommitFiles = false;
11134
- for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
11135
- for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
11136
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
11137
- const baseline = readBaselineSha();
11138
- if (baseline) {
11139
- const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
11140
- if (committed.length > 0) {
11141
- hasRecentCommitFiles = true;
11142
- for (const f of committed) sets.add(f);
11143
- }
11144
- } else {
11145
- const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
11146
- const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
11147
- const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
11148
- const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
11149
- if (commitAge < 120 && !hasUnstaged && !hasStaged) {
11150
- const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
11151
- if (recentFiles.length > 0) {
11152
- hasRecentCommitFiles = true;
11153
- for (const f of recentFiles) sets.add(f);
11154
- }
11155
- }
11089
+ printVerbose(`Response ${response.status}: ${JSON.stringify(data).slice(0, 500)}`, verbose);
11090
+ const duration = Date.now() - startedAt;
11091
+ if (!response.ok) {
11092
+ const apiErr = data;
11093
+ const code = apiErr?.error?.code ?? "UNKNOWN";
11094
+ const message = apiErr?.error?.message ?? `HTTP ${response.status}`;
11095
+ const category = response.status >= 500 ? "http_5xx" : "http_4xx";
11096
+ const error = `${code}: ${message}`;
11097
+ logHttpCall({
11098
+ ...logBase,
11099
+ duration_ms: duration,
11100
+ http_status: response.status,
11101
+ category,
11102
+ error
11103
+ });
11104
+ return { ok: false, error, category, http_status: response.status };
11156
11105
  }
11157
- const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
11158
- return { files: filtered, hasRecentCommitFiles };
11106
+ logHttpCall({
11107
+ ...logBase,
11108
+ duration_ms: duration,
11109
+ http_status: response.status,
11110
+ category: "ok"
11111
+ });
11112
+ return { ok: true, data };
11159
11113
  }
11160
- function getStagedFiles() {
11161
- return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
11114
+ function analyzeRequest(options) {
11115
+ return apiRequest({
11116
+ method: "POST",
11117
+ path: "/analyze",
11118
+ serviceUrl: options.serviceUrl,
11119
+ token: options.token,
11120
+ body: options.body,
11121
+ timeout: options.timeout,
11122
+ cmd: options.cmd,
11123
+ verbose: options.verbose,
11124
+ retry: options.retry,
11125
+ encodeBody: true
11126
+ });
11162
11127
  }
11163
- function getDirtyFiles() {
11164
- const set = /* @__PURE__ */ new Set();
11165
- for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
11166
- for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
11167
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
11168
- return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
11128
+
11129
+ // src/lib/service-url.ts
11130
+ var import_promises2 = require("node:fs/promises");
11131
+ async function serviceUrlFromCredentials() {
11132
+ const rec = await readGlobalCredential(currentRemote());
11133
+ return rec?.serviceUrl ?? null;
11169
11134
  }
11170
- function showContentAtRef(ref, repoRelPath) {
11171
- if (!ref || ref === "no-git") return null;
11172
- const normalizedPath = repoRelPath.replace(/\\/g, "/");
11135
+ async function serviceUrlFromVerityMd() {
11173
11136
  try {
11174
- return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
11175
- encoding: "utf-8",
11176
- maxBuffer: 64 * 1024 * 1024,
11177
- stdio: ["pipe", "pipe", "pipe"]
11178
- });
11137
+ const content = await (0, import_promises2.readFile)(projectPath(VERITY_MD_FILE), "utf-8");
11138
+ const boldLine = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
11139
+ if (boldLine) {
11140
+ const urlMatch = boldLine.match(/https:\/\/[^\s]+/);
11141
+ if (urlMatch) return urlMatch[0];
11142
+ }
11143
+ const plainLine = content.split("\n").find((l) => /(?:url|service)\s*:/i.test(l));
11144
+ if (plainLine) {
11145
+ const urlMatch = plainLine.match(/https:\/\/[^\s]+/);
11146
+ if (urlMatch) return urlMatch[0];
11147
+ }
11179
11148
  } catch {
11180
- return null;
11181
11149
  }
11150
+ return null;
11182
11151
  }
11183
- function getPushRangeFiles() {
11184
- const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
11185
- const resolvers = [
11186
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
11187
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
11188
- () => {
11189
- const branch = execGit("git rev-parse --abbrev-ref HEAD");
11190
- return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
11191
- }
11192
- ];
11193
- for (const resolve2 of resolvers) {
11194
- const range = resolve2();
11195
- if (range) return { files: diff(range), range };
11152
+ async function resolveServiceUrlDetailed(flagUrl) {
11153
+ if (flagUrl) {
11154
+ return { ok: true, data: { url: flagUrl, source: "flag" } };
11196
11155
  }
11197
- const baseline = readBaselineSha();
11198
- if (baseline) {
11199
- const files = diff(`${baseline}..HEAD`);
11200
- if (files.length > 0) return { files, range: `${baseline}..HEAD` };
11156
+ const envUrl = process.env.VERITY_SERVICE_URL;
11157
+ if (envUrl) {
11158
+ return { ok: true, data: { url: envUrl, source: "env" } };
11201
11159
  }
11202
- const last = diff("HEAD~1..HEAD");
11203
- return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
11160
+ const credsUrl = await serviceUrlFromCredentials();
11161
+ if (credsUrl) {
11162
+ return { ok: true, data: { url: credsUrl, source: "credentials" } };
11163
+ }
11164
+ const mdUrl = await serviceUrlFromVerityMd();
11165
+ if (mdUrl) {
11166
+ return { ok: true, data: { url: mdUrl, source: "verity_md" } };
11167
+ }
11168
+ return { ok: false, error: "No Verity service URL found. Run /verity-setup to configure." };
11204
11169
  }
11205
- function getPushRangeMessages() {
11206
- const { range } = getPushRangeFiles();
11207
- if (!range) return "";
11208
- return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
11170
+ async function resolveServiceUrl(flagUrl) {
11171
+ const result = await resolveServiceUrlDetailed(flagUrl);
11172
+ return result.ok ? { ok: true, data: result.data.url } : result;
11209
11173
  }
11210
- function filterAnalyzable(files) {
11211
- return files.filter((f) => {
11212
- const ext = (0, import_node_path3.extname)(f).slice(1);
11213
- return ANALYZABLE_EXTENSIONS.has(ext);
11214
- });
11174
+ function isHealCandidate(resolved) {
11175
+ return (resolved.source === "credentials" || resolved.source === "verity_md") && resolved.url !== DEFAULT_SERVICE_URL;
11215
11176
  }
11216
- function filterReviewable(files) {
11217
- return files.filter((f) => {
11218
- const ext = (0, import_node_path3.extname)(f).slice(1);
11219
- if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
11220
- if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
11221
- const basename5 = f.split("/").pop() ?? "";
11222
- if (REVIEWABLE_FILENAMES.has(basename5)) return true;
11223
- if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
11224
- return false;
11177
+
11178
+ // src/lib/auth.ts
11179
+ async function resolveToken(flagToken) {
11180
+ if (flagToken) {
11181
+ return { ok: true, data: { token: flagToken, source: "flag" } };
11182
+ }
11183
+ const envToken = process.env.VERITY_TOKEN;
11184
+ if (envToken) {
11185
+ return { ok: true, data: { token: envToken, source: "env" } };
11186
+ }
11187
+ const rec = await readGlobalCredential(currentRemote());
11188
+ if (rec) {
11189
+ return {
11190
+ ok: true,
11191
+ data: { token: rec.token, source: "global", userId: rec.userId, email: rec.email }
11192
+ };
11193
+ }
11194
+ const local = await readLegacyLocalCredential();
11195
+ if (local) {
11196
+ return {
11197
+ ok: true,
11198
+ data: { token: local.token, source: "local", userId: local.userId, email: local.email }
11199
+ };
11200
+ }
11201
+ return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
11202
+ }
11203
+ async function whoami(token, serviceUrl, verbose) {
11204
+ return apiRequest({
11205
+ method: "GET",
11206
+ path: "/auth/whoami",
11207
+ serviceUrl,
11208
+ token,
11209
+ verbose,
11210
+ cmd: "whoami"
11225
11211
  });
11226
11212
  }
11227
- function filterSecurity(files) {
11228
- return files.filter(
11229
- (f) => SECURITY_PATTERNS.some((p) => p.test(f))
11230
- );
11213
+ function reverifyNudge(who) {
11214
+ if (who.grant_status === "hard_stale") {
11215
+ return 'Your GitHub verification has expired \u2014 run "verity login" to restore saved runs and memory.';
11216
+ }
11217
+ if (who.grant_status === "soft_stale") {
11218
+ const by = who.reverify_by ? ` by ${who.reverify_by.slice(0, 10)}` : " soon";
11219
+ return `Your GitHub verification needs a refresh${by} \u2014 run "verity login" to re-verify.`;
11220
+ }
11221
+ return null;
11231
11222
  }
11232
- function getCurrentCommit() {
11233
- return execGit("git rev-parse HEAD") || "no-git";
11223
+ function authDenialRemedy(error) {
11224
+ if (error.startsWith("STALE_VERIFICATION")) {
11225
+ return {
11226
+ code: "STALE_VERIFICATION",
11227
+ remedy: 'Your GitHub verification has expired \u2014 run "verity login" to re-verify your repository access.'
11228
+ };
11229
+ }
11230
+ if (error.startsWith("FORBIDDEN")) {
11231
+ return {
11232
+ code: "FORBIDDEN",
11233
+ remedy: 'No access grant for this repository \u2014 run "verity login" to refresh your grants (a repo granted after your last login needs one), or get write access to it.'
11234
+ };
11235
+ }
11236
+ return null;
11234
11237
  }
11235
- function getCurrentBranch() {
11236
- const b = execGit("git rev-parse --abbrev-ref HEAD");
11237
- return !b || b === "HEAD" ? null : b;
11238
+ async function probeService(serviceUrl, verbose) {
11239
+ const res = await apiRequest({
11240
+ method: "GET",
11241
+ path: "/auth/whoami",
11242
+ serviceUrl,
11243
+ verbose,
11244
+ timeout: 5e3,
11245
+ cmd: "probe"
11246
+ });
11247
+ if (res.ok || res.http_status != null) return { reachable: true };
11248
+ return { reachable: false, dnsDead: /\bENOTFOUND\b/.test(res.error), error: res.error };
11238
11249
  }
11239
- function commitResolves(sha) {
11240
- if (!sha) return false;
11241
- try {
11242
- (0, import_node_child_process3.execSync)(`git merge-base --is-ancestor ${JSON.stringify(sha)} HEAD`, {
11243
- stdio: ["pipe", "pipe", "pipe"]
11244
- });
11245
- return true;
11246
- } catch {
11247
- return false;
11250
+ async function maybeHealServiceUrl(resolution, verbose) {
11251
+ if (!isHealCandidate(resolution)) {
11252
+ return { serviceUrl: resolution.url, healed: false };
11253
+ }
11254
+ const probe = await probeService(resolution.url, verbose);
11255
+ if (probe.reachable) {
11256
+ return { serviceUrl: resolution.url, healed: false };
11257
+ }
11258
+ const from = resolution.source === "credentials" ? "~/.verity/credentials" : "VERITY.md";
11259
+ printWarn(`Your configured Verity service URL is unreachable: ${resolution.url}`);
11260
+ printInfo(` (${probe.error})`);
11261
+ if (probe.dnsDead && (await probeService(DEFAULT_SERVICE_URL, verbose)).reachable) {
11262
+ printInfo(` The hostname no longer exists \u2014 the URL in ${from} is stale (e.g. a retired preview backend).`);
11263
+ printInfo(` Falling back to the default Verity service: ${DEFAULT_SERVICE_URL}`);
11264
+ if (resolution.source === "verity_md") {
11265
+ printWarn(` Note: VERITY.md still contains the stale URL \u2014 update it to ${DEFAULT_SERVICE_URL} and commit.`);
11266
+ }
11267
+ return { serviceUrl: DEFAULT_SERVICE_URL, healed: true };
11248
11268
  }
11269
+ printInfo(" Continuing against the configured URL. If it is stale, log in against the default with:");
11270
+ printInfo(` VERITY_SERVICE_URL=${DEFAULT_SERVICE_URL} verity login`);
11271
+ return { serviceUrl: resolution.url, healed: false };
11249
11272
  }
11250
- function commitsSincePaths(sha, paths) {
11251
- if (!sha) return null;
11273
+
11274
+ // src/lib/register.ts
11275
+ var readline = __toESM(require("node:readline/promises"));
11276
+
11277
+ // src/lib/provider-auth.ts
11278
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
11279
+ var form = (fields) => new URLSearchParams(fields).toString();
11280
+ async function githubAccountId(owner) {
11252
11281
  try {
11253
- const scope = paths.length > 0 ? ` -- ${paths.slice(0, 50).map((p) => JSON.stringify(p)).join(" ")}` : "";
11254
- const out = (0, import_node_child_process3.execSync)(`git rev-list --count ${JSON.stringify(sha)}..HEAD${scope}`, {
11255
- encoding: "utf-8",
11256
- stdio: ["pipe", "pipe", "pipe"]
11257
- }).trim();
11258
- const n = Number.parseInt(out, 10);
11259
- return Number.isFinite(n) ? n : null;
11282
+ const res = await fetch(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
11283
+ headers: {
11284
+ Accept: "application/vnd.github+json",
11285
+ "X-GitHub-Api-Version": "2022-11-28",
11286
+ "User-Agent": "verity-cli"
11287
+ }
11288
+ });
11289
+ if (!res.ok) return null;
11290
+ const body = await res.json();
11291
+ return typeof body.id === "number" ? body.id : null;
11260
11292
  } catch {
11261
11293
  return null;
11262
11294
  }
11263
11295
  }
11264
- function detectProvider(host) {
11265
- const h = host.toLowerCase();
11266
- if (h.includes("github")) return "github";
11267
- if (h.includes("gitlab")) return "gitlab";
11268
- if (h.includes("bitbucket")) return "bitbucket";
11269
- return "unknown";
11270
- }
11271
- function parseRemote(raw) {
11272
- if (!raw || typeof raw !== "string") return null;
11273
- let s = raw.trim();
11274
- if (!s) return null;
11275
- let host = "";
11276
- let path = "";
11277
- const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
11278
- if (scp) {
11279
- host = scp[1];
11280
- path = scp[2];
11281
- } else {
11282
- s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
11283
- s = s.replace(/^[^/@]+@/, "");
11284
- const slash = s.indexOf("/");
11285
- if (slash === -1) return null;
11286
- host = s.slice(0, slash);
11287
- path = s.slice(slash + 1);
11288
- }
11289
- host = host.toLowerCase().trim();
11290
- path = path.replace(/\/+$/, "").replace(/\.git$/, "");
11291
- if (!host || !path) return null;
11292
- const segments = path.split("/").filter(Boolean);
11293
- if (segments.length < 2) return null;
11294
- const owner = segments[0];
11295
- const repo = segments[segments.length - 1];
11296
- if (!owner || !repo) return null;
11297
- return {
11298
- host,
11299
- owner,
11300
- repo,
11301
- provider: detectProvider(host),
11302
- orgUrl: `https://${host}/${owner}`,
11303
- orgName: owner
11304
- };
11296
+ async function githubCanSeeRepo(owner, repo, token) {
11297
+ let res;
11298
+ try {
11299
+ res = await fetch(
11300
+ `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
11301
+ {
11302
+ headers: {
11303
+ Authorization: `Bearer ${token}`,
11304
+ Accept: "application/vnd.github+json",
11305
+ "X-GitHub-Api-Version": "2022-11-28",
11306
+ "User-Agent": "verity-cli"
11307
+ }
11308
+ }
11309
+ );
11310
+ } catch (err) {
11311
+ return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
11312
+ }
11313
+ if (res.status === 404) return { ok: true, data: false };
11314
+ if (res.ok) return { ok: true, data: true };
11315
+ return { ok: false, error: `GitHub repo lookup failed (HTTP ${res.status})` };
11305
11316
  }
11306
- function listTrackedFiles() {
11307
- const set = /* @__PURE__ */ new Set();
11308
- for (const f of splitLines(execGit("git ls-files"))) set.add(f);
11309
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
11310
- return Array.from(set);
11317
+ async function githubDeviceFlow() {
11318
+ const override = process.env.VERITY_PROVIDER_TOKEN;
11319
+ if (override) return { ok: true, data: override };
11320
+ let dc;
11321
+ try {
11322
+ const res = await fetch(GITHUB_DEVICE_CODE_URL, {
11323
+ method: "POST",
11324
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
11325
+ body: form({ client_id: GITHUB_CLIENT_ID })
11326
+ });
11327
+ if (!res.ok) {
11328
+ return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
11329
+ }
11330
+ dc = await res.json();
11331
+ } catch (err) {
11332
+ return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
11333
+ }
11334
+ if (!dc.device_code || !dc.user_code) {
11335
+ return {
11336
+ ok: false,
11337
+ error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
11338
+ };
11339
+ }
11340
+ printInfo("");
11341
+ printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
11342
+ printInfo(`And enter the code: ${dc.user_code}`);
11343
+ printInfo("Waiting for authorization\u2026");
11344
+ const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
11345
+ let interval = dc.interval || 5;
11346
+ while (Date.now() < deadline) {
11347
+ await sleep(interval * 1e3);
11348
+ let data;
11349
+ try {
11350
+ const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
11351
+ method: "POST",
11352
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
11353
+ body: form({
11354
+ client_id: GITHUB_CLIENT_ID,
11355
+ device_code: dc.device_code,
11356
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
11357
+ })
11358
+ });
11359
+ data = await res.json().catch(() => ({}));
11360
+ } catch {
11361
+ continue;
11362
+ }
11363
+ if (data.access_token) return { ok: true, data: data.access_token };
11364
+ switch (data.error) {
11365
+ case "authorization_pending":
11366
+ break;
11367
+ case "slow_down":
11368
+ interval += 5;
11369
+ break;
11370
+ case "access_denied":
11371
+ return { ok: false, error: "Authorization was denied on GitHub." };
11372
+ case "expired_token":
11373
+ return { ok: false, error: "The authorization code expired. Re-run register." };
11374
+ default:
11375
+ if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
11376
+ }
11377
+ }
11378
+ return { ok: false, error: "Timed out waiting for GitHub authorization." };
11311
11379
  }
11312
11380
 
11313
11381
  // src/lib/register.ts
@@ -11392,6 +11460,60 @@ async function registerProject(opts) {
11392
11460
  }
11393
11461
  return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email, userId } };
11394
11462
  }
11463
+ async function loginOnce(opts) {
11464
+ const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
11465
+ const providerAuth = await githubDeviceFlow();
11466
+ if (!providerAuth.ok) {
11467
+ return { ok: false, error: providerAuth.error };
11468
+ }
11469
+ const providerToken = providerAuth.data;
11470
+ const parsed = opts.remote ? parseRemote(opts.remote) : null;
11471
+ if (!usingTokenOverride && parsed && parsed.provider === "github") {
11472
+ const installed = await ensureAppInstalled(parsed.owner, parsed.repo, providerToken);
11473
+ if (!installed.ok) {
11474
+ printWarn(installed.error);
11475
+ printInfo("Continuing login \u2014 repositories on other accounts are still granted.");
11476
+ }
11477
+ }
11478
+ const result = await apiRequest({
11479
+ method: "POST",
11480
+ path: "/auth/login",
11481
+ serviceUrl: opts.serviceUrl,
11482
+ extraHeaders: { "X-Provider-Token": providerToken },
11483
+ verbose: opts.verbose,
11484
+ cmd: "login"
11485
+ });
11486
+ if (!result.ok) {
11487
+ return { ok: false, error: result.error };
11488
+ }
11489
+ const { token, service_url, user_id, user, repo_count } = result.data;
11490
+ const loginUserId = user_id ?? user?.id ?? void 0;
11491
+ try {
11492
+ await upsertGlobalCredential("", {
11493
+ token,
11494
+ serviceUrl: service_url,
11495
+ userId: loginUserId,
11496
+ email: user?.email
11497
+ });
11498
+ } catch (err) {
11499
+ return {
11500
+ ok: false,
11501
+ error: `Logged in with the Verity service, but could not save credentials to ~/.verity/credentials: ${err.message}. Check filesystem permissions and re-run "verity login".`
11502
+ };
11503
+ }
11504
+ const pruned = await removeSupersededUserCredentials(service_url, loginUserId);
11505
+ return {
11506
+ ok: true,
11507
+ data: {
11508
+ token,
11509
+ serviceUrl: service_url,
11510
+ email: user?.email,
11511
+ userId: loginUserId,
11512
+ repoCount: repo_count ?? 0,
11513
+ prunedCredentials: pruned
11514
+ }
11515
+ };
11516
+ }
11395
11517
 
11396
11518
  // src/commands/auth.ts
11397
11519
  function registerAuthCommands(program2) {
@@ -11481,10 +11603,8 @@ function registerAuthCommands(program2) {
11481
11603
  }
11482
11604
 
11483
11605
  // src/commands/login.ts
11484
- var import_node_child_process5 = require("node:child_process");
11485
- var import_node_path4 = require("node:path");
11486
11606
  function registerLoginCommand(program2) {
11487
- 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) => {
11607
+ program2.command("login").description("Log in to Verity (one GitHub login grants access to all your repositories)").option("--force", "Re-authenticate even if already logged in").action(async (opts) => {
11488
11608
  const globals = program2.opts();
11489
11609
  const urlResult = await resolveServiceUrlDetailed(globals.serviceUrl);
11490
11610
  if (!urlResult.ok) {
@@ -11494,60 +11614,208 @@ function registerLoginCommand(program2) {
11494
11614
  const heal = await maybeHealServiceUrl(urlResult.data, globals.verbose);
11495
11615
  const serviceUrl = heal.serviceUrl;
11496
11616
  if (heal.healed) {
11497
- printInfo(" Completing login re-registers this project and updates ~/.verity/credentials.");
11617
+ printInfo(" Completing login updates ~/.verity/credentials against the live service.");
11618
+ }
11619
+ const remote = currentRemote();
11620
+ if (remote && !parseRemote(remote)) {
11621
+ printWarn(`This repository's origin remote is not a recognizable git URL: ${remote}`);
11622
+ printInfo(" Verity cannot identify the repository from it, so runs here will not be saved.");
11623
+ printInfo(" Point origin at the full URL (e.g. git@github.com:owner/repo.git) to fix it.");
11498
11624
  }
11499
11625
  const existing = await resolveToken(globals.token);
11500
11626
  if (existing.ok && !opts.force && !heal.healed) {
11501
- if (existing.data.userId != null) {
11502
- printInfo(`Already logged in as ${existing.data.email ?? `user #${existing.data.userId}`}. \u2713`);
11503
- printInfo(" Re-authenticate with: verity login --force");
11504
- return;
11505
- }
11506
- const who2 = await whoami(existing.data.token, serviceUrl, globals.verbose);
11507
- if (who2.ok && who2.data.logged_in) {
11508
- printInfo(`Already logged in as ${who2.data.email ?? `user #${who2.data.user_id}`}. \u2713`);
11509
- printInfo(" Re-authenticate with: verity login --force");
11510
- return;
11511
- }
11512
- if (who2.ok && who2.data.anonymous) {
11627
+ const who = await whoami(existing.data.token, serviceUrl, globals.verbose);
11628
+ if (who.ok && who.data.logged_in) {
11629
+ const nudge = reverifyNudge(who.data);
11630
+ if (!nudge) {
11631
+ printInfo(`Already logged in as ${who.data.email ?? `user #${who.data.user_id}`}. \u2713`);
11632
+ printInfo(" Re-authenticate with: verity login --force");
11633
+ return;
11634
+ }
11635
+ printWarn(nudge);
11636
+ printInfo("Re-verifying your repository access\u2026");
11637
+ } else if (who.ok && who.data.anonymous) {
11513
11638
  printInfo("You have an anonymous token (the gate runs, but nothing is saved). Logging you in\u2026");
11514
- } else if (!who2.ok) {
11639
+ } else if (!who.ok) {
11515
11640
  printWarn("Could not confirm your current login state with the service \u2014 proceeding to log in.");
11516
11641
  }
11517
11642
  }
11518
- let remote = "";
11519
- try {
11520
- remote = (0, import_node_child_process5.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
11521
- } catch {
11643
+ printInfo("Authenticating with GitHub\u2026");
11644
+ const result = await loginOnce({ serviceUrl, remote: remote || void 0, verbose: globals.verbose });
11645
+ if (!result.ok) {
11646
+ printError(`Login failed: ${result.error}`);
11647
+ process.exit(1);
11522
11648
  }
11523
- if (!remote) {
11524
- printError("verity login needs a git remote (origin) to link this repository.");
11525
- printInfo(" Add one with: git remote add origin <url>, then re-run verity login.");
11649
+ const out = result.data;
11650
+ const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
11651
+ printInfo(`Logged in as ${identity}. \u2713`);
11652
+ printInfo(` Access granted to ${out.repoCount} ${out.repoCount === 1 ? "repository" : "repositories"}.`);
11653
+ printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
11654
+ if (out.prunedCredentials > 0) {
11655
+ printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, globals.verbose);
11656
+ } else if (out.prunedCredentials < 0) {
11657
+ printWarn(" Could not rewrite ~/.verity/credentials: superseded per-repository tokens remain and");
11658
+ printWarn(" will keep taking precedence over this login in their own repositories.");
11659
+ printInfo(` Check the file's permissions; the next successful "verity login" retries the cleanup.`);
11660
+ }
11661
+ if (out.repoCount === 0) {
11662
+ printWarn("The Verity GitHub App is not installed on any account you can access.");
11663
+ printInfo(` Install it (and grant your repositories), then re-run verity login:`);
11664
+ printInfo(` ${githubAppInstallUrl(null)}`);
11665
+ return;
11666
+ }
11667
+ if (remote) {
11668
+ const who = await whoami(out.token, out.serviceUrl, globals.verbose);
11669
+ if (who.ok && who.data.grant_status != null) {
11670
+ printInfo(" \u2713 This repository is covered.");
11671
+ } else if (!who.ok) {
11672
+ printWarn(` Could not confirm this repository's coverage (${who.error}) \u2014 verity status will show it.`);
11673
+ } else {
11674
+ const parsed = parseRemote(remote);
11675
+ const installUrl = githubAppInstallUrl(parsed ? await githubAccountId(parsed.owner) : null);
11676
+ printWarn(` This repository (${parsed ? `${parsed.owner}/${parsed.repo}` : remote}) is NOT covered by your grants.`);
11677
+ printInfo(" Grant the Verity GitHub App access to it, then re-run verity login:");
11678
+ printInfo(` ${installUrl}`);
11679
+ }
11680
+ const rec = await readGlobalCredential(remote);
11681
+ if (rec && rec.token !== out.token) {
11682
+ const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
11683
+ if (otherBackend) {
11684
+ printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
11685
+ printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
11686
+ printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
11687
+ } else {
11688
+ const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
11689
+ printWarn(` Note: this repository has a ${kind} credential that takes`);
11690
+ printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
11691
+ }
11692
+ }
11693
+ }
11694
+ });
11695
+ }
11696
+
11697
+ // src/commands/token.ts
11698
+ function registerTokenCommand(program2) {
11699
+ const token = program2.command("token").description("Manage service (CI/machine) tokens for this repository");
11700
+ async function requireAuth(globals) {
11701
+ const tokenResult = await resolveToken(globals.token);
11702
+ if (!tokenResult.ok) {
11703
+ printError(tokenResult.error);
11526
11704
  process.exit(1);
11527
11705
  }
11528
- const projectName = parseRemote(remote)?.repo ?? (0, import_node_path4.basename)(process.cwd());
11529
- printInfo("Authenticating with GitHub\u2026");
11530
- const result = await registerProject({ projectName, remote, serviceUrl, verbose: globals.verbose });
11706
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
11707
+ if (!urlResult.ok) {
11708
+ printError(urlResult.error);
11709
+ process.exit(1);
11710
+ }
11711
+ return { bearer: tokenResult.data.token, serviceUrl: urlResult.data };
11712
+ }
11713
+ function explainDenial(error) {
11714
+ if (error.startsWith("STALE_VERIFICATION")) {
11715
+ printInfo(' Your GitHub verification has expired \u2014 run "verity login", then retry.');
11716
+ } else if (error.startsWith("FORBIDDEN")) {
11717
+ printInfo(" You need write access to this repository. If it was added recently,");
11718
+ printInfo(' run "verity login" to refresh your grants.');
11719
+ } else if (error.startsWith("INVALID_REQUEST")) {
11720
+ printInfo(" Tokens are managed per repository \u2014 run this inside a repository with a");
11721
+ printInfo(" git remote (origin), so Verity knows which project the token belongs to.");
11722
+ }
11723
+ }
11724
+ token.command("create").description("Mint a service token for CI (store it in your CI secret store as VERITY_TOKEN)").requiredOption("--name <name>", "Label for the token (e.g. github-actions)").option("--expires <days>", "Optional expiry in days (default: never)").action(async (opts) => {
11725
+ const globals = program2.opts();
11726
+ const { bearer, serviceUrl } = await requireAuth(globals);
11727
+ let expiresInDays;
11728
+ if (opts.expires != null) {
11729
+ expiresInDays = Number(opts.expires);
11730
+ if (!Number.isInteger(expiresInDays) || expiresInDays <= 0 || expiresInDays > 3650) {
11731
+ printError("--expires must be a whole number of days between 1 and 3650");
11732
+ process.exit(1);
11733
+ }
11734
+ }
11735
+ const result = await apiRequest({
11736
+ method: "POST",
11737
+ path: "/auth/tokens",
11738
+ serviceUrl,
11739
+ token: bearer,
11740
+ body: {
11741
+ agent_name: String(opts.name).trim(),
11742
+ token_type: "service",
11743
+ ...expiresInDays != null ? { expires_in_days: expiresInDays } : {}
11744
+ },
11745
+ verbose: globals.verbose,
11746
+ cmd: "token-create"
11747
+ });
11531
11748
  if (!result.ok) {
11532
- printError(`Login failed: ${result.error}`);
11749
+ printError(`Could not create the service token: ${result.error}`);
11750
+ explainDenial(result.error);
11533
11751
  process.exit(1);
11534
11752
  }
11535
- const who = result.data.email ?? (result.data.userId != null ? `user #${result.data.userId}` : "your account");
11536
- printInfo(`Logged in as ${who}. \u2713`);
11537
- printInfo(" Runs, history, and cloud memory now sync to Verity for this project.");
11538
- if (result.data.userId == null) {
11539
- printWarn(" (Server did not return a user id \u2014 update the CLI if this persists.)");
11753
+ printInfo(`Service token "${result.data.agent_name}" created. \u2713`);
11754
+ printInfo("");
11755
+ printInfo(` ${result.data.token}`);
11756
+ printInfo("");
11757
+ printWarn("This token is shown ONCE \u2014 store it now (e.g. as a VERITY_TOKEN CI secret).");
11758
+ if (result.data.expires_at) {
11759
+ printInfo(` Expires: ${result.data.expires_at.slice(0, 10)}`);
11540
11760
  }
11761
+ printInfo(" Any current writer of this repository can revoke it: verity token revoke <id>");
11762
+ printInfo(` Token id: ${result.data.token_id}`);
11763
+ });
11764
+ token.command("list").description("List this repository's tokens (ids and metadata only \u2014 never secrets)").action(async () => {
11765
+ const globals = program2.opts();
11766
+ const { bearer, serviceUrl } = await requireAuth(globals);
11767
+ const result = await apiRequest({
11768
+ method: "GET",
11769
+ path: "/auth/tokens",
11770
+ serviceUrl,
11771
+ token: bearer,
11772
+ verbose: globals.verbose,
11773
+ cmd: "token-list"
11774
+ });
11775
+ if (!result.ok) {
11776
+ printError(`Could not list tokens: ${result.error}`);
11777
+ explainDenial(result.error);
11778
+ process.exit(1);
11779
+ }
11780
+ const tokens = result.data.tokens ?? [];
11781
+ if (tokens.length === 0) {
11782
+ printInfo("No tokens found for this repository.");
11783
+ return;
11784
+ }
11785
+ for (const t of tokens) {
11786
+ const type = t.token_type ?? "user";
11787
+ const expires = t.expires_at ? `expires ${t.expires_at.slice(0, 10)}` : "no expiry";
11788
+ const lastUsed = t.last_used_at ? `last used ${t.last_used_at.slice(0, 10)}` : "never used";
11789
+ printInfo(`${t.id} [${type}] ${t.agent_name ?? "unnamed"} (${expires}, ${lastUsed})`);
11790
+ }
11791
+ });
11792
+ token.command("revoke <id>").description("Revoke a token by id (service tokens: any current writer may revoke)").action(async (id) => {
11793
+ const globals = program2.opts();
11794
+ const { bearer, serviceUrl } = await requireAuth(globals);
11795
+ const result = await apiRequest({
11796
+ method: "DELETE",
11797
+ path: `/auth/tokens/${encodeURIComponent(id)}`,
11798
+ serviceUrl,
11799
+ token: bearer,
11800
+ verbose: globals.verbose,
11801
+ cmd: "token-revoke"
11802
+ });
11803
+ if (!result.ok) {
11804
+ printError(`Could not revoke the token: ${result.error}`);
11805
+ explainDenial(result.error);
11806
+ process.exit(1);
11807
+ }
11808
+ printInfo(`Token ${result.data.token_id} revoked. \u2713`);
11541
11809
  });
11542
11810
  }
11543
11811
 
11544
11812
  // src/lib/hooks.ts
11545
11813
  var import_promises4 = require("node:fs/promises");
11546
- var import_node_path6 = require("node:path");
11814
+ var import_node_path5 = require("node:path");
11547
11815
 
11548
11816
  // src/lib/json-file.ts
11549
11817
  var import_promises3 = require("node:fs/promises");
11550
- var import_node_path5 = require("node:path");
11818
+ var import_node_path4 = require("node:path");
11551
11819
  function jsonSemanticEqual(a, b) {
11552
11820
  if (a === b) return true;
11553
11821
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
@@ -11593,7 +11861,7 @@ async function writeJsonFilePreservingStyle(file, value) {
11593
11861
  const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
11594
11862
  const next = JSON.stringify(value, null, indent) + "\n";
11595
11863
  if (next === currentRaw) return false;
11596
- await (0, import_promises3.mkdir)((0, import_node_path5.dirname)(file), { recursive: true });
11864
+ await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
11597
11865
  await (0, import_promises3.writeFile)(file, next);
11598
11866
  return true;
11599
11867
  }
@@ -11766,13 +12034,13 @@ async function writeSettings(settings) {
11766
12034
  }
11767
12035
  async function readSettingsAt(root) {
11768
12036
  try {
11769
- return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path6.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
12037
+ return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11770
12038
  } catch {
11771
12039
  return {};
11772
12040
  }
11773
12041
  }
11774
12042
  async function writeSettingsAt(root, settings) {
11775
- await writeJsonFilePreservingStyle((0, import_node_path6.join)(root, CLAUDE_SETTINGS_FILE), settings);
12043
+ await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
11776
12044
  }
11777
12045
  async function hasLegacyHooksAt(root) {
11778
12046
  const settings = await readSettingsAt(root);
@@ -12045,7 +12313,7 @@ var import_node_crypto6 = require("node:crypto");
12045
12313
  // src/lib/conversation-buffer.ts
12046
12314
  var import_promises5 = require("node:fs/promises");
12047
12315
  var import_node_fs4 = require("node:fs");
12048
- var import_node_child_process6 = require("node:child_process");
12316
+ var import_node_child_process5 = require("node:child_process");
12049
12317
  var import_node_crypto = require("node:crypto");
12050
12318
  function stripImageReferences(text) {
12051
12319
  return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
@@ -12148,7 +12416,7 @@ async function readBufferEntries() {
12148
12416
  }
12149
12417
  function getRecentCommitMessages() {
12150
12418
  try {
12151
- const output = (0, import_node_child_process6.execSync)(
12419
+ const output = (0, import_node_child_process5.execSync)(
12152
12420
  'git log --since="30 minutes ago" --format="%s" -5',
12153
12421
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
12154
12422
  ).trim();
@@ -12163,7 +12431,7 @@ function getRecentCommitMessages() {
12163
12431
  var import_node_crypto2 = require("node:crypto");
12164
12432
  var import_node_fs5 = require("node:fs");
12165
12433
  var import_node_os = require("node:os");
12166
- var import_node_path7 = require("node:path");
12434
+ var import_node_path6 = require("node:path");
12167
12435
  var SHARED_SENTINELS = /* @__PURE__ */ new Set([
12168
12436
  "",
12169
12437
  "-",
@@ -12217,13 +12485,13 @@ function contextIdentity(input) {
12217
12485
  }
12218
12486
  function verityHome() {
12219
12487
  const override = process.env.VERITY_HOME;
12220
- return override && override.trim() ? (0, import_node_path7.resolve)(override) : (0, import_node_path7.join)((0, import_node_os.homedir)(), ".verity");
12488
+ return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0, import_node_os.homedir)(), ".verity");
12221
12489
  }
12222
12490
  function dossierDir(identity) {
12223
- return (0, import_node_path7.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
12491
+ return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
12224
12492
  }
12225
12493
  function treeDir(identity) {
12226
- return (0, import_node_path7.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
12494
+ return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
12227
12495
  }
12228
12496
  function scopeIdentity(token, sessionId) {
12229
12497
  const t = (token ?? "").trim();
@@ -12239,7 +12507,7 @@ function sessionScopeKey(token, sessionId) {
12239
12507
  // src/lib/task-context-buffer.ts
12240
12508
  var import_promises6 = require("node:fs/promises");
12241
12509
  var import_node_fs6 = require("node:fs");
12242
- var import_node_path8 = require("node:path");
12510
+ var import_node_path7 = require("node:path");
12243
12511
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
12244
12512
  var MAX_BUFFER_BYTES = 500 * 1024;
12245
12513
  var MAX_PROMPT_CHARS = 2e3;
@@ -12317,7 +12585,7 @@ async function cleanupTaskContextBuffers() {
12317
12585
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
12318
12586
  for (const file of files) {
12319
12587
  if (!file.endsWith(".jsonl")) continue;
12320
- const filePath = (0, import_node_path8.join)(TASK_CONTEXT_DIR, file);
12588
+ const filePath = (0, import_node_path7.join)(TASK_CONTEXT_DIR, file);
12321
12589
  try {
12322
12590
  const stats = await (0, import_promises6.stat)(filePath);
12323
12591
  if (stats.mtimeMs < cutoffMs) {
@@ -12331,7 +12599,7 @@ async function cleanupTaskContextBuffers() {
12331
12599
  }
12332
12600
  function bufferPath(taskId) {
12333
12601
  const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
12334
- return (0, import_node_path8.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
12602
+ return (0, import_node_path7.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
12335
12603
  }
12336
12604
  async function appendEntry(taskId, entry) {
12337
12605
  try {
@@ -12357,7 +12625,7 @@ async function appendEntry(taskId, entry) {
12357
12625
  // src/lib/memory-retrieval.ts
12358
12626
  var import_promises7 = require("node:fs/promises");
12359
12627
  var import_node_fs7 = require("node:fs");
12360
- var import_node_path9 = require("node:path");
12628
+ var import_node_path8 = require("node:path");
12361
12629
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
12362
12630
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
12363
12631
  var DEFAULT_BUDGET_TOKENS = 2e3;
@@ -12455,14 +12723,14 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
12455
12723
  const promptTokens = tokenize(promptText);
12456
12724
  const nodes = [];
12457
12725
  for (const domain of DOMAINS) {
12458
- const domainDir = (0, import_node_path9.join)(memoryDir(), domain);
12726
+ const domainDir = (0, import_node_path8.join)(memoryDir(), domain);
12459
12727
  if (!(0, import_node_fs7.existsSync)(domainDir)) continue;
12460
12728
  try {
12461
12729
  const files = await (0, import_promises7.readdir)(domainDir);
12462
12730
  for (const file of files) {
12463
12731
  if (!file.endsWith(".md")) continue;
12464
12732
  try {
12465
- const content = await (0, import_promises7.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
12733
+ const content = await (0, import_promises7.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
12466
12734
  const { fm, body } = parseFrontmatter(content);
12467
12735
  if (fm.status && fm.status !== "active") continue;
12468
12736
  nodes.push({
@@ -12522,7 +12790,7 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
12522
12790
  // src/lib/memory-sync.ts
12523
12791
  var import_promises8 = require("node:fs/promises");
12524
12792
  var import_node_fs8 = require("node:fs");
12525
- var import_node_path10 = require("node:path");
12793
+ var import_node_path9 = require("node:path");
12526
12794
  var import_node_crypto3 = require("node:crypto");
12527
12795
 
12528
12796
  // src/lib/glob-match.ts
@@ -12593,16 +12861,16 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
12593
12861
  async function ensureMemoryDir() {
12594
12862
  await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
12595
12863
  for (const domain of DOMAINS2) {
12596
- await (0, import_promises8.mkdir)((0, import_node_path10.join)(memoryDir2(), domain), { recursive: true });
12864
+ await (0, import_promises8.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
12597
12865
  }
12598
- if (!(0, import_node_fs8.existsSync)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"))) {
12599
- await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12866
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"))) {
12867
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12600
12868
  }
12601
- if (!(0, import_node_fs8.existsSync)((0, import_node_path10.join)(memoryDir2(), "index.md"))) {
12602
- await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
12869
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "index.md"))) {
12870
+ 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");
12603
12871
  }
12604
- if (!(0, import_node_fs8.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md"))) {
12605
- await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12872
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md"))) {
12873
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12606
12874
  }
12607
12875
  }
12608
12876
  async function buildManifest() {
@@ -12611,14 +12879,14 @@ async function buildManifest() {
12611
12879
  }
12612
12880
  const nodes = [];
12613
12881
  for (const domain of DOMAINS2) {
12614
- const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
12882
+ const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12615
12883
  if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
12616
12884
  try {
12617
12885
  const files = await (0, import_promises8.readdir)(domainDir);
12618
12886
  for (const file of files) {
12619
12887
  if (!file.endsWith(".md")) continue;
12620
12888
  const filePath = `${domain}/${file}`;
12621
- const fullPath = (0, import_node_path10.join)(memoryDir2(), filePath);
12889
+ const fullPath = (0, import_node_path9.join)(memoryDir2(), filePath);
12622
12890
  try {
12623
12891
  const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
12624
12892
  const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
@@ -12631,13 +12899,13 @@ async function buildManifest() {
12631
12899
  }
12632
12900
  let indexHash = null;
12633
12901
  try {
12634
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "index.md"), "utf-8");
12902
+ const indexContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
12635
12903
  indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
12636
12904
  } catch {
12637
12905
  }
12638
12906
  let logLength = 0;
12639
12907
  try {
12640
- const logContent = await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "utf-8");
12908
+ const logContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
12641
12909
  logLength = logContent.split("\n").length;
12642
12910
  } catch {
12643
12911
  }
@@ -12650,13 +12918,13 @@ async function readOnDiskNodes() {
12650
12918
  const out = /* @__PURE__ */ new Map();
12651
12919
  if (!(0, import_node_fs8.existsSync)(memoryDir2())) return out;
12652
12920
  for (const domain of DOMAINS2) {
12653
- const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
12921
+ const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12654
12922
  if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
12655
12923
  try {
12656
12924
  for (const file of await (0, import_promises8.readdir)(domainDir)) {
12657
12925
  if (!file.endsWith(".md")) continue;
12658
12926
  try {
12659
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path10.join)(domainDir, file), "utf-8")));
12927
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
12660
12928
  } catch {
12661
12929
  }
12662
12930
  }
@@ -12702,7 +12970,7 @@ async function computeEditedNodeUploads() {
12702
12970
  const uploads = [];
12703
12971
  for (const [path, prevHash] of prev) {
12704
12972
  if (prevHash == null) continue;
12705
- const full = (0, import_node_path10.join)(memoryDir2(), path);
12973
+ const full = (0, import_node_path9.join)(memoryDir2(), path);
12706
12974
  if (!(0, import_node_fs8.existsSync)(full)) continue;
12707
12975
  let content;
12708
12976
  try {
@@ -12739,15 +13007,15 @@ async function applyMemoryWrites(writes, opts = {}) {
12739
13007
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
12740
13008
  for (const n of notes) logLines.push(` - ${n}`);
12741
13009
  try {
12742
- const existing = (0, import_node_fs8.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
12743
- await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
13010
+ const existing = (0, import_node_fs8.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";
13011
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
12744
13012
  } catch {
12745
13013
  }
12746
13014
  await recordSyncedNodePaths();
12747
13015
  return count;
12748
13016
  }
12749
13017
  async function applyOneWrite(write, treePaths) {
12750
- const fullPath = (0, import_node_path10.join)(memoryDir2(), write.path);
13018
+ const fullPath = (0, import_node_path9.join)(memoryDir2(), write.path);
12751
13019
  const notes = [];
12752
13020
  let content = write.content;
12753
13021
  if (treePaths && treePaths.length > 0) {
@@ -12769,7 +13037,7 @@ async function applyOneWrite(write, treePaths) {
12769
13037
  return { written: false, notes };
12770
13038
  }
12771
13039
  }
12772
- await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(fullPath), { recursive: true });
13040
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
12773
13041
  await (0, import_promises8.writeFile)(fullPath, content);
12774
13042
  return { written: true, notes };
12775
13043
  }
@@ -12810,7 +13078,7 @@ async function regenerateIndex() {
12810
13078
  ];
12811
13079
  let totalNodes = 0;
12812
13080
  for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
12813
- const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13081
+ const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12814
13082
  if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
12815
13083
  try {
12816
13084
  const files = await (0, import_promises8.readdir)(domainDir);
@@ -12821,7 +13089,7 @@ async function regenerateIndex() {
12821
13089
  for (const file of mdFiles.sort()) {
12822
13090
  const slug = file.replace(/\.md$/, "");
12823
13091
  try {
12824
- const content = await (0, import_promises8.readFile)((0, import_node_path10.join)(domainDir, file), "utf-8");
13092
+ const content = await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
12825
13093
  const title = pickFrontmatter(content, "title") ?? slug;
12826
13094
  const kind = pickFrontmatter(content, "kind") ?? "-";
12827
13095
  const confidence = pickFrontmatter(content, "confidence");
@@ -12845,7 +13113,7 @@ async function regenerateIndex() {
12845
13113
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
12846
13114
  }
12847
13115
  const next = lines.join("\n") + "\n";
12848
- const indexPath = (0, import_node_path10.join)(memoryDir2(), "index.md");
13116
+ const indexPath = (0, import_node_path9.join)(memoryDir2(), "index.md");
12849
13117
  let existing = null;
12850
13118
  try {
12851
13119
  existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
@@ -12927,7 +13195,7 @@ function hasLegacyMemoryBlock(text) {
12927
13195
  return findMarker(text, LEGACY_MD_START) !== -1;
12928
13196
  }
12929
13197
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
12930
- const claudeMdPath = (0, import_node_path10.join)(cwd, "CLAUDE.md");
13198
+ const claudeMdPath = (0, import_node_path9.join)(cwd, "CLAUDE.md");
12931
13199
  let existing = "";
12932
13200
  if ((0, import_node_fs8.existsSync)(claudeMdPath)) {
12933
13201
  existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
@@ -13061,12 +13329,12 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
13061
13329
  // src/lib/dossier-session.ts
13062
13330
  var import_node_fs10 = require("node:fs");
13063
13331
  var import_node_crypto5 = require("node:crypto");
13064
- var import_node_path12 = require("node:path");
13332
+ var import_node_path11 = require("node:path");
13065
13333
 
13066
13334
  // src/lib/dossier.ts
13067
13335
  var import_node_fs9 = require("node:fs");
13068
13336
  var import_node_crypto4 = require("node:crypto");
13069
- var import_node_path11 = require("node:path");
13337
+ var import_node_path10 = require("node:path");
13070
13338
  var MAX_LINE_BYTES = 4096;
13071
13339
  var MAX_GOAL_CHARS = 2e3;
13072
13340
  var GOAL_KEEP = 8;
@@ -13109,9 +13377,9 @@ function openDossier(identity) {
13109
13377
  return {
13110
13378
  dir,
13111
13379
  identity,
13112
- eventsPath: (0, import_node_path11.join)(dir, "events.jsonl"),
13113
- foldPath: (0, import_node_path11.join)(dir, "fold.json"),
13114
- rotatedDir: (0, import_node_path11.join)(dir, "rotated")
13380
+ eventsPath: (0, import_node_path10.join)(dir, "events.jsonl"),
13381
+ foldPath: (0, import_node_path10.join)(dir, "fold.json"),
13382
+ rotatedDir: (0, import_node_path10.join)(dir, "rotated")
13115
13383
  };
13116
13384
  } catch {
13117
13385
  return null;
@@ -13184,11 +13452,11 @@ function rotateIfNeeded2(d) {
13184
13452
  if (!(0, import_node_fs9.existsSync)(d.eventsPath)) return;
13185
13453
  if ((0, import_node_fs9.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
13186
13454
  (0, import_node_fs9.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
13187
- (0, import_node_fs9.renameSync)(d.eventsPath, (0, import_node_path11.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
13455
+ (0, import_node_fs9.renameSync)(d.eventsPath, (0, import_node_path10.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
13188
13456
  const kept = (0, import_node_fs9.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
13189
13457
  for (const stale of kept.slice(0, Math.max(0, kept.length - ROTATE_KEEP))) {
13190
13458
  try {
13191
- (0, import_node_fs9.renameSync)((0, import_node_path11.join)(d.rotatedDir, stale), (0, import_node_path11.join)(d.rotatedDir, `${stale}.pruned`));
13459
+ (0, import_node_fs9.renameSync)((0, import_node_path10.join)(d.rotatedDir, stale), (0, import_node_path10.join)(d.rotatedDir, `${stale}.pruned`));
13192
13460
  } catch {
13193
13461
  }
13194
13462
  }
@@ -13250,7 +13518,7 @@ function foldDossier(d, opts = {}) {
13250
13518
  state.meta.rotations = files.length;
13251
13519
  for (const f of files) {
13252
13520
  try {
13253
- ingest((0, import_node_fs9.readFileSync)((0, import_node_path11.join)(d.rotatedDir, f), "utf8"));
13521
+ ingest((0, import_node_fs9.readFileSync)((0, import_node_path10.join)(d.rotatedDir, f), "utf8"));
13254
13522
  } catch {
13255
13523
  state.meta.dropped_lines++;
13256
13524
  }
@@ -14046,16 +14314,16 @@ function foreignAuthoredPaths(identity, opts = {}) {
14046
14314
  for (const entry of (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true })) {
14047
14315
  if (!entry.isDirectory()) continue;
14048
14316
  if (entry.name === identity.sessionKey) continue;
14049
- const log = (0, import_node_path12.join)(dir, entry.name, "events.jsonl");
14317
+ const log = (0, import_node_path11.join)(dir, entry.name, "events.jsonl");
14050
14318
  try {
14051
14319
  if (!(0, import_node_fs10.existsSync)(log)) continue;
14052
14320
  if (now - (0, import_node_fs10.statSync)(log).mtimeMs > windowMs) continue;
14053
14321
  const sib = {
14054
- dir: (0, import_node_path12.join)(dir, entry.name),
14322
+ dir: (0, import_node_path11.join)(dir, entry.name),
14055
14323
  identity,
14056
14324
  eventsPath: log,
14057
- foldPath: (0, import_node_path12.join)(dir, entry.name, "fold.json"),
14058
- rotatedDir: (0, import_node_path12.join)(dir, entry.name, "rotated")
14325
+ foldPath: (0, import_node_path11.join)(dir, entry.name, "fold.json"),
14326
+ rotatedDir: (0, import_node_path11.join)(dir, entry.name, "rotated")
14059
14327
  };
14060
14328
  const state = readFoldCache(sib) ?? foldDossier(sib);
14061
14329
  sessions++;
@@ -14083,22 +14351,22 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
14083
14351
  let removed = 0;
14084
14352
  try {
14085
14353
  const mine = dossierDir(identity);
14086
- const userDir = (0, import_node_path12.dirname)((0, import_node_path12.dirname)(mine));
14354
+ const userDir = (0, import_node_path11.dirname)((0, import_node_path11.dirname)(mine));
14087
14355
  if (!(0, import_node_fs10.existsSync)(userDir)) return 0;
14088
14356
  const cutoff = Date.now() - maxAgeMs;
14089
14357
  for (const tree of (0, import_node_fs10.readdirSync)(userDir, { withFileTypes: true })) {
14090
14358
  if (!tree.isDirectory()) continue;
14091
- const treePath = (0, import_node_path12.join)(userDir, tree.name);
14359
+ const treePath = (0, import_node_path11.join)(userDir, tree.name);
14092
14360
  let live = 0;
14093
14361
  for (const entry of (0, import_node_fs10.readdirSync)(treePath, { withFileTypes: true })) {
14094
14362
  if (!entry.isDirectory()) continue;
14095
- const dir = (0, import_node_path12.join)(treePath, entry.name);
14363
+ const dir = (0, import_node_path11.join)(treePath, entry.name);
14096
14364
  if (dir === mine) {
14097
14365
  live++;
14098
14366
  continue;
14099
14367
  }
14100
14368
  try {
14101
- const log = (0, import_node_path12.join)(dir, "events.jsonl");
14369
+ const log = (0, import_node_path11.join)(dir, "events.jsonl");
14102
14370
  const at = (0, import_node_fs10.existsSync)(log) ? (0, import_node_fs10.statSync)(log).mtimeMs : (0, import_node_fs10.statSync)(dir).mtimeMs;
14103
14371
  if (at < cutoff) {
14104
14372
  (0, import_node_fs10.rmSync)(dir, { recursive: true, force: true });
@@ -14141,7 +14409,7 @@ function recordTurn(d, t) {
14141
14409
  for (const a of t.authored) {
14142
14410
  const origin = a.owner === "subagent" ? "subagent" : "edit_tool";
14143
14411
  const prior = t.known?.authored?.get(a.p);
14144
- const hash = fileHash((0, import_node_path12.join)(root, a.p));
14412
+ const hash = fileHash((0, import_node_path11.join)(root, a.p));
14145
14413
  const hunks = Math.max(0, a.h - (prior?.hunks ?? 0));
14146
14414
  const adds = Math.max(0, a.a - (prior?.adds ?? 0));
14147
14415
  const dels = Math.max(0, a.d - (prior?.dels ?? 0));
@@ -14174,7 +14442,7 @@ function recordTurn(d, t) {
14174
14442
  }
14175
14443
  const seenDivergence = t.known?.divergence ?? /* @__PURE__ */ new Set();
14176
14444
  for (const u of t.unobserved) {
14177
- const hash = fileHash((0, import_node_path12.join)(root, u.p));
14445
+ const hash = fileHash((0, import_node_path11.join)(root, u.p));
14178
14446
  if (seenDivergence.has(divergenceKey(u.p, hash))) continue;
14179
14447
  appendEvent(d, { k: "divergence", kind: "external_mutation", path: u.p, hash });
14180
14448
  }
@@ -14203,7 +14471,7 @@ function recordVerdict(d, v) {
14203
14471
  if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
14204
14472
  if (!lines.has(f.file)) {
14205
14473
  try {
14206
- const abs = (0, import_node_path12.join)(root, f.file);
14474
+ const abs = (0, import_node_path11.join)(root, f.file);
14207
14475
  lines.set(f.file, (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null);
14208
14476
  } catch {
14209
14477
  lines.set(f.file, null);
@@ -14278,7 +14546,7 @@ function recallMemory(d, identity, opts) {
14278
14546
  budgetBytes: opts.budgetBytes,
14279
14547
  readFileLines: (file) => {
14280
14548
  try {
14281
- const abs = (0, import_node_path12.join)(root, file);
14549
+ const abs = (0, import_node_path11.join)(root, file);
14282
14550
  return (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null;
14283
14551
  } catch {
14284
14552
  return null;
@@ -14418,21 +14686,21 @@ async function fireClassify(prompt, sessionId, globals) {
14418
14686
 
14419
14687
  // src/commands/lifecycle.ts
14420
14688
  var import_node_fs14 = require("node:fs");
14421
- var import_node_path16 = require("node:path");
14689
+ var import_node_path15 = require("node:path");
14422
14690
 
14423
14691
  // src/lib/baseline.ts
14424
14692
  var import_node_fs13 = require("node:fs");
14425
- var import_node_path15 = require("node:path");
14693
+ var import_node_path14 = require("node:path");
14426
14694
  var import_node_crypto7 = require("node:crypto");
14427
14695
 
14428
14696
  // src/lib/snapshot.ts
14429
14697
  var import_node_fs12 = require("node:fs");
14430
- var import_node_path14 = require("node:path");
14431
- var import_node_child_process7 = require("node:child_process");
14698
+ var import_node_path13 = require("node:path");
14699
+ var import_node_child_process6 = require("node:child_process");
14432
14700
 
14433
14701
  // src/lib/files.ts
14434
14702
  var import_node_fs11 = require("node:fs");
14435
- var import_node_path13 = require("node:path");
14703
+ var import_node_path12 = require("node:path");
14436
14704
  var LANG_MAP = {
14437
14705
  // Analyzable (static analysis + Gemini)
14438
14706
  ts: "typescript",
@@ -14500,7 +14768,7 @@ var LANG_MAP = {
14500
14768
  mk: "make"
14501
14769
  };
14502
14770
  function detectLanguage(filepath) {
14503
- const ext = (0, import_node_path13.extname)(filepath).slice(1);
14771
+ const ext = (0, import_node_path12.extname)(filepath).slice(1);
14504
14772
  return LANG_MAP[ext] ?? ext;
14505
14773
  }
14506
14774
  function sortByMtime(files) {
@@ -14588,7 +14856,7 @@ function generateSnapshotDiffs(files) {
14588
14856
  }
14589
14857
  const diffs = [];
14590
14858
  for (const file of files) {
14591
- const snapshotPath = (0, import_node_path14.join)(SNAPSHOT_DIR, file.path);
14859
+ const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
14592
14860
  const language = file.language ?? detectLanguage(file.path);
14593
14861
  if ((0, import_node_fs12.existsSync)(snapshotPath)) {
14594
14862
  const oldContent = (0, import_node_fs12.readFileSync)(snapshotPath, "utf-8");
@@ -14615,21 +14883,21 @@ ${addedLines}`,
14615
14883
  function saveSnapshots(files) {
14616
14884
  const snapshotPaths = /* @__PURE__ */ new Set();
14617
14885
  for (const file of files) {
14618
- const snapshotPath = (0, import_node_path14.join)(SNAPSHOT_DIR, file.path);
14886
+ const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
14619
14887
  snapshotPaths.add(snapshotPath);
14620
- (0, import_node_fs12.mkdirSync)((0, import_node_path14.dirname)(snapshotPath), { recursive: true });
14888
+ (0, import_node_fs12.mkdirSync)((0, import_node_path13.dirname)(snapshotPath), { recursive: true });
14621
14889
  (0, import_node_fs12.writeFileSync)(snapshotPath, file.content);
14622
14890
  }
14623
14891
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
14624
14892
  }
14625
14893
  function computeDiff(oldContent, newContent, filePath) {
14626
- const tmpOld = (0, import_node_path14.join)(SNAPSHOT_DIR, ".diff-old.tmp");
14627
- const tmpNew = (0, import_node_path14.join)(SNAPSHOT_DIR, ".diff-new.tmp");
14894
+ const tmpOld = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-old.tmp");
14895
+ const tmpNew = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-new.tmp");
14628
14896
  try {
14629
14897
  (0, import_node_fs12.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
14630
14898
  (0, import_node_fs12.writeFileSync)(tmpOld, oldContent);
14631
14899
  (0, import_node_fs12.writeFileSync)(tmpNew, newContent);
14632
- const result = (0, import_node_child_process7.execSync)(
14900
+ const result = (0, import_node_child_process6.execSync)(
14633
14901
  `git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
14634
14902
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
14635
14903
  );
@@ -14657,7 +14925,7 @@ function cleanStaleSnapshots(dir, keepSet) {
14657
14925
  const entries = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true });
14658
14926
  for (const entry of entries) {
14659
14927
  if (entry.name.startsWith(".")) continue;
14660
- const fullPath = (0, import_node_path14.join)(dir, entry.name);
14928
+ const fullPath = (0, import_node_path13.join)(dir, entry.name);
14661
14929
  if (entry.isDirectory()) {
14662
14930
  cleanStaleSnapshots(fullPath, keepSet);
14663
14931
  try {
@@ -14686,13 +14954,13 @@ function sessionKey(sessionId) {
14686
14954
  return (0, import_node_crypto7.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
14687
14955
  }
14688
14956
  function sessionDir(key) {
14689
- return (0, import_node_path15.join)(projectPath(BASELINE_DIR), key);
14957
+ return (0, import_node_path14.join)(projectPath(BASELINE_DIR), key);
14690
14958
  }
14691
14959
  function manifestPath(dir) {
14692
- return (0, import_node_path15.join)(dir, "manifest.json");
14960
+ return (0, import_node_path14.join)(dir, "manifest.json");
14693
14961
  }
14694
14962
  function mirrorPath(dir, repoRelPath) {
14695
- return (0, import_node_path15.join)(dir, "files", repoRelPath);
14963
+ return (0, import_node_path14.join)(dir, "files", repoRelPath);
14696
14964
  }
14697
14965
  var CARRY_FILE = `${BASELINE_DIR}/.carry`;
14698
14966
  var CARRY_WINDOW_MS = 12e4;
@@ -14754,7 +15022,7 @@ function captureBaseline(opts = {}) {
14754
15022
  (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
14755
15023
  } catch {
14756
15024
  }
14757
- const filesDir = (0, import_node_path15.join)(dir, "files");
15025
+ const filesDir = (0, import_node_path14.join)(dir, "files");
14758
15026
  const mirrored = [];
14759
15027
  try {
14760
15028
  (0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
@@ -14764,7 +15032,7 @@ function captureBaseline(opts = {}) {
14764
15032
  if (content === null) continue;
14765
15033
  const dest = mirrorPath(dir, p);
14766
15034
  try {
14767
- (0, import_node_fs13.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15035
+ (0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
14768
15036
  (0, import_node_fs13.writeFileSync)(dest, content);
14769
15037
  mirrored.push(p);
14770
15038
  } catch {
@@ -14892,7 +15160,7 @@ function pruneOldBaselines() {
14892
15160
  }
14893
15161
  const now = Date.now();
14894
15162
  for (const name of entries) {
14895
- const dir = (0, import_node_path15.join)(root, name);
15163
+ const dir = (0, import_node_path14.join)(root, name);
14896
15164
  const manifest = readManifest(dir);
14897
15165
  if (!manifest) {
14898
15166
  try {
@@ -15078,7 +15346,7 @@ function buildCompactionContext(session) {
15078
15346
  commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
15079
15347
  readFileLines: (file) => {
15080
15348
  try {
15081
- const abs = (0, import_node_path16.join)(root, file);
15349
+ const abs = (0, import_node_path15.join)(root, file);
15082
15350
  return (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null;
15083
15351
  } catch {
15084
15352
  return null;
@@ -15414,6 +15682,7 @@ function registerStatusCommand(program2) {
15414
15682
  }
15415
15683
  const token = tokenResult.data.token;
15416
15684
  const serviceUrl = urlResult.data;
15685
+ const who = await whoami(token, serviceUrl, globals.verbose);
15417
15686
  const memResult = await apiRequest({
15418
15687
  method: "GET",
15419
15688
  path: "/memory",
@@ -15421,14 +15690,21 @@ function registerStatusCommand(program2) {
15421
15690
  token,
15422
15691
  verbose: globals.verbose
15423
15692
  });
15424
- if (!memResult.ok) {
15693
+ const denial = memResult.ok ? null : authDenialRemedy(memResult.error);
15694
+ if (!memResult.ok && !denial) {
15425
15695
  printError(memResult.error);
15426
15696
  process.exit(1);
15427
15697
  }
15428
- const mem = memResult.data;
15698
+ const mem = memResult.ok ? memResult.data : null;
15429
15699
  if (opts.json) {
15430
- const output = { memory: mem };
15431
- if (opts.history) {
15700
+ const output = {
15701
+ auth: who.ok ? who.data : { error: who.error },
15702
+ memory: mem
15703
+ };
15704
+ if (denial && !memResult.ok) {
15705
+ output.error = { code: denial.code, message: memResult.error, remedy: denial.remedy };
15706
+ }
15707
+ if (opts.history && !denial) {
15432
15708
  const runsResult = await apiRequest({
15433
15709
  method: "GET",
15434
15710
  path: `/runs?limit=${opts.limit}`,
@@ -15443,23 +15719,30 @@ function registerStatusCommand(program2) {
15443
15719
  printJson(output);
15444
15720
  return;
15445
15721
  }
15446
- if (mem.configured === false) {
15722
+ if (mem?.configured === false) {
15447
15723
  printInfo("Verity is not configured for this project. Run /verity-setup.");
15448
15724
  return;
15449
15725
  }
15450
15726
  printInfo("=== Verity Status ===");
15451
- if (tokenResult.data.userId != null) {
15452
- printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} \u2713`);
15453
- } else {
15454
- const who = await whoami(token, serviceUrl, globals.verbose);
15455
- if (who.ok && who.data.logged_in) {
15456
- printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
15457
- } else if (who.ok && who.data.anonymous) {
15458
- printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
15459
- }
15460
- }
15461
- if (mem.project_name) printInfo(`Project: ${mem.project_name}`);
15462
- if (mem.standard) {
15727
+ if (who.ok && who.data.logged_in) {
15728
+ printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
15729
+ } else if (who.ok && who.data.anonymous) {
15730
+ printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
15731
+ } else if (tokenResult.data.userId != null) {
15732
+ printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} (cached \u2014 could not reach the Verity service) \u2713`);
15733
+ } else if (!who.ok) {
15734
+ printInfo(`Account: Unknown \u2014 could not reach the Verity service (${who.error})`);
15735
+ }
15736
+ if (who.ok) {
15737
+ const nudge = reverifyNudge(who.data);
15738
+ if (nudge) printWarn(` ${nudge}`);
15739
+ }
15740
+ if (denial) {
15741
+ printWarn(`Access: ${denial.remedy}`);
15742
+ printInfo(" Project status, history, and cloud memory stay unavailable until then.");
15743
+ }
15744
+ if (mem?.project_name) printInfo(`Project: ${mem.project_name}`);
15745
+ if (mem?.standard) {
15463
15746
  const s = mem.standard;
15464
15747
  printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
15465
15748
  printInfo(`Languages: ${s.languages.join(", ")}`);
@@ -15470,6 +15753,7 @@ function registerStatusCommand(program2) {
15470
15753
  if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
15471
15754
  if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
15472
15755
  printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
15756
+ if (!mem) return;
15473
15757
  if (mem.recent_runs) {
15474
15758
  const r = mem.recent_runs;
15475
15759
  printInfo("");
@@ -15621,7 +15905,7 @@ async function sendGeneralFeedback(message, opts, globals) {
15621
15905
 
15622
15906
  // src/commands/analyze.ts
15623
15907
  var import_node_fs24 = require("node:fs");
15624
- var import_node_path21 = require("node:path");
15908
+ var import_node_path20 = require("node:path");
15625
15909
 
15626
15910
  // src/lib/debounce.ts
15627
15911
  var import_node_fs15 = require("node:fs");
@@ -15758,7 +16042,7 @@ function writeIteration(iteration, commit, _contentHash) {
15758
16042
  }
15759
16043
 
15760
16044
  // src/lib/static-analysis.ts
15761
- var import_node_child_process8 = require("node:child_process");
16045
+ var import_node_child_process7 = require("node:child_process");
15762
16046
  var import_node_fs16 = require("node:fs");
15763
16047
  var SEVERITY_ORDER = {
15764
16048
  Error: 0,
@@ -15771,7 +16055,7 @@ var SEVERITY_ORDER = {
15771
16055
  };
15772
16056
  function isCodacyAvailable() {
15773
16057
  try {
15774
- (0, import_node_child_process8.execSync)("which codacy-analysis", { stdio: "pipe" });
16058
+ (0, import_node_child_process7.execSync)("which codacy-analysis", { stdio: "pipe" });
15775
16059
  return true;
15776
16060
  } catch {
15777
16061
  return false;
@@ -15795,7 +16079,7 @@ function runCodacyAnalysis(files) {
15795
16079
  const fileArgs = existingFiles.join(" ");
15796
16080
  let output;
15797
16081
  try {
15798
- output = (0, import_node_child_process8.execSync)(
16082
+ output = (0, import_node_child_process7.execSync)(
15799
16083
  `codacy-analysis analyze --install-dependencies --files ${fileArgs} --output-format json --log-level error --parallel-tools 3`,
15800
16084
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 }
15801
16085
  );
@@ -15849,7 +16133,7 @@ function runCodacyAnalysis(files) {
15849
16133
 
15850
16134
  // src/lib/specs.ts
15851
16135
  var import_node_fs17 = require("node:fs");
15852
- var import_node_path17 = require("node:path");
16136
+ var import_node_path16 = require("node:path");
15853
16137
  var SPEC_CANDIDATES = [
15854
16138
  "CLAUDE.md",
15855
16139
  "AGENTS.md",
@@ -15911,7 +16195,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
15911
16195
  try {
15912
16196
  const entries = (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true });
15913
16197
  for (const entry of entries) {
15914
- const fullPath = (0, import_node_path17.join)(dir, entry.name);
16198
+ const fullPath = (0, import_node_path16.join)(dir, entry.name);
15915
16199
  if (entry.isFile() && entry.name.endsWith(".md")) {
15916
16200
  result.push(fullPath);
15917
16201
  } else if (entry.isDirectory() && depth < maxDepth - 1) {
@@ -15923,7 +16207,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
15923
16207
  return result;
15924
16208
  }
15925
16209
  function discoverPlans() {
15926
- const homePlansDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".claude", "plans");
16210
+ const homePlansDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".claude", "plans");
15927
16211
  const localPlansDir = ".claude/plans";
15928
16212
  const candidates = [];
15929
16213
  const seen = /* @__PURE__ */ new Set();
@@ -15933,7 +16217,7 @@ function discoverPlans() {
15933
16217
  for (const f of (0, import_node_fs17.readdirSync)(plansDir)) {
15934
16218
  if (!f.endsWith(".md") || seen.has(f)) continue;
15935
16219
  seen.add(f);
15936
- const fullPath = (0, import_node_path17.join)(plansDir, f);
16220
+ const fullPath = (0, import_node_path16.join)(plansDir, f);
15937
16221
  try {
15938
16222
  const stat3 = (0, import_node_fs17.statSync)(fullPath);
15939
16223
  candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
@@ -15957,7 +16241,7 @@ function discoverPlans() {
15957
16241
  }
15958
16242
 
15959
16243
  // src/lib/task-context.ts
15960
- var import_node_child_process9 = require("node:child_process");
16244
+ var import_node_child_process8 = require("node:child_process");
15961
16245
  var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
15962
16246
  var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
15963
16247
  function parseLinkedIssue(sources) {
@@ -15973,7 +16257,7 @@ function parseLinkedIssue(sources) {
15973
16257
  }
15974
16258
  function safeExec(cmd, timeout) {
15975
16259
  try {
15976
- return (0, import_node_child_process9.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
16260
+ return (0, import_node_child_process8.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
15977
16261
  } catch {
15978
16262
  return "";
15979
16263
  }
@@ -16003,7 +16287,7 @@ function resolveTaskContext(opts) {
16003
16287
  // src/lib/cli-version.ts
16004
16288
  function cliVersion() {
16005
16289
  try {
16006
- return true ? "0.28.1-experimental.a57c8d9" : "dev";
16290
+ return true ? "0.28.1-experimental.dbd87b1" : "dev";
16007
16291
  } catch {
16008
16292
  return "dev";
16009
16293
  }
@@ -16207,7 +16491,7 @@ function truthy(v) {
16207
16491
 
16208
16492
  // src/lib/fold.ts
16209
16493
  var import_node_fs20 = require("node:fs");
16210
- var import_node_path18 = require("node:path");
16494
+ var import_node_path17 = require("node:path");
16211
16495
  var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
16212
16496
  "user",
16213
16497
  "assistant",
@@ -16391,7 +16675,7 @@ function fold(transcriptPath, opts = {}) {
16391
16675
  return result;
16392
16676
  }
16393
16677
  try {
16394
- const sidecarDir = (0, import_node_path18.join)((0, import_node_path18.dirname)(transcriptPath), "subagents");
16678
+ const sidecarDir = (0, import_node_path17.join)((0, import_node_path17.dirname)(transcriptPath), "subagents");
16395
16679
  if ((0, import_node_fs20.existsSync)(sidecarDir)) {
16396
16680
  const maxFiles = opts.maxSidecars ?? 200;
16397
16681
  const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
@@ -16399,7 +16683,7 @@ function fold(transcriptPath, opts = {}) {
16399
16683
  const walk = (d, depth) => {
16400
16684
  if (depth > 4) return;
16401
16685
  for (const e of (0, import_node_fs20.readdirSync)(d, { withFileTypes: true })) {
16402
- const p = (0, import_node_path18.join)(d, e.name);
16686
+ const p = (0, import_node_path17.join)(d, e.name);
16403
16687
  if (e.isDirectory()) {
16404
16688
  walk(p, depth + 1);
16405
16689
  } else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
@@ -16606,7 +16890,7 @@ function buildHookOutput(gateDecision, systemMessage, agentContext) {
16606
16890
 
16607
16891
  // src/lib/cache-cleanup.ts
16608
16892
  var import_node_fs21 = require("node:fs");
16609
- var import_node_path19 = require("node:path");
16893
+ var import_node_path18 = require("node:path");
16610
16894
  var CACHE_TTL_DAYS = 7;
16611
16895
  function pruneStaleCache() {
16612
16896
  try {
@@ -16614,7 +16898,7 @@ function pruneStaleCache() {
16614
16898
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
16615
16899
  for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
16616
16900
  if (!entry.startsWith("pending-")) continue;
16617
- const path = (0, import_node_path19.join)(dir, entry);
16901
+ const path = (0, import_node_path18.join)(dir, entry);
16618
16902
  try {
16619
16903
  const stat3 = (0, import_node_fs21.statSync)(path);
16620
16904
  if (stat3.mtimeMs < cutoff) {
@@ -17061,7 +17345,7 @@ function isExplicitlyAutonomous(env = process.env) {
17061
17345
  // src/lib/seed-runner.ts
17062
17346
  var import_promises11 = require("node:fs/promises");
17063
17347
  var import_node_fs23 = require("node:fs");
17064
- var import_node_path20 = require("node:path");
17348
+ var import_node_path19 = require("node:path");
17065
17349
  var import_yaml2 = __toESM(require_dist());
17066
17350
 
17067
17351
  // src/lib/seed.ts
@@ -17343,7 +17627,7 @@ async function runSeed(opts) {
17343
17627
  if (candidates.length === 0) {
17344
17628
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
17345
17629
  }
17346
- const overviewPath = (0, import_node_path20.join)(MEMORY_DIR, "domain", "project-overview.md");
17630
+ const overviewPath = (0, import_node_path19.join)(MEMORY_DIR, "domain", "project-overview.md");
17347
17631
  if ((0, import_node_fs23.existsSync)(overviewPath) && !opts.force) {
17348
17632
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
17349
17633
  }
@@ -17379,9 +17663,9 @@ async function runSeed(opts) {
17379
17663
  }
17380
17664
  const nodeId = res.data.node_id;
17381
17665
  const filePathRel = res.data.file_path;
17382
- const targetPath = (0, import_node_path20.join)(MEMORY_DIR, filePathRel);
17666
+ const targetPath = (0, import_node_path19.join)(MEMORY_DIR, filePathRel);
17383
17667
  try {
17384
- await (0, import_promises11.mkdir)((0, import_node_path20.dirname)(targetPath), { recursive: true });
17668
+ await (0, import_promises11.mkdir)((0, import_node_path19.dirname)(targetPath), { recursive: true });
17385
17669
  await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
17386
17670
  created++;
17387
17671
  opts.onCreated?.(nodeId, filePathRel, c);
@@ -17707,7 +17991,7 @@ async function runAnalyze(opts, globals) {
17707
17991
  let autoSeedNotice = null;
17708
17992
  try {
17709
17993
  await ensureMemoryDir();
17710
- const seedMarker = (0, import_node_path21.join)(VERITY_DIR, ".seeded");
17994
+ const seedMarker = (0, import_node_path20.join)(VERITY_DIR, ".seeded");
17711
17995
  const hasStandard = (0, import_node_fs24.existsSync)(STANDARD_FILE);
17712
17996
  const alreadyTried = (0, import_node_fs24.existsSync)(seedMarker);
17713
17997
  if (hasStandard && !alreadyTried) {
@@ -17775,7 +18059,7 @@ async function runAnalyze(opts, globals) {
17775
18059
  const priorState = foldForMarks(memorySession.d);
17776
18060
  incrementReport = computeIncrement(
17777
18061
  allForReview,
17778
- (p) => fileHash((0, import_node_path21.join)(repoRoot(), p)),
18062
+ (p) => fileHash((0, import_node_path20.join)(repoRoot(), p)),
17779
18063
  priorState.authored_all.map((a) => ({
17780
18064
  path: a.path,
17781
18065
  hash_at_last_verdict: a.hash_at_last_verdict
@@ -18030,6 +18314,10 @@ async function runAnalyze(opts, globals) {
18030
18314
  message = `Verity: request rejected \u2014 payload too large (${kb}KB over the 190KB cap). Commit or stash pre-existing changes, or run with --mode skip if you didn't intend to submit code this turn.`;
18031
18315
  } else if (result.category === "network" || result.category === "timeout") {
18032
18316
  message = `Verity offline \u2014 ${result.error}`;
18317
+ } else if (result.error.startsWith("STALE_VERIFICATION")) {
18318
+ message = "Verity: your GitHub verification expired \u2014 run `verity login` to re-verify (local analysis this run)";
18319
+ } else if (result.error.startsWith("FORBIDDEN")) {
18320
+ message = "Verity: no access grant for this repository \u2014 run `verity login` to refresh your grants (local analysis this run)";
18033
18321
  } else if (result.http_status && result.http_status >= 400 && result.http_status < 500) {
18034
18322
  message = `Verity: request rejected (HTTP ${result.http_status}) \u2014 ${result.error}`;
18035
18323
  } else if (result.http_status && result.http_status >= 500) {
@@ -18153,6 +18441,11 @@ async function runAnalyze(opts, globals) {
18153
18441
  }
18154
18442
  saveSnapshots(codeDelta.files.map((f) => ({ path: f.path, content: f.content })));
18155
18443
  const loginNudge = response.persisted === false ? " \u26A0\uFE0F Not logged in \u2014 this run was NOT saved and Verity has no memory of your project. Run `verity login` to unlock run history, trends, and cloud memory." : "";
18444
+ const grantWarning = reverifyNudge({
18445
+ grant_status: response.grant_status,
18446
+ reverify_by: response.reverify_by
18447
+ });
18448
+ const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
18156
18449
  switch (decision) {
18157
18450
  case "FAIL": {
18158
18451
  writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
@@ -18228,6 +18521,9 @@ async function runAnalyze(opts, globals) {
18228
18521
  `);
18229
18522
  if (loginNudge) process.stderr.write(`
18230
18523
  ${YELLOW}${loginNudge.trim()}${NC}
18524
+ `);
18525
+ if (grantNudge) process.stderr.write(`
18526
+ ${YELLOW}${grantNudge.trim()}${NC}
18231
18527
  `);
18232
18528
  process.exit(2);
18233
18529
  break;
@@ -18240,7 +18536,7 @@ ${YELLOW}${loginNudge.trim()}${NC}
18240
18536
  const viewUrl = response.view_url ?? "";
18241
18537
  if (viewUrl) userSummary += ` Report: ${viewUrl}`;
18242
18538
  if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
18243
- userSummary += loginNudge;
18539
+ userSummary += loginNudge + grantNudge;
18244
18540
  printJsonCompact(
18245
18541
  buildHookOutput("PASS", userSummary, agentContextFor(response))
18246
18542
  );
@@ -18254,7 +18550,7 @@ ${YELLOW}${loginNudge.trim()}${NC}
18254
18550
  const viewUrl = response.view_url ?? "";
18255
18551
  if (viewUrl) userSummary += ` Report: ${viewUrl}`;
18256
18552
  if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
18257
- userSummary += loginNudge;
18553
+ userSummary += loginNudge + grantNudge;
18258
18554
  printJsonCompact(
18259
18555
  buildHookOutput("WARN", userSummary, agentContextFor(response))
18260
18556
  );
@@ -18263,7 +18559,7 @@ ${YELLOW}${loginNudge.trim()}${NC}
18263
18559
  }
18264
18560
  default: {
18265
18561
  const raw = String(decision ?? "(missing)");
18266
- const msg = (autoSeedNotice ? `${autoSeedNotice} Verity: unrecognised verdict \u2014 treating as WARN` : "Verity: unrecognised verdict \u2014 treating as WARN") + loginNudge;
18562
+ const msg = (autoSeedNotice ? `${autoSeedNotice} Verity: unrecognised verdict \u2014 treating as WARN` : "Verity: unrecognised verdict \u2014 treating as WARN") + loginNudge + grantNudge;
18267
18563
  process.stderr.write(
18268
18564
  `Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
18269
18565
  `
@@ -18430,9 +18726,9 @@ async function runReview(opts, globals) {
18430
18726
 
18431
18727
  // src/commands/guard.ts
18432
18728
  var import_node_fs27 = require("node:fs");
18433
- var import_node_path22 = require("node:path");
18729
+ var import_node_path21 = require("node:path");
18434
18730
  var GUARD_BLOCK_CAP = 2;
18435
- var GUARD_ITER_FILE = (0, import_node_path22.join)(VERITY_DIR, ".guard-iteration");
18731
+ var GUARD_ITER_FILE = (0, import_node_path21.join)(VERITY_DIR, ".guard-iteration");
18436
18732
  function readPreToolUseStdin() {
18437
18733
  const empty = { command: "", cwd: null, sessionId: null };
18438
18734
  return new Promise((resolve2) => {
@@ -18674,9 +18970,10 @@ async function runGuard(opts, globals) {
18674
18970
  cmd: "guard"
18675
18971
  });
18676
18972
  if (!result.ok) {
18973
+ const authRemedy = result.error.startsWith("STALE_VERIFICATION") ? " Your GitHub verification expired \u2014 run `verity login` to re-verify." : result.error.startsWith("FORBIDDEN") ? " No access grant for this repository \u2014 run `verity login` to refresh your grants." : "";
18677
18974
  emitAllowNotice(
18678
- `\u26A0 Verity ${moment}: service offline \u2014 ${verb}ed WITHOUT review`,
18679
- `Verity ${moment}: service unavailable (${result.error}); the ${verb} was allowed WITHOUT a Verity review.`
18975
+ `\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "service offline"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
18976
+ `Verity ${moment}: ${authRemedy ? "not authorized" : "service unavailable"} (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${authRemedy}`
18680
18977
  );
18681
18978
  }
18682
18979
  if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
@@ -18748,14 +19045,14 @@ function writeBlockMessage(moment, response) {
18748
19045
  // src/commands/init.ts
18749
19046
  var import_node_fs29 = require("node:fs");
18750
19047
  var import_promises13 = require("node:fs/promises");
18751
- var import_node_path24 = require("node:path");
18752
- var import_node_child_process11 = require("node:child_process");
19048
+ var import_node_path23 = require("node:path");
19049
+ var import_node_child_process10 = require("node:child_process");
18753
19050
  var readline2 = __toESM(require("node:readline/promises"));
18754
19051
 
18755
19052
  // src/commands/migrate.ts
18756
19053
  var import_node_fs28 = require("node:fs");
18757
- var import_node_path23 = require("node:path");
18758
- var import_node_child_process10 = require("node:child_process");
19054
+ var import_node_path22 = require("node:path");
19055
+ var import_node_child_process9 = require("node:child_process");
18759
19056
 
18760
19057
  // src/lib/telemetry.ts
18761
19058
  var import_promises12 = require("node:fs/promises");
@@ -18850,11 +19147,11 @@ async function uninstallTelemetry() {
18850
19147
  // src/commands/migrate.ts
18851
19148
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
18852
19149
  function defaultNpmRemover(pkg) {
18853
- (0, import_node_child_process10.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
19150
+ (0, import_node_child_process9.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
18854
19151
  }
18855
19152
  function isGitTracked(cwd, relPath) {
18856
19153
  try {
18857
- (0, import_node_child_process10.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
19154
+ (0, import_node_child_process9.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
18858
19155
  return true;
18859
19156
  } catch {
18860
19157
  return false;
@@ -18862,7 +19159,7 @@ function isGitTracked(cwd, relPath) {
18862
19159
  }
18863
19160
  function isGitRepo(cwd) {
18864
19161
  try {
18865
- (0, import_node_child_process10.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
19162
+ (0, import_node_child_process9.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
18866
19163
  return true;
18867
19164
  } catch {
18868
19165
  return false;
@@ -18883,8 +19180,8 @@ async function runMigration(opts = {}) {
18883
19180
  return { actions, migrated: actions.length > 0 };
18884
19181
  }
18885
19182
  function migrateProjectDir(root, actions) {
18886
- const gateDir = (0, import_node_path23.join)(root, ".gate");
18887
- const verityDir = (0, import_node_path23.join)(root, ".verity");
19183
+ const gateDir = (0, import_node_path22.join)(root, ".gate");
19184
+ const verityDir = (0, import_node_path22.join)(root, ".verity");
18888
19185
  if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) {
18889
19186
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
18890
19187
  }
@@ -18902,7 +19199,7 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
18902
19199
  );
18903
19200
  }
18904
19201
  try {
18905
- (0, import_node_child_process10.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
19202
+ (0, import_node_child_process9.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
18906
19203
  actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
18907
19204
  moved = true;
18908
19205
  } catch {
@@ -18938,11 +19235,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
18938
19235
  }
18939
19236
  function migrateGlobalCredentials(home, actions) {
18940
19237
  if (!home) return;
18941
- const gateCreds = (0, import_node_path23.join)(home, ".gate", "credentials");
18942
- const verityCreds = (0, import_node_path23.join)(home, ".verity", "credentials");
19238
+ const gateCreds = (0, import_node_path22.join)(home, ".gate", "credentials");
19239
+ const verityCreds = (0, import_node_path22.join)(home, ".verity", "credentials");
18943
19240
  if (!(0, import_node_fs28.existsSync)(gateCreds)) return;
18944
19241
  if (!(0, import_node_fs28.existsSync)(verityCreds)) {
18945
- (0, import_node_fs28.mkdirSync)((0, import_node_path23.join)(home, ".verity"), { recursive: true });
19242
+ (0, import_node_fs28.mkdirSync)((0, import_node_path22.join)(home, ".verity"), { recursive: true });
18946
19243
  moveFile(gateCreds, verityCreds);
18947
19244
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
18948
19245
  return;
@@ -18964,7 +19261,7 @@ async function migrateLegacyHooks(root, actions) {
18964
19261
  }
18965
19262
  }
18966
19263
  async function migrateClaudeMd(root, actions) {
18967
- const claudeMd = (0, import_node_path23.join)(root, "CLAUDE.md");
19264
+ const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
18968
19265
  const hadLegacyBlock = (0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
18969
19266
  if (!hadLegacyBlock) return;
18970
19267
  try {
@@ -18975,13 +19272,13 @@ async function migrateClaudeMd(root, actions) {
18975
19272
  }
18976
19273
  }
18977
19274
  function migrateStandardFile(root, actions) {
18978
- const gateMd = (0, import_node_path23.join)(root, "GATE.md");
18979
- const verityMd = (0, import_node_path23.join)(root, "VERITY.md");
19275
+ const gateMd = (0, import_node_path22.join)(root, "GATE.md");
19276
+ const verityMd = (0, import_node_path22.join)(root, "VERITY.md");
18980
19277
  if (!(0, import_node_fs28.existsSync)(gateMd) || (0, import_node_fs28.existsSync)(verityMd)) return;
18981
19278
  let moved = false;
18982
19279
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
18983
19280
  try {
18984
- (0, import_node_child_process10.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
19281
+ (0, import_node_child_process9.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
18985
19282
  moved = true;
18986
19283
  } catch {
18987
19284
  }
@@ -18993,7 +19290,7 @@ function migrateStandardFile(root, actions) {
18993
19290
  actions.push("Renamed GATE.md \u2192 VERITY.md");
18994
19291
  }
18995
19292
  async function migrateTelemetryHeaders(root, actions) {
18996
- const file = (0, import_node_path23.join)(root, ".claude", "settings.local.json");
19293
+ const file = (0, import_node_path22.join)(root, ".claude", "settings.local.json");
18997
19294
  if (!(0, import_node_fs28.existsSync)(file)) return;
18998
19295
  let settings;
18999
19296
  try {
@@ -19056,7 +19353,7 @@ function readFileSyncSafe(path) {
19056
19353
  }
19057
19354
  function hasStagedChanges(root) {
19058
19355
  try {
19059
- (0, import_node_child_process10.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
19356
+ (0, import_node_child_process9.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
19060
19357
  return false;
19061
19358
  } catch {
19062
19359
  return true;
@@ -19083,15 +19380,15 @@ function moveFile(from, to) {
19083
19380
  function carryLegacyContents(gateDir, verityDir) {
19084
19381
  let copied = 0;
19085
19382
  const walk = (relDir) => {
19086
- const srcDir = (0, import_node_path23.join)(gateDir, relDir);
19383
+ const srcDir = (0, import_node_path22.join)(gateDir, relDir);
19087
19384
  for (const entry of (0, import_node_fs28.readdirSync)(srcDir)) {
19088
- const rel = relDir ? (0, import_node_path23.join)(relDir, entry) : entry;
19089
- const src = (0, import_node_path23.join)(gateDir, rel);
19090
- const dest = (0, import_node_path23.join)(verityDir, rel);
19385
+ const rel = relDir ? (0, import_node_path22.join)(relDir, entry) : entry;
19386
+ const src = (0, import_node_path22.join)(gateDir, rel);
19387
+ const dest = (0, import_node_path22.join)(verityDir, rel);
19091
19388
  if ((0, import_node_fs28.statSync)(src).isDirectory()) {
19092
19389
  walk(rel);
19093
19390
  } else if (!(0, import_node_fs28.existsSync)(dest)) {
19094
- (0, import_node_fs28.mkdirSync)((0, import_node_path23.dirname)(dest), { recursive: true });
19391
+ (0, import_node_fs28.mkdirSync)((0, import_node_path22.dirname)(dest), { recursive: true });
19095
19392
  (0, import_node_fs28.cpSync)(src, dest);
19096
19393
  copied++;
19097
19394
  }
@@ -19101,22 +19398,22 @@ function carryLegacyContents(gateDir, verityDir) {
19101
19398
  return copied;
19102
19399
  }
19103
19400
  async function needsMigration(root = repoRoot()) {
19104
- const gateDir = (0, import_node_path23.join)(root, ".gate");
19105
- const verityDir = (0, import_node_path23.join)(root, ".verity");
19401
+ const gateDir = (0, import_node_path22.join)(root, ".gate");
19402
+ const verityDir = (0, import_node_path22.join)(root, ".verity");
19106
19403
  if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) return true;
19107
19404
  if ((0, import_node_fs28.existsSync)(gateDir) && (0, import_node_fs28.existsSync)(verityDir)) {
19108
- if ((0, import_node_fs28.existsSync)((0, import_node_path23.join)(gateDir, "credentials")) && !(0, import_node_fs28.existsSync)((0, import_node_path23.join)(verityDir, "credentials"))) {
19405
+ if ((0, import_node_fs28.existsSync)((0, import_node_path22.join)(gateDir, "credentials")) && !(0, import_node_fs28.existsSync)((0, import_node_path22.join)(verityDir, "credentials"))) {
19109
19406
  return true;
19110
19407
  }
19111
- if ((0, import_node_fs28.existsSync)((0, import_node_path23.join)(gateDir, "memory")) && !(0, import_node_fs28.existsSync)((0, import_node_path23.join)(verityDir, "memory"))) {
19408
+ if ((0, import_node_fs28.existsSync)((0, import_node_path22.join)(gateDir, "memory")) && !(0, import_node_fs28.existsSync)((0, import_node_path22.join)(verityDir, "memory"))) {
19112
19409
  return true;
19113
19410
  }
19114
19411
  }
19115
- const claudeMd = (0, import_node_path23.join)(root, "CLAUDE.md");
19412
+ const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
19116
19413
  if ((0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
19117
19414
  return true;
19118
19415
  }
19119
- if ((0, import_node_fs28.existsSync)((0, import_node_path23.join)(root, "GATE.md")) && !(0, import_node_fs28.existsSync)((0, import_node_path23.join)(root, "VERITY.md"))) {
19416
+ if ((0, import_node_fs28.existsSync)((0, import_node_path22.join)(root, "GATE.md")) && !(0, import_node_fs28.existsSync)((0, import_node_path22.join)(root, "VERITY.md"))) {
19120
19417
  return true;
19121
19418
  }
19122
19419
  if (await hasLegacyHooksAt(root)) return true;
@@ -19152,12 +19449,25 @@ async function promptYes(question) {
19152
19449
  rl.close();
19153
19450
  }
19154
19451
  }
19155
- async function confirmExistingLogin(serviceUrl, opts) {
19452
+ async function confirmExistingLogin(serviceUrl, remote, opts) {
19156
19453
  const existing = await resolveToken(opts.token);
19157
19454
  if (!existing.ok) return "drive-login";
19158
19455
  const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
19159
19456
  if (who.ok && who.data.logged_in) {
19160
- printInfo(`Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713 \u2014 runs & memory sync to Verity.`);
19457
+ const identity = who.data.email ?? `user #${who.data.user_id}`;
19458
+ const covered = who.data.grant_status != null;
19459
+ if (!remote) {
19460
+ printInfo(`Logged in as ${identity} \u2713`);
19461
+ printInfo(" This directory has no git remote, so there is no project here to sync to.");
19462
+ } else if (covered) {
19463
+ printInfo(`Logged in as ${identity} \u2713 \u2014 runs & memory sync to Verity.`);
19464
+ } else {
19465
+ printInfo(`Logged in as ${identity} \u2713`);
19466
+ printWarn(" This repository is NOT covered by your Verity access grants \u2014 nothing will sync.");
19467
+ printInfo(' Grant the Verity GitHub App access to it, then run "verity login" to refresh your grants.');
19468
+ }
19469
+ const nudge = reverifyNudge(who.data);
19470
+ if (nudge) printWarn(` ${nudge}`);
19161
19471
  return "handled";
19162
19472
  }
19163
19473
  if (!who.ok) {
@@ -19186,18 +19496,18 @@ async function runOptionalAuth(resolution, opts = {}) {
19186
19496
  printInfo(" Log in below to re-register this project and repair ~/.verity/credentials.");
19187
19497
  }
19188
19498
  }
19189
- if (!healed) {
19190
- const state = await confirmExistingLogin(serviceUrl, opts);
19191
- if (state === "handled") return;
19192
- }
19193
19499
  let remote = "";
19194
19500
  try {
19195
- remote = (0, import_node_child_process11.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
19501
+ remote = (0, import_node_child_process10.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
19196
19502
  } catch {
19197
19503
  }
19504
+ if (!healed) {
19505
+ const state = await confirmExistingLogin(serviceUrl, remote, opts);
19506
+ if (state === "handled") return;
19507
+ }
19198
19508
  const localOnlyNote = () => {
19199
19509
  printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
19200
- printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
19510
+ printInfo(' Authenticate anytime: run "verity login" (one login covers every repo you can write to).');
19201
19511
  };
19202
19512
  if (process.stdin.isTTY && process.stdout.isTTY) {
19203
19513
  console.log("");
@@ -19223,7 +19533,7 @@ async function runOptionalAuth(resolution, opts = {}) {
19223
19533
  localOnlyNote();
19224
19534
  return;
19225
19535
  }
19226
- const projectName = parseRemote(remote)?.repo ?? (0, import_node_path24.basename)(process.cwd());
19536
+ const projectName = parseRemote(remote)?.repo ?? (0, import_node_path23.basename)(process.cwd());
19227
19537
  printInfo("Authenticating with GitHub\u2026");
19228
19538
  const result = await registerProject({ projectName, remote, serviceUrl, verbose: opts.verbose });
19229
19539
  if (result.ok) {
@@ -19236,15 +19546,15 @@ async function runOptionalAuth(resolution, opts = {}) {
19236
19546
  }
19237
19547
  function resolveDataDir() {
19238
19548
  const candidates = [
19239
- (0, import_node_path24.join)(__dirname, "..", "data"),
19549
+ (0, import_node_path23.join)(__dirname, "..", "data"),
19240
19550
  // installed: node_modules/@codacy/verity-cli/data
19241
- (0, import_node_path24.join)(__dirname, "..", "..", "data"),
19551
+ (0, import_node_path23.join)(__dirname, "..", "..", "data"),
19242
19552
  // edge case: nested resolution
19243
- (0, import_node_path24.join)(process.cwd(), "cli", "data")
19553
+ (0, import_node_path23.join)(process.cwd(), "cli", "data")
19244
19554
  // local dev: running from repo root
19245
19555
  ];
19246
19556
  for (const candidate of candidates) {
19247
- if ((0, import_node_fs29.existsSync)((0, import_node_path24.join)(candidate, "skills"))) {
19557
+ if ((0, import_node_fs29.existsSync)((0, import_node_path23.join)(candidate, "skills"))) {
19248
19558
  return candidate;
19249
19559
  }
19250
19560
  }
@@ -19288,30 +19598,30 @@ function registerInitCommand(program2) {
19288
19598
  }
19289
19599
  printInfo(` Node.js ${nodeVersion} \u2713`);
19290
19600
  try {
19291
- const gitVersion = (0, import_node_child_process11.execSync)("git --version", { encoding: "utf-8" }).trim();
19601
+ const gitVersion = (0, import_node_child_process10.execSync)("git --version", { encoding: "utf-8" }).trim();
19292
19602
  printInfo(` ${gitVersion} \u2713`);
19293
19603
  } catch {
19294
19604
  printError("git is required but not installed. Install from https://git-scm.com");
19295
19605
  process.exit(1);
19296
19606
  }
19297
19607
  try {
19298
- (0, import_node_child_process11.execSync)("which claude", { encoding: "utf-8" });
19608
+ (0, import_node_child_process10.execSync)("which claude", { encoding: "utf-8" });
19299
19609
  printInfo(" Claude Code \u2713");
19300
19610
  } catch {
19301
19611
  printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
19302
19612
  }
19303
19613
  try {
19304
- (0, import_node_child_process11.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
19614
+ (0, import_node_child_process10.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
19305
19615
  printInfo(" @codacy/analysis-cli \u2713");
19306
19616
  } catch {
19307
19617
  printInfo(" Installing @codacy/analysis-cli...");
19308
19618
  try {
19309
- (0, import_node_child_process11.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
19619
+ (0, import_node_child_process10.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
19310
19620
  printInfo(" @codacy/analysis-cli installed \u2713");
19311
19621
  } catch {
19312
19622
  try {
19313
19623
  printWarn(" Retrying with sudo...");
19314
- (0, import_node_child_process11.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
19624
+ (0, import_node_child_process10.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
19315
19625
  printInfo(" @codacy/analysis-cli installed \u2713");
19316
19626
  } catch {
19317
19627
  printWarn(" Could not install @codacy/analysis-cli automatically.");
@@ -19323,20 +19633,20 @@ function registerInitCommand(program2) {
19323
19633
  console.log("");
19324
19634
  printInfo("Installing skills...");
19325
19635
  const dataDir = resolveDataDir();
19326
- const skillsSource = (0, import_node_path24.join)(dataDir, "skills");
19636
+ const skillsSource = (0, import_node_path23.join)(dataDir, "skills");
19327
19637
  const skillsDest = ".claude/skills";
19328
19638
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
19329
19639
  let skillsInstalled = 0;
19330
19640
  for (const skill of skills) {
19331
- const src = (0, import_node_path24.join)(skillsSource, skill);
19332
- const dest = (0, import_node_path24.join)(skillsDest, skill);
19641
+ const src = (0, import_node_path23.join)(skillsSource, skill);
19642
+ const dest = (0, import_node_path23.join)(skillsDest, skill);
19333
19643
  if (!(0, import_node_fs29.existsSync)(src)) {
19334
19644
  printWarn(` Skill data not found: ${skill}`);
19335
19645
  continue;
19336
19646
  }
19337
19647
  if ((0, import_node_fs29.existsSync)(dest) && !force) {
19338
- const srcSkill = (0, import_node_path24.join)(src, "SKILL.md");
19339
- const destSkill = (0, import_node_path24.join)(dest, "SKILL.md");
19648
+ const srcSkill = (0, import_node_path23.join)(src, "SKILL.md");
19649
+ const destSkill = (0, import_node_path23.join)(dest, "SKILL.md");
19340
19650
  if ((0, import_node_fs29.existsSync)(destSkill)) {
19341
19651
  try {
19342
19652
  const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
@@ -19374,7 +19684,7 @@ function registerInitCommand(program2) {
19374
19684
  } catch (err) {
19375
19685
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
19376
19686
  }
19377
- const globalVerityDir = (0, import_node_path24.join)(process.env.HOME ?? "", ".verity");
19687
+ const globalVerityDir = (0, import_node_path23.join)(process.env.HOME ?? "", ".verity");
19378
19688
  await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
19379
19689
  console.log("");
19380
19690
  try {
@@ -19403,14 +19713,14 @@ function registerInitCommand(program2) {
19403
19713
  console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
19404
19714
  console.log("");
19405
19715
  console.log(" Next step: open this project in Claude Code and run /verity-setup");
19406
- console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
19716
+ console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity login".)');
19407
19717
  console.log("");
19408
19718
  });
19409
19719
  }
19410
19720
 
19411
19721
  // src/commands/uninstall.ts
19412
19722
  var import_node_fs30 = require("node:fs");
19413
- var import_node_path25 = require("node:path");
19723
+ var import_node_path24 = require("node:path");
19414
19724
  var SKILL_NAMES = [
19415
19725
  "verity-setup",
19416
19726
  "verity-analyze",
@@ -19429,7 +19739,7 @@ function registerUninstallCommand(program2) {
19429
19739
  const actions = [];
19430
19740
  const skillsRoot = projectPath(".claude/skills");
19431
19741
  for (const name of SKILL_NAMES) {
19432
- const dir = (0, import_node_path25.join)(skillsRoot, name);
19742
+ const dir = (0, import_node_path24.join)(skillsRoot, name);
19433
19743
  if ((0, import_node_fs30.existsSync)(dir)) {
19434
19744
  actions.push({
19435
19745
  label: `Remove .claude/skills/${name}/`,
@@ -19475,7 +19785,7 @@ function registerUninstallCommand(program2) {
19475
19785
  }
19476
19786
  });
19477
19787
  const home = process.env.HOME ?? "";
19478
- const globalVerityDir = (0, import_node_path25.join)(home, ".verity");
19788
+ const globalVerityDir = (0, import_node_path24.join)(home, ".verity");
19479
19789
  if (purgeGlobal && (0, import_node_fs30.existsSync)(globalVerityDir)) {
19480
19790
  actions.push({
19481
19791
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
@@ -19674,7 +19984,7 @@ function registerTaskCommands(program2) {
19674
19984
 
19675
19985
  // src/commands/reset.ts
19676
19986
  var import_node_fs31 = require("node:fs");
19677
- var import_node_path26 = require("node:path");
19987
+ var import_node_path25 = require("node:path");
19678
19988
  function registerResetCommand(program2) {
19679
19989
  program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.verity/.logs/)").action(async (opts) => {
19680
19990
  const globals = program2.opts();
@@ -19715,7 +20025,7 @@ function registerResetCommand(program2) {
19715
20025
  for (const entry of (0, import_node_fs31.readdirSync)(cacheDir)) {
19716
20026
  if (entry.startsWith("pending-")) {
19717
20027
  try {
19718
- (0, import_node_fs31.unlinkSync)((0, import_node_path26.join)(cacheDir, entry));
20028
+ (0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(cacheDir, entry));
19719
20029
  purged++;
19720
20030
  } catch {
19721
20031
  }
@@ -19742,7 +20052,7 @@ function registerResetCommand(program2) {
19742
20052
  if ((0, import_node_fs31.existsSync)(logsDir)) {
19743
20053
  for (const entry of (0, import_node_fs31.readdirSync)(logsDir)) {
19744
20054
  try {
19745
- (0, import_node_fs31.unlinkSync)((0, import_node_path26.join)(logsDir, entry));
20055
+ (0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(logsDir, entry));
19746
20056
  } catch {
19747
20057
  }
19748
20058
  }
@@ -20010,7 +20320,11 @@ function registerTelemetryCommands(program2) {
20010
20320
  const globals = program2.opts();
20011
20321
  const tokenResult = await resolveToken(globals.token);
20012
20322
  if (tokenResult.ok) {
20013
- printJsonCompact({ Authorization: `Bearer ${tokenResult.data.token}` });
20323
+ const remote = requestRemote();
20324
+ printJsonCompact({
20325
+ Authorization: `Bearer ${tokenResult.data.token}`,
20326
+ ...remote ? { "X-Verity-Remote": remote } : {}
20327
+ });
20014
20328
  } else {
20015
20329
  printJsonCompact({});
20016
20330
  }
@@ -20040,7 +20354,7 @@ function registerTelemetryCommands(program2) {
20040
20354
  }
20041
20355
 
20042
20356
  // src/cli.ts
20043
- program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.a57c8d9").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
20357
+ program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.dbd87b1").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
20044
20358
  try {
20045
20359
  await foldLegacyLocalCredential();
20046
20360
  } catch {
@@ -20048,6 +20362,7 @@ program.name("verity").description("CLI for Verity quality gate service").versio
20048
20362
  });
20049
20363
  registerAuthCommands(program);
20050
20364
  registerLoginCommand(program);
20365
+ registerTokenCommand(program);
20051
20366
  registerHooksCommands(program);
20052
20367
  registerIntentCommands(program);
20053
20368
  registerLifecycleCommands(program);