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

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,67 @@ 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
+ }
10710
+ async function removeGlobalCredential(remote) {
10711
+ const path = globalCredentialsPath();
10712
+ let content;
10713
+ try {
10714
+ content = await (0, import_promises.readFile)(path, "utf-8");
10715
+ } catch {
10716
+ return false;
10717
+ }
10718
+ const key = encodeRemoteKey(remote);
10719
+ const kept = [];
10720
+ let removed = false;
10721
+ for (const line of content.split("\n")) {
10722
+ const parsed = parseCredentialLine(line);
10723
+ if (parsed && parsed.remote === key) {
10724
+ removed = true;
10725
+ continue;
10726
+ }
10727
+ kept.push(line);
10728
+ }
10729
+ if (!removed) return false;
10730
+ while (kept.length && kept[kept.length - 1].trim() === "") kept.pop();
10731
+ try {
10732
+ await (0, import_promises.writeFile)(path, kept.length ? kept.join("\n") + "\n" : "", { mode: 384 });
10733
+ await (0, import_promises.chmod)(path, 384).catch(() => {
10734
+ });
10735
+ } catch {
10736
+ return false;
10737
+ }
10738
+ return true;
10739
+ }
10808
10740
  function parseLocalCredentialFile(content) {
10809
10741
  const tokenMatch = content.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
10810
10742
  if (!tokenMatch) return null;
@@ -10857,275 +10789,49 @@ async function foldLegacyLocalCredential(remoteArg) {
10857
10789
  return true;
10858
10790
  }
10859
10791
 
10860
- // src/lib/service-url.ts
10861
- async function serviceUrlFromCredentials() {
10862
- const rec = await readGlobalCredential(currentRemote());
10863
- return rec?.serviceUrl ?? null;
10792
+ // src/lib/git.ts
10793
+ var import_node_child_process3 = require("node:child_process");
10794
+ var import_node_fs3 = require("node:fs");
10795
+ var import_node_path3 = require("node:path");
10796
+ function resolveFile(relpath) {
10797
+ return (0, import_node_fs3.existsSync)(relpath) ? relpath : null;
10864
10798
  }
10865
- async function serviceUrlFromVerityMd() {
10799
+ function execGit(cmd) {
10866
10800
  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
- }
10801
+ return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10878
10802
  } catch {
10803
+ return "";
10879
10804
  }
10880
- return null;
10881
10805
  }
10882
- async function resolveServiceUrlDetailed(flagUrl) {
10883
- if (flagUrl) {
10884
- return { ok: true, data: { url: flagUrl, source: "flag" } };
10885
- }
10886
- const envUrl = process.env.VERITY_SERVICE_URL;
10887
- if (envUrl) {
10888
- return { ok: true, data: { url: envUrl, source: "env" } };
10889
- }
10890
- const credsUrl = await serviceUrlFromCredentials();
10891
- if (credsUrl) {
10892
- return { ok: true, data: { url: credsUrl, source: "credentials" } };
10806
+ function splitLines(s) {
10807
+ return s.split("\n").filter((l) => l.length > 0);
10808
+ }
10809
+ var SHA_RE = /^[0-9a-f]{40}$/;
10810
+ function readBaselineSha() {
10811
+ if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
10812
+ let sha;
10813
+ try {
10814
+ sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
10815
+ } catch {
10816
+ return null;
10893
10817
  }
10894
- const mdUrl = await serviceUrlFromVerityMd();
10895
- if (mdUrl) {
10896
- return { ok: true, data: { url: mdUrl, source: "verity_md" } };
10818
+ if (!SHA_RE.test(sha)) return null;
10819
+ const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
10820
+ if (!reachable) {
10821
+ try {
10822
+ (0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
10823
+ } catch {
10824
+ }
10825
+ return null;
10897
10826
  }
10898
- return { ok: false, error: "No Verity service URL found. Run /verity-setup to configure." };
10899
- }
10900
- async function resolveServiceUrl(flagUrl) {
10901
- const result = await resolveServiceUrlDetailed(flagUrl);
10902
- return result.ok ? { ok: true, data: result.data.url } : result;
10903
- }
10904
- function isHealCandidate(resolved) {
10905
- return (resolved.source === "credentials" || resolved.source === "verity_md") && resolved.url !== DEFAULT_SERVICE_URL;
10827
+ return sha;
10906
10828
  }
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" } };
10916
- }
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
- };
10923
- }
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
- };
10930
- }
10931
- return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
10932
- }
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"
10941
- });
10942
- }
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"
10951
- });
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
- }
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 };
10977
- }
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) {
10986
- 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
- }
10993
- });
10994
- if (!res.ok) return null;
10995
- const body = await res.json();
10996
- return typeof body.id === "number" ? body.id : null;
10997
- } catch {
10998
- return null;
10999
- }
11000
- }
11001
- async function githubCanSeeRepo(owner, repo, token) {
11002
- let res;
11003
- 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}` };
11017
- }
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
- }
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
- }
11082
- }
11083
- return { ok: false, error: "Timed out waiting for GitHub authorization." };
11084
- }
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;
11092
- }
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
- }
11099
- }
11100
- function splitLines(s) {
11101
- return s.split("\n").filter((l) => l.length > 0);
11102
- }
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;
11111
- }
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 {
11118
- }
11119
- return null;
11120
- }
11121
- return sha;
11122
- }
11123
- function writeBaselineSha(sha) {
11124
- if (!SHA_RE.test(sha)) return;
11125
- 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);
11128
- } catch {
10829
+ function writeBaselineSha(sha) {
10830
+ if (!SHA_RE.test(sha)) return;
10831
+ try {
10832
+ (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(BASELINE_SHA_FILE), { recursive: true });
10833
+ (0, import_node_fs3.writeFileSync)(BASELINE_SHA_FILE, sha);
10834
+ } catch {
11129
10835
  }
11130
10836
  }
11131
10837
  function getChangedFiles() {
@@ -11218,8 +10924,8 @@ function filterReviewable(files) {
11218
10924
  const ext = (0, import_node_path3.extname)(f).slice(1);
11219
10925
  if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
11220
10926
  if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
11221
- const basename5 = f.split("/").pop() ?? "";
11222
- if (REVIEWABLE_FILENAMES.has(basename5)) return true;
10927
+ const basename4 = f.split("/").pop() ?? "";
10928
+ if (REVIEWABLE_FILENAMES.has(basename4)) return true;
11223
10929
  if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
11224
10930
  return false;
11225
10931
  });
@@ -11309,47 +11015,446 @@ function listTrackedFiles() {
11309
11015
  for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
11310
11016
  return Array.from(set);
11311
11017
  }
11018
+ function sanitizeRemote(remote) {
11019
+ return remote.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/]*@/, "$1");
11020
+ }
11312
11021
 
11313
- // src/lib/register.ts
11314
- var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
11315
- async function waitForEnter(prompt) {
11316
- if (!isInteractive()) return;
11317
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
11318
- try {
11319
- await rl.question(prompt);
11320
- } finally {
11321
- rl.close();
11322
- }
11022
+ // src/lib/api-client.ts
11023
+ var cachedRemote = null;
11024
+ function requestRemote() {
11025
+ const override = process.env.VERITY_REMOTE_OVERRIDE;
11026
+ if (override) return sanitizeRemote(override.trim());
11027
+ if (cachedRemote === null) cachedRemote = sanitizeRemote(currentRemote());
11028
+ return cachedRemote;
11323
11029
  }
11324
- async function ensureAppInstalled(owner, repo, token) {
11325
- let installUrl = null;
11326
- const maxAttempts = 3;
11327
- for (let attempt = 1; ; attempt++) {
11328
- const visible = await githubCanSeeRepo(owner, repo, token);
11329
- if (!visible.ok) return { ok: true, data: void 0 };
11330
- if (visible.data) return { ok: true, data: void 0 };
11331
- if (installUrl === null) installUrl = githubAppInstallUrl(await githubAccountId(owner));
11332
- if (!isInteractive() || attempt >= maxAttempts) {
11333
- return {
11334
- ok: false,
11335
- error: `The Verity GitHub App can't see ${owner}/${repo}. Install it on "${owner}" and grant access to this repository, then re-run:
11336
- ${installUrl}`
11337
- };
11338
- }
11339
- printWarn(`The Verity GitHub App can't see ${owner}/${repo} yet.`);
11340
- printInfo(`Install it on "${owner}" (grant access to this repo):`);
11341
- printInfo(` ${installUrl}`);
11342
- await waitForEnter("Press Enter once installed to retry\u2026 ");
11343
- }
11030
+ function describeFetchError(err, url) {
11031
+ const message = err instanceof Error ? err.message : String(err);
11032
+ const cause = err?.cause;
11033
+ const causeBits = [cause?.code, cause?.hostname].filter(Boolean).join(" ");
11034
+ const detail = causeBits || (cause?.message && cause.message !== message ? cause.message : "");
11035
+ return `${message}${detail ? ` (${detail})` : ""} \u2014 could not reach ${url}`;
11344
11036
  }
11345
- async function registerProject(opts) {
11346
- const parsed = parseRemote(opts.remote);
11347
- if (!parsed) {
11348
- return { ok: false, error: `Could not parse git remote: ${opts.remote}` };
11349
- }
11350
- if (parsed.provider !== "github") {
11351
- return { ok: false, error: `Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.` };
11352
- }
11037
+ async function apiRequest(options) {
11038
+ const {
11039
+ method,
11040
+ path,
11041
+ serviceUrl,
11042
+ token,
11043
+ body,
11044
+ verbose,
11045
+ timeout = 9e4,
11046
+ cmd = "unknown",
11047
+ retry = false,
11048
+ encodeBody = false,
11049
+ extraHeaders
11050
+ } = options;
11051
+ const url = `${serviceUrl}${path}`;
11052
+ const headers = {
11053
+ "Content-Type": "application/json"
11054
+ };
11055
+ if (token) {
11056
+ headers["Authorization"] = `Bearer ${token}`;
11057
+ }
11058
+ const remote = requestRemote();
11059
+ if (remote) {
11060
+ headers["X-Verity-Remote"] = remote;
11061
+ }
11062
+ if (extraHeaders) {
11063
+ Object.assign(headers, extraHeaders);
11064
+ }
11065
+ const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
11066
+ if (testMockScenario) {
11067
+ headers["X-Verity-Mock-Scenario"] = testMockScenario;
11068
+ }
11069
+ const testMockFailure = process.env.VERITY_TEST_MOCK_FAILURE;
11070
+ if (testMockFailure) {
11071
+ headers["X-Verity-Mock-Failure"] = testMockFailure;
11072
+ }
11073
+ printVerbose(`${method} ${url}`, verbose);
11074
+ let serializedBody;
11075
+ if (body !== void 0 && body !== null) {
11076
+ const innerJson = JSON.stringify(body);
11077
+ if (encodeBody) {
11078
+ const payload = Buffer.from(innerJson, "utf8").toString("base64");
11079
+ serializedBody = JSON.stringify({ encoding: "base64", payload });
11080
+ } else {
11081
+ serializedBody = innerJson;
11082
+ }
11083
+ printVerbose(`Body: ${serializedBody.slice(0, 500)}`, verbose);
11084
+ }
11085
+ const startedAt = Date.now();
11086
+ const bodyBytes = serializedBody ? Buffer.byteLength(serializedBody) : 0;
11087
+ const logBase = { cmd, method, url, body_bytes: bodyBytes, retry, encoded: encodeBody };
11088
+ let response;
11089
+ try {
11090
+ response = await fetch(url, {
11091
+ method,
11092
+ headers,
11093
+ body: serializedBody,
11094
+ signal: AbortSignal.timeout(timeout)
11095
+ });
11096
+ } catch (err) {
11097
+ const duration2 = Date.now() - startedAt;
11098
+ const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
11099
+ const category = isTimeout ? "timeout" : "network";
11100
+ const error = isTimeout ? `Request timed out after ${timeout}ms (${url})` : `Network error: ${describeFetchError(err, url)}`;
11101
+ logHttpCall({ ...logBase, duration_ms: duration2, http_status: null, category, error });
11102
+ return { ok: false, error, category, http_status: null };
11103
+ }
11104
+ let data;
11105
+ try {
11106
+ data = response.status === 204 || response.headers.get("content-length") === "0" ? {} : await response.json();
11107
+ } catch {
11108
+ const duration2 = Date.now() - startedAt;
11109
+ const error = `Invalid JSON response (HTTP ${response.status})`;
11110
+ logHttpCall({
11111
+ ...logBase,
11112
+ duration_ms: duration2,
11113
+ http_status: response.status,
11114
+ category: "invalid_json",
11115
+ error
11116
+ });
11117
+ return { ok: false, error, category: "invalid_json", http_status: response.status };
11118
+ }
11119
+ printVerbose(`Response ${response.status}: ${JSON.stringify(data).slice(0, 500)}`, verbose);
11120
+ const duration = Date.now() - startedAt;
11121
+ if (!response.ok) {
11122
+ const apiErr = data;
11123
+ const code = apiErr?.error?.code ?? "UNKNOWN";
11124
+ const message = apiErr?.error?.message ?? `HTTP ${response.status}`;
11125
+ const category = response.status >= 500 ? "http_5xx" : "http_4xx";
11126
+ const error = `${code}: ${message}`;
11127
+ logHttpCall({
11128
+ ...logBase,
11129
+ duration_ms: duration,
11130
+ http_status: response.status,
11131
+ category,
11132
+ error
11133
+ });
11134
+ return { ok: false, error, category, http_status: response.status };
11135
+ }
11136
+ logHttpCall({
11137
+ ...logBase,
11138
+ duration_ms: duration,
11139
+ http_status: response.status,
11140
+ category: "ok"
11141
+ });
11142
+ return { ok: true, data };
11143
+ }
11144
+ function analyzeRequest(options) {
11145
+ return apiRequest({
11146
+ method: "POST",
11147
+ path: "/analyze",
11148
+ serviceUrl: options.serviceUrl,
11149
+ token: options.token,
11150
+ body: options.body,
11151
+ timeout: options.timeout,
11152
+ cmd: options.cmd,
11153
+ verbose: options.verbose,
11154
+ retry: options.retry,
11155
+ encodeBody: true
11156
+ });
11157
+ }
11158
+
11159
+ // src/lib/service-url.ts
11160
+ var import_promises2 = require("node:fs/promises");
11161
+ async function serviceUrlFromCredentials() {
11162
+ const rec = await readGlobalCredential(currentRemote());
11163
+ return rec?.serviceUrl ?? null;
11164
+ }
11165
+ async function serviceUrlFromVerityMd() {
11166
+ try {
11167
+ const content = await (0, import_promises2.readFile)(projectPath(VERITY_MD_FILE), "utf-8");
11168
+ const boldLine = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
11169
+ if (boldLine) {
11170
+ const urlMatch = boldLine.match(/https:\/\/[^\s]+/);
11171
+ if (urlMatch) return urlMatch[0];
11172
+ }
11173
+ const plainLine = content.split("\n").find((l) => /(?:url|service)\s*:/i.test(l));
11174
+ if (plainLine) {
11175
+ const urlMatch = plainLine.match(/https:\/\/[^\s]+/);
11176
+ if (urlMatch) return urlMatch[0];
11177
+ }
11178
+ } catch {
11179
+ }
11180
+ return null;
11181
+ }
11182
+ async function resolveServiceUrlDetailed(flagUrl) {
11183
+ if (flagUrl) {
11184
+ return { ok: true, data: { url: flagUrl, source: "flag" } };
11185
+ }
11186
+ const envUrl = process.env.VERITY_SERVICE_URL;
11187
+ if (envUrl) {
11188
+ return { ok: true, data: { url: envUrl, source: "env" } };
11189
+ }
11190
+ const credsUrl = await serviceUrlFromCredentials();
11191
+ if (credsUrl) {
11192
+ return { ok: true, data: { url: credsUrl, source: "credentials" } };
11193
+ }
11194
+ const mdUrl = await serviceUrlFromVerityMd();
11195
+ if (mdUrl) {
11196
+ return { ok: true, data: { url: mdUrl, source: "verity_md" } };
11197
+ }
11198
+ return { ok: false, error: "No Verity service URL found. Run /verity-setup to configure." };
11199
+ }
11200
+ async function resolveServiceUrl(flagUrl) {
11201
+ const result = await resolveServiceUrlDetailed(flagUrl);
11202
+ return result.ok ? { ok: true, data: result.data.url } : result;
11203
+ }
11204
+ function isHealCandidate(resolved) {
11205
+ return (resolved.source === "credentials" || resolved.source === "verity_md") && resolved.url !== DEFAULT_SERVICE_URL;
11206
+ }
11207
+
11208
+ // src/lib/auth.ts
11209
+ async function resolveToken(flagToken) {
11210
+ if (flagToken) {
11211
+ return { ok: true, data: { token: flagToken, source: "flag" } };
11212
+ }
11213
+ const envToken = process.env.VERITY_TOKEN;
11214
+ if (envToken) {
11215
+ return { ok: true, data: { token: envToken, source: "env" } };
11216
+ }
11217
+ const rec = await readGlobalCredential(currentRemote());
11218
+ if (rec) {
11219
+ return {
11220
+ ok: true,
11221
+ data: { token: rec.token, source: "global", userId: rec.userId, email: rec.email }
11222
+ };
11223
+ }
11224
+ const local = await readLegacyLocalCredential();
11225
+ if (local) {
11226
+ return {
11227
+ ok: true,
11228
+ data: { token: local.token, source: "local", userId: local.userId, email: local.email }
11229
+ };
11230
+ }
11231
+ return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
11232
+ }
11233
+ async function whoami(token, serviceUrl, verbose) {
11234
+ return apiRequest({
11235
+ method: "GET",
11236
+ path: "/auth/whoami",
11237
+ serviceUrl,
11238
+ token,
11239
+ verbose,
11240
+ cmd: "whoami"
11241
+ });
11242
+ }
11243
+ function reverifyNudge(who) {
11244
+ if (who.grant_status === "hard_stale") {
11245
+ return 'Your GitHub verification has expired \u2014 run "verity login" to restore saved runs and memory.';
11246
+ }
11247
+ if (who.grant_status === "soft_stale") {
11248
+ const by = who.reverify_by ? ` by ${who.reverify_by.slice(0, 10)}` : " soon";
11249
+ return `Your GitHub verification needs a refresh${by} \u2014 run "verity login" to re-verify.`;
11250
+ }
11251
+ return null;
11252
+ }
11253
+ function authDenialRemedy(error) {
11254
+ if (error.startsWith("STALE_VERIFICATION")) {
11255
+ return {
11256
+ code: "STALE_VERIFICATION",
11257
+ remedy: 'Your GitHub verification has expired \u2014 run "verity login" to re-verify your repository access.'
11258
+ };
11259
+ }
11260
+ if (error.startsWith("FORBIDDEN")) {
11261
+ return {
11262
+ code: "FORBIDDEN",
11263
+ 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.'
11264
+ };
11265
+ }
11266
+ if (error.startsWith("INVALID_TOKEN")) {
11267
+ return {
11268
+ code: "INVALID_TOKEN",
11269
+ remedy: 'Your Verity login has expired or was revoked \u2014 run "verity login" to sign in again.'
11270
+ };
11271
+ }
11272
+ return null;
11273
+ }
11274
+ async function probeService(serviceUrl, verbose) {
11275
+ const res = await apiRequest({
11276
+ method: "GET",
11277
+ path: "/auth/whoami",
11278
+ serviceUrl,
11279
+ verbose,
11280
+ timeout: 5e3,
11281
+ cmd: "probe"
11282
+ });
11283
+ if (res.ok || res.http_status != null) return { reachable: true };
11284
+ return { reachable: false, dnsDead: /\bENOTFOUND\b/.test(res.error), error: res.error };
11285
+ }
11286
+ async function maybeHealServiceUrl(resolution, verbose) {
11287
+ if (!isHealCandidate(resolution)) {
11288
+ return { serviceUrl: resolution.url, healed: false };
11289
+ }
11290
+ const probe = await probeService(resolution.url, verbose);
11291
+ if (probe.reachable) {
11292
+ return { serviceUrl: resolution.url, healed: false };
11293
+ }
11294
+ const from = resolution.source === "credentials" ? "~/.verity/credentials" : "VERITY.md";
11295
+ printWarn(`Your configured Verity service URL is unreachable: ${resolution.url}`);
11296
+ printInfo(` (${probe.error})`);
11297
+ if (probe.dnsDead && (await probeService(DEFAULT_SERVICE_URL, verbose)).reachable) {
11298
+ printInfo(` The hostname no longer exists \u2014 the URL in ${from} is stale (e.g. a retired preview backend).`);
11299
+ printInfo(` Falling back to the default Verity service: ${DEFAULT_SERVICE_URL}`);
11300
+ if (resolution.source === "verity_md") {
11301
+ printWarn(` Note: VERITY.md still contains the stale URL \u2014 update it to ${DEFAULT_SERVICE_URL} and commit.`);
11302
+ }
11303
+ return { serviceUrl: DEFAULT_SERVICE_URL, healed: true };
11304
+ }
11305
+ printInfo(" Continuing against the configured URL. If it is stale, log in against the default with:");
11306
+ printInfo(` VERITY_SERVICE_URL=${DEFAULT_SERVICE_URL} verity login`);
11307
+ return { serviceUrl: resolution.url, healed: false };
11308
+ }
11309
+
11310
+ // src/lib/register.ts
11311
+ var readline = __toESM(require("node:readline/promises"));
11312
+ var import_node_os = require("node:os");
11313
+
11314
+ // src/lib/provider-auth.ts
11315
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
11316
+ var form = (fields) => new URLSearchParams(fields).toString();
11317
+ async function githubAccountId(owner) {
11318
+ try {
11319
+ const res = await fetch(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
11320
+ headers: {
11321
+ Accept: "application/vnd.github+json",
11322
+ "X-GitHub-Api-Version": "2022-11-28",
11323
+ "User-Agent": "verity-cli"
11324
+ }
11325
+ });
11326
+ if (!res.ok) return null;
11327
+ const body = await res.json();
11328
+ return typeof body.id === "number" ? body.id : null;
11329
+ } catch {
11330
+ return null;
11331
+ }
11332
+ }
11333
+ async function githubCanSeeRepo(owner, repo, token) {
11334
+ let res;
11335
+ try {
11336
+ res = await fetch(
11337
+ `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
11338
+ {
11339
+ headers: {
11340
+ Authorization: `Bearer ${token}`,
11341
+ Accept: "application/vnd.github+json",
11342
+ "X-GitHub-Api-Version": "2022-11-28",
11343
+ "User-Agent": "verity-cli"
11344
+ }
11345
+ }
11346
+ );
11347
+ } catch (err) {
11348
+ return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
11349
+ }
11350
+ if (res.status === 404) return { ok: true, data: false };
11351
+ if (res.ok) return { ok: true, data: true };
11352
+ return { ok: false, error: `GitHub repo lookup failed (HTTP ${res.status})` };
11353
+ }
11354
+ async function githubDeviceFlow() {
11355
+ const override = process.env.VERITY_PROVIDER_TOKEN;
11356
+ if (override) return { ok: true, data: override };
11357
+ let dc;
11358
+ try {
11359
+ const res = await fetch(GITHUB_DEVICE_CODE_URL, {
11360
+ method: "POST",
11361
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
11362
+ body: form({ client_id: GITHUB_CLIENT_ID })
11363
+ });
11364
+ if (!res.ok) {
11365
+ return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
11366
+ }
11367
+ dc = await res.json();
11368
+ } catch (err) {
11369
+ return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
11370
+ }
11371
+ if (!dc.device_code || !dc.user_code) {
11372
+ return {
11373
+ ok: false,
11374
+ error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
11375
+ };
11376
+ }
11377
+ printInfo("");
11378
+ printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
11379
+ printInfo(`And enter the code: ${dc.user_code}`);
11380
+ printInfo("Waiting for authorization\u2026");
11381
+ const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
11382
+ let interval = dc.interval || 5;
11383
+ while (Date.now() < deadline) {
11384
+ await sleep(interval * 1e3);
11385
+ let data;
11386
+ try {
11387
+ const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
11388
+ method: "POST",
11389
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
11390
+ body: form({
11391
+ client_id: GITHUB_CLIENT_ID,
11392
+ device_code: dc.device_code,
11393
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
11394
+ })
11395
+ });
11396
+ data = await res.json().catch(() => ({}));
11397
+ } catch {
11398
+ continue;
11399
+ }
11400
+ if (data.access_token) return { ok: true, data: data.access_token };
11401
+ switch (data.error) {
11402
+ case "authorization_pending":
11403
+ break;
11404
+ case "slow_down":
11405
+ interval += 5;
11406
+ break;
11407
+ case "access_denied":
11408
+ return { ok: false, error: "Authorization was denied on GitHub." };
11409
+ case "expired_token":
11410
+ return { ok: false, error: "The authorization code expired. Re-run register." };
11411
+ default:
11412
+ if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
11413
+ }
11414
+ }
11415
+ return { ok: false, error: "Timed out waiting for GitHub authorization." };
11416
+ }
11417
+
11418
+ // src/lib/register.ts
11419
+ var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
11420
+ async function waitForEnter(prompt) {
11421
+ if (!isInteractive()) return;
11422
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
11423
+ try {
11424
+ await rl.question(prompt);
11425
+ } finally {
11426
+ rl.close();
11427
+ }
11428
+ }
11429
+ async function ensureAppInstalled(owner, repo, token) {
11430
+ let installUrl = null;
11431
+ const maxAttempts = 3;
11432
+ for (let attempt = 1; ; attempt++) {
11433
+ const visible = await githubCanSeeRepo(owner, repo, token);
11434
+ if (!visible.ok) return { ok: true, data: void 0 };
11435
+ if (visible.data) return { ok: true, data: void 0 };
11436
+ if (installUrl === null) installUrl = githubAppInstallUrl(await githubAccountId(owner));
11437
+ if (!isInteractive() || attempt >= maxAttempts) {
11438
+ return {
11439
+ ok: false,
11440
+ error: `The Verity GitHub App can't see ${owner}/${repo}. Install it on "${owner}" and grant access to this repository, then re-run:
11441
+ ${installUrl}`
11442
+ };
11443
+ }
11444
+ printWarn(`The Verity GitHub App can't see ${owner}/${repo} yet.`);
11445
+ printInfo(`Install it on "${owner}" (grant access to this repo):`);
11446
+ printInfo(` ${installUrl}`);
11447
+ await waitForEnter("Press Enter once installed to retry\u2026 ");
11448
+ }
11449
+ }
11450
+ async function registerProject(opts) {
11451
+ const parsed = parseRemote(opts.remote);
11452
+ if (!parsed) {
11453
+ return { ok: false, error: `Could not parse git remote: ${opts.remote}` };
11454
+ }
11455
+ if (parsed.provider !== "github") {
11456
+ return { ok: false, error: `Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.` };
11457
+ }
11353
11458
  const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
11354
11459
  const providerAuth = await githubDeviceFlow();
11355
11460
  if (!providerAuth.ok) {
@@ -11392,11 +11497,81 @@ async function registerProject(opts) {
11392
11497
  }
11393
11498
  return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email, userId } };
11394
11499
  }
11500
+ function deviceLabel() {
11501
+ const override = process.env.VERITY_DEVICE_LABEL?.trim();
11502
+ if (override) return override;
11503
+ try {
11504
+ return (0, import_node_os.hostname)() || void 0;
11505
+ } catch {
11506
+ return void 0;
11507
+ }
11508
+ }
11509
+ async function loginOnce(opts) {
11510
+ const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
11511
+ const providerAuth = await githubDeviceFlow();
11512
+ if (!providerAuth.ok) {
11513
+ return { ok: false, error: providerAuth.error };
11514
+ }
11515
+ const providerToken = providerAuth.data;
11516
+ const parsed = opts.remote ? parseRemote(opts.remote) : null;
11517
+ if (!usingTokenOverride && parsed && parsed.provider === "github") {
11518
+ const installed = await ensureAppInstalled(parsed.owner, parsed.repo, providerToken);
11519
+ if (!installed.ok) {
11520
+ printWarn(installed.error);
11521
+ printInfo("Continuing login \u2014 repositories on other accounts are still granted.");
11522
+ }
11523
+ }
11524
+ const result = await apiRequest({
11525
+ method: "POST",
11526
+ path: "/auth/login",
11527
+ serviceUrl: opts.serviceUrl,
11528
+ extraHeaders: { "X-Provider-Token": providerToken },
11529
+ // Label the session so its owner can tell their machines apart in
11530
+ // `verity sessions list` — a list of identical "login" rows is unusable when
11531
+ // the question is "which of these is the laptop I lost?". The hostname is the
11532
+ // useful default; VERITY_DEVICE_LABEL overrides it for anyone who would
11533
+ // rather not send it. Server-side it is sanitized and capped.
11534
+ body: { device: deviceLabel() },
11535
+ verbose: opts.verbose,
11536
+ cmd: "login"
11537
+ });
11538
+ if (!result.ok) {
11539
+ return { ok: false, error: result.error };
11540
+ }
11541
+ const { token, service_url, user_id, user, repo_count, expires_at } = result.data;
11542
+ const loginUserId = user_id ?? user?.id ?? void 0;
11543
+ try {
11544
+ await upsertGlobalCredential("", {
11545
+ token,
11546
+ serviceUrl: service_url,
11547
+ userId: loginUserId,
11548
+ email: user?.email
11549
+ });
11550
+ } catch (err) {
11551
+ return {
11552
+ ok: false,
11553
+ 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".`
11554
+ };
11555
+ }
11556
+ const pruned = await removeSupersededUserCredentials(service_url, loginUserId);
11557
+ return {
11558
+ ok: true,
11559
+ data: {
11560
+ token,
11561
+ serviceUrl: service_url,
11562
+ email: user?.email,
11563
+ userId: loginUserId,
11564
+ repoCount: repo_count ?? 0,
11565
+ prunedCredentials: pruned,
11566
+ expiresAt: expires_at
11567
+ }
11568
+ };
11569
+ }
11395
11570
 
11396
11571
  // src/commands/auth.ts
11397
11572
  function registerAuthCommands(program2) {
11398
- const auth = program2.command("auth").description("Manage project authentication");
11399
- auth.command("register").description("Register a project with Verity").requiredOption("--project <name>", "Project name").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
11573
+ const auth2 = program2.command("auth").description("Manage project authentication");
11574
+ auth2.command("register").description("Register a project with Verity").requiredOption("--project <name>", "Project name").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
11400
11575
  const globals = program2.opts();
11401
11576
  const serviceUrl = globals.serviceUrl ?? DEFAULT_SERVICE_URL;
11402
11577
  let remote = opts.remote;
@@ -11423,7 +11598,7 @@ function registerAuthCommands(program2) {
11423
11598
  if (email) printInfo(`Authenticated as: ${email}`);
11424
11599
  printJson({ project_id: projectId, service_url: resolvedUrl });
11425
11600
  });
11426
- auth.command("verify").description("Verify the current token is valid").action(async () => {
11601
+ auth2.command("verify").description("Verify the current token is valid").action(async () => {
11427
11602
  const globals = program2.opts();
11428
11603
  const tokenResult = await resolveToken(globals.token);
11429
11604
  if (!tokenResult.ok) {
@@ -11449,7 +11624,7 @@ function registerAuthCommands(program2) {
11449
11624
  printInfo(`Token valid. Project: ${result.data.project_name}`);
11450
11625
  printJson(result.data);
11451
11626
  });
11452
- auth.command("discover").description("Check if a project is registered").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
11627
+ auth2.command("discover").description("Check if a project is registered").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
11453
11628
  const globals = program2.opts();
11454
11629
  let remote = opts.remote;
11455
11630
  if (!remote) {
@@ -11481,10 +11656,8 @@ function registerAuthCommands(program2) {
11481
11656
  }
11482
11657
 
11483
11658
  // src/commands/login.ts
11484
- var import_node_child_process5 = require("node:child_process");
11485
- var import_node_path4 = require("node:path");
11486
11659
  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) => {
11660
+ 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
11661
  const globals = program2.opts();
11489
11662
  const urlResult = await resolveServiceUrlDetailed(globals.serviceUrl);
11490
11663
  if (!urlResult.ok) {
@@ -11494,60 +11667,383 @@ function registerLoginCommand(program2) {
11494
11667
  const heal = await maybeHealServiceUrl(urlResult.data, globals.verbose);
11495
11668
  const serviceUrl = heal.serviceUrl;
11496
11669
  if (heal.healed) {
11497
- printInfo(" Completing login re-registers this project and updates ~/.verity/credentials.");
11670
+ printInfo(" Completing login updates ~/.verity/credentials against the live service.");
11671
+ }
11672
+ const remote = currentRemote();
11673
+ if (remote && !parseRemote(remote)) {
11674
+ printWarn(`This repository's origin remote is not a recognizable git URL: ${remote}`);
11675
+ printInfo(" Verity cannot identify the repository from it, so runs here will not be saved.");
11676
+ printInfo(" Point origin at the full URL (e.g. git@github.com:owner/repo.git) to fix it.");
11498
11677
  }
11499
11678
  const existing = await resolveToken(globals.token);
11500
11679
  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) {
11680
+ const who = await whoami(existing.data.token, serviceUrl, globals.verbose);
11681
+ if (who.ok && who.data.logged_in) {
11682
+ const nudge = reverifyNudge(who.data);
11683
+ if (!nudge) {
11684
+ printInfo(`Already logged in as ${who.data.email ?? `user #${who.data.user_id}`}. \u2713`);
11685
+ printInfo(" Re-authenticate with: verity login --force");
11686
+ return;
11687
+ }
11688
+ printWarn(nudge);
11689
+ printInfo("Re-verifying your repository access\u2026");
11690
+ } else if (who.ok && who.data.anonymous) {
11513
11691
  printInfo("You have an anonymous token (the gate runs, but nothing is saved). Logging you in\u2026");
11514
- } else if (!who2.ok) {
11692
+ } else if (!who.ok) {
11515
11693
  printWarn("Could not confirm your current login state with the service \u2014 proceeding to log in.");
11516
11694
  }
11517
11695
  }
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 {
11696
+ printInfo("Authenticating with GitHub\u2026");
11697
+ const result = await loginOnce({ serviceUrl, remote: remote || void 0, verbose: globals.verbose });
11698
+ if (!result.ok) {
11699
+ printError(`Login failed: ${result.error}`);
11700
+ process.exit(1);
11522
11701
  }
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.");
11702
+ const out = result.data;
11703
+ const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
11704
+ printInfo(`Logged in as ${identity}. \u2713`);
11705
+ printInfo(` Access granted to ${out.repoCount} ${out.repoCount === 1 ? "repository" : "repositories"}.`);
11706
+ if (out.expiresAt) {
11707
+ printInfo(` This login expires on ${out.expiresAt.slice(0, 10)} \u2014 "verity login" again to renew.`);
11708
+ printInfo(' See your machines with "verity sessions list"; sign out with "verity logout".');
11709
+ }
11710
+ printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
11711
+ if (out.prunedCredentials > 0) {
11712
+ printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, globals.verbose);
11713
+ } else if (out.prunedCredentials < 0) {
11714
+ printWarn(" Could not rewrite ~/.verity/credentials: superseded per-repository tokens remain and");
11715
+ printWarn(" will keep taking precedence over this login in their own repositories.");
11716
+ printInfo(` Check the file's permissions; the next successful "verity login" retries the cleanup.`);
11717
+ }
11718
+ if (out.repoCount === 0) {
11719
+ printWarn("The Verity GitHub App is not installed on any account you can access.");
11720
+ printInfo(` Install it (and grant your repositories), then re-run verity login:`);
11721
+ printInfo(` ${githubAppInstallUrl(null)}`);
11722
+ return;
11723
+ }
11724
+ if (remote) {
11725
+ const who = await whoami(out.token, out.serviceUrl, globals.verbose);
11726
+ if (who.ok && who.data.grant_status != null) {
11727
+ printInfo(" \u2713 This repository is covered.");
11728
+ } else if (!who.ok) {
11729
+ printWarn(` Could not confirm this repository's coverage (${who.error}) \u2014 verity status will show it.`);
11730
+ } else {
11731
+ const parsed = parseRemote(remote);
11732
+ const installUrl = githubAppInstallUrl(parsed ? await githubAccountId(parsed.owner) : null);
11733
+ printWarn(` This repository (${parsed ? `${parsed.owner}/${parsed.repo}` : remote}) is NOT covered by your grants.`);
11734
+ printInfo(" Grant the Verity GitHub App access to it, then re-run verity login:");
11735
+ printInfo(` ${installUrl}`);
11736
+ }
11737
+ const rec = await readGlobalCredential(remote);
11738
+ if (rec && rec.token !== out.token) {
11739
+ const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
11740
+ if (otherBackend) {
11741
+ printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
11742
+ printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
11743
+ printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
11744
+ } else {
11745
+ const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
11746
+ printWarn(` Note: this repository has a ${kind} credential that takes`);
11747
+ printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
11748
+ }
11749
+ }
11750
+ }
11751
+ });
11752
+ }
11753
+
11754
+ // src/commands/token.ts
11755
+ function registerTokenCommand(program2) {
11756
+ const token = program2.command("token").description("Manage service (CI/machine) tokens for this repository");
11757
+ async function requireAuth(globals) {
11758
+ const tokenResult = await resolveToken(globals.token);
11759
+ if (!tokenResult.ok) {
11760
+ printError(tokenResult.error);
11526
11761
  process.exit(1);
11527
11762
  }
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 });
11763
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
11764
+ if (!urlResult.ok) {
11765
+ printError(urlResult.error);
11766
+ process.exit(1);
11767
+ }
11768
+ return { bearer: tokenResult.data.token, serviceUrl: urlResult.data };
11769
+ }
11770
+ function explainDenial(error) {
11771
+ if (error.startsWith("STALE_VERIFICATION")) {
11772
+ printInfo(' Your GitHub verification has expired \u2014 run "verity login", then retry.');
11773
+ } else if (error.startsWith("FORBIDDEN")) {
11774
+ printInfo(" You need write access to this repository. If it was added recently,");
11775
+ printInfo(' run "verity login" to refresh your grants.');
11776
+ } else if (error.startsWith("INVALID_REQUEST")) {
11777
+ printInfo(" Tokens are managed per repository \u2014 run this inside a repository with a");
11778
+ printInfo(" git remote (origin), so Verity knows which project the token belongs to.");
11779
+ }
11780
+ }
11781
+ 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) => {
11782
+ const globals = program2.opts();
11783
+ const { bearer, serviceUrl } = await requireAuth(globals);
11784
+ let expiresInDays;
11785
+ if (opts.expires != null) {
11786
+ expiresInDays = Number(opts.expires);
11787
+ if (!Number.isInteger(expiresInDays) || expiresInDays <= 0 || expiresInDays > 3650) {
11788
+ printError("--expires must be a whole number of days between 1 and 3650");
11789
+ process.exit(1);
11790
+ }
11791
+ }
11792
+ const result = await apiRequest({
11793
+ method: "POST",
11794
+ path: "/auth/tokens",
11795
+ serviceUrl,
11796
+ token: bearer,
11797
+ body: {
11798
+ agent_name: String(opts.name).trim(),
11799
+ token_type: "service",
11800
+ ...expiresInDays != null ? { expires_in_days: expiresInDays } : {}
11801
+ },
11802
+ verbose: globals.verbose,
11803
+ cmd: "token-create"
11804
+ });
11531
11805
  if (!result.ok) {
11532
- printError(`Login failed: ${result.error}`);
11806
+ printError(`Could not create the service token: ${result.error}`);
11807
+ explainDenial(result.error);
11808
+ process.exit(1);
11809
+ }
11810
+ printInfo(`Service token "${result.data.agent_name}" created. \u2713`);
11811
+ printInfo("");
11812
+ printInfo(` ${result.data.token}`);
11813
+ printInfo("");
11814
+ printWarn("This token is shown ONCE \u2014 store it now (e.g. as a VERITY_TOKEN CI secret).");
11815
+ if (result.data.expires_at) {
11816
+ printInfo(` Expires: ${result.data.expires_at.slice(0, 10)}`);
11817
+ }
11818
+ printInfo(" Any current writer of this repository can revoke it: verity token revoke <id>");
11819
+ printInfo(` Token id: ${result.data.token_id}`);
11820
+ });
11821
+ token.command("list").description("List this repository's tokens (ids and metadata only \u2014 never secrets)").action(async () => {
11822
+ const globals = program2.opts();
11823
+ const { bearer, serviceUrl } = await requireAuth(globals);
11824
+ const result = await apiRequest({
11825
+ method: "GET",
11826
+ path: "/auth/tokens",
11827
+ serviceUrl,
11828
+ token: bearer,
11829
+ verbose: globals.verbose,
11830
+ cmd: "token-list"
11831
+ });
11832
+ if (!result.ok) {
11833
+ printError(`Could not list tokens: ${result.error}`);
11834
+ explainDenial(result.error);
11835
+ process.exit(1);
11836
+ }
11837
+ const tokens = result.data.tokens ?? [];
11838
+ if (tokens.length === 0) {
11839
+ printInfo("No tokens found for this repository.");
11840
+ return;
11841
+ }
11842
+ for (const t of tokens) {
11843
+ const type = t.token_type ?? "user";
11844
+ const expires = t.expires_at ? `expires ${t.expires_at.slice(0, 10)}` : "no expiry";
11845
+ const lastUsed = t.last_used_at ? `last used ${t.last_used_at.slice(0, 10)}` : "never used";
11846
+ printInfo(`${t.id} [${type}] ${t.agent_name ?? "unnamed"} (${expires}, ${lastUsed})`);
11847
+ }
11848
+ });
11849
+ token.command("revoke <id>").description("Revoke a token by id (service tokens: any current writer may revoke)").action(async (id) => {
11850
+ const globals = program2.opts();
11851
+ const { bearer, serviceUrl } = await requireAuth(globals);
11852
+ const result = await apiRequest({
11853
+ method: "DELETE",
11854
+ path: `/auth/tokens/${encodeURIComponent(id)}`,
11855
+ serviceUrl,
11856
+ token: bearer,
11857
+ verbose: globals.verbose,
11858
+ cmd: "token-revoke"
11859
+ });
11860
+ if (!result.ok) {
11861
+ printError(`Could not revoke the token: ${result.error}`);
11862
+ explainDenial(result.error);
11863
+ process.exit(1);
11864
+ }
11865
+ printInfo(`Token ${result.data.token_id} revoked. \u2713`);
11866
+ });
11867
+ }
11868
+
11869
+ // src/commands/sessions.ts
11870
+ function shortDate(iso) {
11871
+ return iso ? iso.slice(0, 10) : "\u2014";
11872
+ }
11873
+ function daysUntil(iso) {
11874
+ if (!iso) return null;
11875
+ const ms = Date.parse(iso);
11876
+ if (Number.isNaN(ms)) return null;
11877
+ return Math.round((ms - Date.now()) / 864e5);
11878
+ }
11879
+ async function auth(globals) {
11880
+ const tokenResult = await resolveToken(globals.token);
11881
+ if (!tokenResult.ok) {
11882
+ printError(tokenResult.error);
11883
+ process.exit(1);
11884
+ }
11885
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
11886
+ if (!urlResult.ok) {
11887
+ printError(urlResult.error);
11888
+ process.exit(1);
11889
+ }
11890
+ return { token: tokenResult.data.token, serviceUrl: urlResult.data };
11891
+ }
11892
+ function explain(error) {
11893
+ if (error.startsWith("FORBIDDEN")) {
11894
+ printInfo(' Sessions belong to a logged-in account \u2014 run "verity login" first.');
11895
+ } else if (error.startsWith("INVALID_TOKEN")) {
11896
+ printInfo(' This login has expired or was revoked \u2014 run "verity login" to sign in again.');
11897
+ }
11898
+ }
11899
+ function registerSessionsCommands(program2) {
11900
+ const sessions = program2.command("sessions").description("List and revoke your Verity logins (one per machine)");
11901
+ sessions.command("list").description("List your active logins \u2014 device, last use, and expiry").option("--json", "Output raw JSON").action(async (opts) => {
11902
+ const globals = program2.opts();
11903
+ const { token, serviceUrl } = await auth(globals);
11904
+ const result = await apiRequest({
11905
+ method: "GET",
11906
+ path: "/auth/sessions",
11907
+ serviceUrl,
11908
+ token,
11909
+ verbose: globals.verbose,
11910
+ cmd: "sessions"
11911
+ });
11912
+ if (!result.ok) {
11913
+ printError(result.error);
11914
+ explain(result.error);
11915
+ process.exit(1);
11916
+ }
11917
+ if (opts.json) {
11918
+ printJson(result.data);
11919
+ return;
11920
+ }
11921
+ const list = result.data.sessions;
11922
+ if (list.length === 0) {
11923
+ printInfo('No active logins. (Run "verity login".)');
11924
+ return;
11925
+ }
11926
+ printInfo(`${list.length} active login${list.length === 1 ? "" : "s"}:`);
11927
+ printInfo("");
11928
+ printInfo(`${"SESSION ID".padEnd(38)}${"DEVICE".padEnd(24)}${"CREATED".padEnd(12)}${"LAST USED".padEnd(12)}EXPIRES`);
11929
+ for (const s of list) {
11930
+ const days = daysUntil(s.expires_at);
11931
+ const expiry = s.expires_at ? `${shortDate(s.expires_at)}${days != null ? ` (${days}d)` : ""}` : "never";
11932
+ const device = (s.device ?? "login").slice(0, 22);
11933
+ printInfo(
11934
+ `${s.id.padEnd(38)}${device.padEnd(24)}${shortDate(s.created_at).padEnd(12)}${shortDate(s.last_used_at).padEnd(12)}${expiry}${s.current ? " \u2190 this machine" : ""}`
11935
+ );
11936
+ }
11937
+ printInfo("");
11938
+ printInfo("Revoke one: verity sessions revoke <session-id>");
11939
+ printInfo("Sign out everywhere else: verity logout --others");
11940
+ });
11941
+ sessions.command("revoke <session-id>").description("Revoke one login. Revoking this machine's also clears the local credential").action(async (sessionId) => {
11942
+ const globals = program2.opts();
11943
+ const { token, serviceUrl } = await auth(globals);
11944
+ const result = await apiRequest({
11945
+ method: "DELETE",
11946
+ path: `/auth/sessions/${encodeURIComponent(sessionId)}`,
11947
+ serviceUrl,
11948
+ token,
11949
+ verbose: globals.verbose,
11950
+ cmd: "sessions-revoke"
11951
+ });
11952
+ if (!result.ok) {
11953
+ printError(result.error);
11954
+ if (result.http_status === 404) {
11955
+ printInfo(' No session with that id on your account \u2014 check "verity sessions list".');
11956
+ }
11957
+ explain(result.error);
11958
+ process.exit(1);
11959
+ }
11960
+ printInfo(`Session ${sessionId} revoked. \u2713`);
11961
+ if (result.data.was_current) {
11962
+ const cleared = await removeGlobalCredential("");
11963
+ printInfo(cleared ? ' That was this machine \u2014 local credential cleared. Run "verity login" to sign back in.' : ' That was this machine. Run "verity login" to sign back in.');
11964
+ }
11965
+ });
11966
+ }
11967
+ function registerLogoutCommand(program2) {
11968
+ program2.command("logout").description("Sign out of Verity on this machine (--all / --others for every machine)").option("--all", "Revoke every login on every machine, including this one").option("--others", "Revoke every login EXCEPT this machine (e.g. a lost laptop)").action(async (opts) => {
11969
+ const globals = program2.opts();
11970
+ if (opts.all && opts.others) {
11971
+ printError("Use either --all or --others, not both.");
11972
+ process.exit(1);
11973
+ }
11974
+ const { token, serviceUrl } = await auth(globals);
11975
+ if (opts.all || opts.others) {
11976
+ const result = await apiRequest({
11977
+ method: "DELETE",
11978
+ path: opts.others ? "/auth/sessions?others=true" : "/auth/sessions",
11979
+ serviceUrl,
11980
+ token,
11981
+ verbose: globals.verbose,
11982
+ cmd: "logout"
11983
+ });
11984
+ if (!result.ok) {
11985
+ printError(result.error);
11986
+ explain(result.error);
11987
+ process.exit(1);
11988
+ }
11989
+ const n = result.data.revoked;
11990
+ printInfo(`Revoked ${n} login${n === 1 ? "" : "s"}. \u2713`);
11991
+ if (opts.others) {
11992
+ printInfo(" This machine is still signed in.");
11993
+ return;
11994
+ }
11995
+ const cleared2 = await removeGlobalCredential("");
11996
+ if (cleared2) printInfo(" Local credential cleared.");
11997
+ printInfo(' Run "verity login" to sign back in.');
11998
+ return;
11999
+ }
12000
+ const list = await apiRequest({
12001
+ method: "GET",
12002
+ path: "/auth/sessions",
12003
+ serviceUrl,
12004
+ token,
12005
+ verbose: globals.verbose,
12006
+ cmd: "logout"
12007
+ });
12008
+ if (!list.ok) {
12009
+ printError(list.error);
12010
+ explain(list.error);
11533
12011
  process.exit(1);
11534
12012
  }
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.)");
12013
+ const current = list.data.sessions.find((s) => s.current);
12014
+ if (!current) {
12015
+ printWarn("This machine is not signed in with a Verity login.");
12016
+ const cleared2 = await removeGlobalCredential("");
12017
+ if (cleared2) printInfo(" Cleared the local login credential anyway.");
12018
+ return;
12019
+ }
12020
+ const revoked = await apiRequest({
12021
+ method: "DELETE",
12022
+ path: `/auth/sessions/${current.id}`,
12023
+ serviceUrl,
12024
+ token,
12025
+ verbose: globals.verbose,
12026
+ cmd: "logout"
12027
+ });
12028
+ if (!revoked.ok) {
12029
+ printError(revoked.error);
12030
+ explain(revoked.error);
12031
+ process.exit(1);
11540
12032
  }
12033
+ const cleared = await removeGlobalCredential("");
12034
+ printInfo("Signed out on this machine. \u2713");
12035
+ if (cleared) printInfo(" Local credential cleared.");
12036
+ printInfo(' Your other machines are unaffected \u2014 use "verity logout --all" for all of them.');
11541
12037
  });
11542
12038
  }
11543
12039
 
11544
12040
  // src/lib/hooks.ts
11545
12041
  var import_promises4 = require("node:fs/promises");
11546
- var import_node_path6 = require("node:path");
12042
+ var import_node_path5 = require("node:path");
11547
12043
 
11548
12044
  // src/lib/json-file.ts
11549
12045
  var import_promises3 = require("node:fs/promises");
11550
- var import_node_path5 = require("node:path");
12046
+ var import_node_path4 = require("node:path");
11551
12047
  function jsonSemanticEqual(a, b) {
11552
12048
  if (a === b) return true;
11553
12049
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
@@ -11593,7 +12089,7 @@ async function writeJsonFilePreservingStyle(file, value) {
11593
12089
  const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
11594
12090
  const next = JSON.stringify(value, null, indent) + "\n";
11595
12091
  if (next === currentRaw) return false;
11596
- await (0, import_promises3.mkdir)((0, import_node_path5.dirname)(file), { recursive: true });
12092
+ await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
11597
12093
  await (0, import_promises3.writeFile)(file, next);
11598
12094
  return true;
11599
12095
  }
@@ -11766,13 +12262,13 @@ async function writeSettings(settings) {
11766
12262
  }
11767
12263
  async function readSettingsAt(root) {
11768
12264
  try {
11769
- return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path6.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
12265
+ return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11770
12266
  } catch {
11771
12267
  return {};
11772
12268
  }
11773
12269
  }
11774
12270
  async function writeSettingsAt(root, settings) {
11775
- await writeJsonFilePreservingStyle((0, import_node_path6.join)(root, CLAUDE_SETTINGS_FILE), settings);
12271
+ await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
11776
12272
  }
11777
12273
  async function hasLegacyHooksAt(root) {
11778
12274
  const settings = await readSettingsAt(root);
@@ -12045,7 +12541,7 @@ var import_node_crypto6 = require("node:crypto");
12045
12541
  // src/lib/conversation-buffer.ts
12046
12542
  var import_promises5 = require("node:fs/promises");
12047
12543
  var import_node_fs4 = require("node:fs");
12048
- var import_node_child_process6 = require("node:child_process");
12544
+ var import_node_child_process5 = require("node:child_process");
12049
12545
  var import_node_crypto = require("node:crypto");
12050
12546
  function stripImageReferences(text) {
12051
12547
  return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
@@ -12148,7 +12644,7 @@ async function readBufferEntries() {
12148
12644
  }
12149
12645
  function getRecentCommitMessages() {
12150
12646
  try {
12151
- const output = (0, import_node_child_process6.execSync)(
12647
+ const output = (0, import_node_child_process5.execSync)(
12152
12648
  'git log --since="30 minutes ago" --format="%s" -5',
12153
12649
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
12154
12650
  ).trim();
@@ -12162,8 +12658,8 @@ function getRecentCommitMessages() {
12162
12658
  // src/lib/context-identity.ts
12163
12659
  var import_node_crypto2 = require("node:crypto");
12164
12660
  var import_node_fs5 = require("node:fs");
12165
- var import_node_os = require("node:os");
12166
- var import_node_path7 = require("node:path");
12661
+ var import_node_os2 = require("node:os");
12662
+ var import_node_path6 = require("node:path");
12167
12663
  var SHARED_SENTINELS = /* @__PURE__ */ new Set([
12168
12664
  "",
12169
12665
  "-",
@@ -12217,13 +12713,13 @@ function contextIdentity(input) {
12217
12713
  }
12218
12714
  function verityHome() {
12219
12715
  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");
12716
+ return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".verity");
12221
12717
  }
12222
12718
  function dossierDir(identity) {
12223
- return (0, import_node_path7.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
12719
+ return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
12224
12720
  }
12225
12721
  function treeDir(identity) {
12226
- return (0, import_node_path7.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
12722
+ return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
12227
12723
  }
12228
12724
  function scopeIdentity(token, sessionId) {
12229
12725
  const t = (token ?? "").trim();
@@ -12239,7 +12735,7 @@ function sessionScopeKey(token, sessionId) {
12239
12735
  // src/lib/task-context-buffer.ts
12240
12736
  var import_promises6 = require("node:fs/promises");
12241
12737
  var import_node_fs6 = require("node:fs");
12242
- var import_node_path8 = require("node:path");
12738
+ var import_node_path7 = require("node:path");
12243
12739
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
12244
12740
  var MAX_BUFFER_BYTES = 500 * 1024;
12245
12741
  var MAX_PROMPT_CHARS = 2e3;
@@ -12317,7 +12813,7 @@ async function cleanupTaskContextBuffers() {
12317
12813
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
12318
12814
  for (const file of files) {
12319
12815
  if (!file.endsWith(".jsonl")) continue;
12320
- const filePath = (0, import_node_path8.join)(TASK_CONTEXT_DIR, file);
12816
+ const filePath = (0, import_node_path7.join)(TASK_CONTEXT_DIR, file);
12321
12817
  try {
12322
12818
  const stats = await (0, import_promises6.stat)(filePath);
12323
12819
  if (stats.mtimeMs < cutoffMs) {
@@ -12331,7 +12827,7 @@ async function cleanupTaskContextBuffers() {
12331
12827
  }
12332
12828
  function bufferPath(taskId) {
12333
12829
  const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
12334
- return (0, import_node_path8.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
12830
+ return (0, import_node_path7.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
12335
12831
  }
12336
12832
  async function appendEntry(taskId, entry) {
12337
12833
  try {
@@ -12357,7 +12853,7 @@ async function appendEntry(taskId, entry) {
12357
12853
  // src/lib/memory-retrieval.ts
12358
12854
  var import_promises7 = require("node:fs/promises");
12359
12855
  var import_node_fs7 = require("node:fs");
12360
- var import_node_path9 = require("node:path");
12856
+ var import_node_path8 = require("node:path");
12361
12857
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
12362
12858
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
12363
12859
  var DEFAULT_BUDGET_TOKENS = 2e3;
@@ -12455,14 +12951,14 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
12455
12951
  const promptTokens = tokenize(promptText);
12456
12952
  const nodes = [];
12457
12953
  for (const domain of DOMAINS) {
12458
- const domainDir = (0, import_node_path9.join)(memoryDir(), domain);
12954
+ const domainDir = (0, import_node_path8.join)(memoryDir(), domain);
12459
12955
  if (!(0, import_node_fs7.existsSync)(domainDir)) continue;
12460
12956
  try {
12461
12957
  const files = await (0, import_promises7.readdir)(domainDir);
12462
12958
  for (const file of files) {
12463
12959
  if (!file.endsWith(".md")) continue;
12464
12960
  try {
12465
- const content = await (0, import_promises7.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
12961
+ const content = await (0, import_promises7.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
12466
12962
  const { fm, body } = parseFrontmatter(content);
12467
12963
  if (fm.status && fm.status !== "active") continue;
12468
12964
  nodes.push({
@@ -12522,7 +13018,7 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
12522
13018
  // src/lib/memory-sync.ts
12523
13019
  var import_promises8 = require("node:fs/promises");
12524
13020
  var import_node_fs8 = require("node:fs");
12525
- var import_node_path10 = require("node:path");
13021
+ var import_node_path9 = require("node:path");
12526
13022
  var import_node_crypto3 = require("node:crypto");
12527
13023
 
12528
13024
  // src/lib/glob-match.ts
@@ -12593,16 +13089,16 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
12593
13089
  async function ensureMemoryDir() {
12594
13090
  await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
12595
13091
  for (const domain of DOMAINS2) {
12596
- await (0, import_promises8.mkdir)((0, import_node_path10.join)(memoryDir2(), domain), { recursive: true });
13092
+ await (0, import_promises8.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
12597
13093
  }
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);
13094
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"))) {
13095
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12600
13096
  }
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");
13097
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "index.md"))) {
13098
+ 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
13099
  }
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");
13100
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md"))) {
13101
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12606
13102
  }
12607
13103
  }
12608
13104
  async function buildManifest() {
@@ -12611,14 +13107,14 @@ async function buildManifest() {
12611
13107
  }
12612
13108
  const nodes = [];
12613
13109
  for (const domain of DOMAINS2) {
12614
- const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13110
+ const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12615
13111
  if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
12616
13112
  try {
12617
13113
  const files = await (0, import_promises8.readdir)(domainDir);
12618
13114
  for (const file of files) {
12619
13115
  if (!file.endsWith(".md")) continue;
12620
13116
  const filePath = `${domain}/${file}`;
12621
- const fullPath = (0, import_node_path10.join)(memoryDir2(), filePath);
13117
+ const fullPath = (0, import_node_path9.join)(memoryDir2(), filePath);
12622
13118
  try {
12623
13119
  const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
12624
13120
  const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
@@ -12631,13 +13127,13 @@ async function buildManifest() {
12631
13127
  }
12632
13128
  let indexHash = null;
12633
13129
  try {
12634
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "index.md"), "utf-8");
13130
+ const indexContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
12635
13131
  indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
12636
13132
  } catch {
12637
13133
  }
12638
13134
  let logLength = 0;
12639
13135
  try {
12640
- const logContent = await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "utf-8");
13136
+ const logContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
12641
13137
  logLength = logContent.split("\n").length;
12642
13138
  } catch {
12643
13139
  }
@@ -12650,13 +13146,13 @@ async function readOnDiskNodes() {
12650
13146
  const out = /* @__PURE__ */ new Map();
12651
13147
  if (!(0, import_node_fs8.existsSync)(memoryDir2())) return out;
12652
13148
  for (const domain of DOMAINS2) {
12653
- const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13149
+ const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12654
13150
  if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
12655
13151
  try {
12656
13152
  for (const file of await (0, import_promises8.readdir)(domainDir)) {
12657
13153
  if (!file.endsWith(".md")) continue;
12658
13154
  try {
12659
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path10.join)(domainDir, file), "utf-8")));
13155
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
12660
13156
  } catch {
12661
13157
  }
12662
13158
  }
@@ -12702,7 +13198,7 @@ async function computeEditedNodeUploads() {
12702
13198
  const uploads = [];
12703
13199
  for (const [path, prevHash] of prev) {
12704
13200
  if (prevHash == null) continue;
12705
- const full = (0, import_node_path10.join)(memoryDir2(), path);
13201
+ const full = (0, import_node_path9.join)(memoryDir2(), path);
12706
13202
  if (!(0, import_node_fs8.existsSync)(full)) continue;
12707
13203
  let content;
12708
13204
  try {
@@ -12739,15 +13235,15 @@ async function applyMemoryWrites(writes, opts = {}) {
12739
13235
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
12740
13236
  for (const n of notes) logLines.push(` - ${n}`);
12741
13237
  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");
13238
+ 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";
13239
+ await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
12744
13240
  } catch {
12745
13241
  }
12746
13242
  await recordSyncedNodePaths();
12747
13243
  return count;
12748
13244
  }
12749
13245
  async function applyOneWrite(write, treePaths) {
12750
- const fullPath = (0, import_node_path10.join)(memoryDir2(), write.path);
13246
+ const fullPath = (0, import_node_path9.join)(memoryDir2(), write.path);
12751
13247
  const notes = [];
12752
13248
  let content = write.content;
12753
13249
  if (treePaths && treePaths.length > 0) {
@@ -12769,7 +13265,7 @@ async function applyOneWrite(write, treePaths) {
12769
13265
  return { written: false, notes };
12770
13266
  }
12771
13267
  }
12772
- await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(fullPath), { recursive: true });
13268
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
12773
13269
  await (0, import_promises8.writeFile)(fullPath, content);
12774
13270
  return { written: true, notes };
12775
13271
  }
@@ -12810,7 +13306,7 @@ async function regenerateIndex() {
12810
13306
  ];
12811
13307
  let totalNodes = 0;
12812
13308
  for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
12813
- const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13309
+ const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
12814
13310
  if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
12815
13311
  try {
12816
13312
  const files = await (0, import_promises8.readdir)(domainDir);
@@ -12821,7 +13317,7 @@ async function regenerateIndex() {
12821
13317
  for (const file of mdFiles.sort()) {
12822
13318
  const slug = file.replace(/\.md$/, "");
12823
13319
  try {
12824
- const content = await (0, import_promises8.readFile)((0, import_node_path10.join)(domainDir, file), "utf-8");
13320
+ const content = await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
12825
13321
  const title = pickFrontmatter(content, "title") ?? slug;
12826
13322
  const kind = pickFrontmatter(content, "kind") ?? "-";
12827
13323
  const confidence = pickFrontmatter(content, "confidence");
@@ -12845,7 +13341,7 @@ async function regenerateIndex() {
12845
13341
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
12846
13342
  }
12847
13343
  const next = lines.join("\n") + "\n";
12848
- const indexPath = (0, import_node_path10.join)(memoryDir2(), "index.md");
13344
+ const indexPath = (0, import_node_path9.join)(memoryDir2(), "index.md");
12849
13345
  let existing = null;
12850
13346
  try {
12851
13347
  existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
@@ -12927,7 +13423,7 @@ function hasLegacyMemoryBlock(text) {
12927
13423
  return findMarker(text, LEGACY_MD_START) !== -1;
12928
13424
  }
12929
13425
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
12930
- const claudeMdPath = (0, import_node_path10.join)(cwd, "CLAUDE.md");
13426
+ const claudeMdPath = (0, import_node_path9.join)(cwd, "CLAUDE.md");
12931
13427
  let existing = "";
12932
13428
  if ((0, import_node_fs8.existsSync)(claudeMdPath)) {
12933
13429
  existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
@@ -13061,12 +13557,12 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
13061
13557
  // src/lib/dossier-session.ts
13062
13558
  var import_node_fs10 = require("node:fs");
13063
13559
  var import_node_crypto5 = require("node:crypto");
13064
- var import_node_path12 = require("node:path");
13560
+ var import_node_path11 = require("node:path");
13065
13561
 
13066
13562
  // src/lib/dossier.ts
13067
13563
  var import_node_fs9 = require("node:fs");
13068
13564
  var import_node_crypto4 = require("node:crypto");
13069
- var import_node_path11 = require("node:path");
13565
+ var import_node_path10 = require("node:path");
13070
13566
  var MAX_LINE_BYTES = 4096;
13071
13567
  var MAX_GOAL_CHARS = 2e3;
13072
13568
  var GOAL_KEEP = 8;
@@ -13109,9 +13605,9 @@ function openDossier(identity) {
13109
13605
  return {
13110
13606
  dir,
13111
13607
  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")
13608
+ eventsPath: (0, import_node_path10.join)(dir, "events.jsonl"),
13609
+ foldPath: (0, import_node_path10.join)(dir, "fold.json"),
13610
+ rotatedDir: (0, import_node_path10.join)(dir, "rotated")
13115
13611
  };
13116
13612
  } catch {
13117
13613
  return null;
@@ -13184,11 +13680,11 @@ function rotateIfNeeded2(d) {
13184
13680
  if (!(0, import_node_fs9.existsSync)(d.eventsPath)) return;
13185
13681
  if ((0, import_node_fs9.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
13186
13682
  (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`));
13683
+ (0, import_node_fs9.renameSync)(d.eventsPath, (0, import_node_path10.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
13188
13684
  const kept = (0, import_node_fs9.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
13189
13685
  for (const stale of kept.slice(0, Math.max(0, kept.length - ROTATE_KEEP))) {
13190
13686
  try {
13191
- (0, import_node_fs9.renameSync)((0, import_node_path11.join)(d.rotatedDir, stale), (0, import_node_path11.join)(d.rotatedDir, `${stale}.pruned`));
13687
+ (0, import_node_fs9.renameSync)((0, import_node_path10.join)(d.rotatedDir, stale), (0, import_node_path10.join)(d.rotatedDir, `${stale}.pruned`));
13192
13688
  } catch {
13193
13689
  }
13194
13690
  }
@@ -13250,7 +13746,7 @@ function foldDossier(d, opts = {}) {
13250
13746
  state.meta.rotations = files.length;
13251
13747
  for (const f of files) {
13252
13748
  try {
13253
- ingest((0, import_node_fs9.readFileSync)((0, import_node_path11.join)(d.rotatedDir, f), "utf8"));
13749
+ ingest((0, import_node_fs9.readFileSync)((0, import_node_path10.join)(d.rotatedDir, f), "utf8"));
13254
13750
  } catch {
13255
13751
  state.meta.dropped_lines++;
13256
13752
  }
@@ -13311,6 +13807,13 @@ function reduce(state, events, now) {
13311
13807
  });
13312
13808
  break;
13313
13809
  }
13810
+ case "goal_delivered": {
13811
+ const g = state.goal.find((x) => x.status === "active");
13812
+ if (!g) break;
13813
+ if (g.delivered) break;
13814
+ g.delivered = { at: ev.at, seq: ev.seq, summary: ev.summary };
13815
+ break;
13816
+ }
13314
13817
  case "authored": {
13315
13818
  authoredEvents++;
13316
13819
  const e = byPath.get(ev.path) ?? {
@@ -13425,6 +13928,12 @@ function reduce(state, events, now) {
13425
13928
  }
13426
13929
  case "verdict": {
13427
13930
  state.meta.last_verdict_seq = ev.seq;
13931
+ state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
13932
+ if (ev.intent_sig) {
13933
+ state.meta.intent_repeat = state.meta.intent_repeat && state.meta.intent_repeat.sig === ev.intent_sig ? { sig: ev.intent_sig, consecutive: state.meta.intent_repeat.consecutive + 1 } : { sig: ev.intent_sig, consecutive: 1 };
13934
+ } else {
13935
+ state.meta.intent_repeat = void 0;
13936
+ }
13428
13937
  state.meta.watermark = {
13429
13938
  sha: ev.head_sha,
13430
13939
  reviewed_hash: ev.watermark_sha,
@@ -13510,7 +14019,12 @@ function compactState(s) {
13510
14019
  const ms = (iso) => Date.parse(iso) || 0;
13511
14020
  return {
13512
14021
  v: 1,
13513
- g: s.goal.map((g) => [g.seq, ms(g.at), g.hash, g.superseded_by, g.text ?? 0, g.text_len ?? 0, g.source ?? 0, g.status ?? 0, g.repeats ?? 0, g.truncated ? 1 : 0, g.collapsed ? 1 : 0]),
14022
+ // APPEND-ONLY POSITIONALLY. Indices 11-13 carry `delivered`; a cache row
14023
+ // written before it existed has length 11 and expands with `delivered`
14024
+ // absent, which is the correct reading of "nothing had been delivered yet".
14025
+ // Inserting rather than appending would silently re-interpret every existing
14026
+ // cached row.
14027
+ g: s.goal.map((g) => [g.seq, ms(g.at), g.hash, g.superseded_by, g.text ?? 0, g.text_len ?? 0, g.source ?? 0, g.status ?? 0, g.repeats ?? 0, g.truncated ? 1 : 0, g.collapsed ? 1 : 0, g.delivered ? ms(g.delivered.at) : 0, g.delivered?.seq ?? 0, g.delivered?.summary ?? 0]),
13514
14028
  a: s.authored?.map((a) => [a.path, a.origin, a.edits, a.hunks, a.adds, a.dels, a.hash_now, a.hash_at_last_verdict, a.first_seq, a.last_seq]) ?? null,
13515
14029
  n: s.not_mine?.map((n) => [n.path, n.reason, ms(n.at), n.head_sha]) ?? null,
13516
14030
  t: s.statements.map((x) => [x.anchor_key, x.file, x.line, x.pattern_id, x.title_hash, x.register, x.line_sha, x.said_at_seq, ms(x.said_at), x.outcome, x.outcome_at ? ms(x.outcome_at) : 0, x.repeats, x.carried ? 1 : 0]),
@@ -13556,7 +14070,14 @@ function expandState(raw) {
13556
14070
  ...x[7] ? { status: x[7] } : {},
13557
14071
  ...x[8] ? { repeats: x[8] } : {},
13558
14072
  ...x[9] ? { truncated: true } : {},
13559
- ...x[10] ? { collapsed: true } : {}
14073
+ ...x[10] ? { collapsed: true } : {},
14074
+ ...x[11] ? {
14075
+ delivered: {
14076
+ at: iso(x[11]),
14077
+ seq: x[12] ?? 0,
14078
+ summary: x[13] || ""
14079
+ }
14080
+ } : {}
13560
14081
  })),
13561
14082
  authored: decodedAuthored,
13562
14083
  // The cache stores the BOUNDED list, so this is the bounded list too. That
@@ -13770,9 +14291,14 @@ function projectMemory(state, opts) {
13770
14291
  seq: active.seq,
13771
14292
  superseded,
13772
14293
  truncated: active.truncated === true,
13773
- collapsed: state.meta.collapsed.goal ?? 0
14294
+ collapsed: state.meta.collapsed.goal ?? 0,
14295
+ ...active.delivered && { delivered: active.delivered }
13774
14296
  };
13775
14297
  }
14298
+ if (state.meta.last_adjudication) {
14299
+ const a = state.meta.last_adjudication;
14300
+ p.last_adjudication = { verdict: a.verdict, score: a.score, at: a.at };
14301
+ }
13776
14302
  const ageOf = (seq, carried) => carried ? "carried" : seq > lastVerdictSeq ? "this_turn" : "this_session";
13777
14303
  if (opts.spoken.length > 0) {
13778
14304
  p.statements = opts.spoken.map((s) => ({
@@ -14046,16 +14572,16 @@ function foreignAuthoredPaths(identity, opts = {}) {
14046
14572
  for (const entry of (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true })) {
14047
14573
  if (!entry.isDirectory()) continue;
14048
14574
  if (entry.name === identity.sessionKey) continue;
14049
- const log = (0, import_node_path12.join)(dir, entry.name, "events.jsonl");
14575
+ const log = (0, import_node_path11.join)(dir, entry.name, "events.jsonl");
14050
14576
  try {
14051
14577
  if (!(0, import_node_fs10.existsSync)(log)) continue;
14052
14578
  if (now - (0, import_node_fs10.statSync)(log).mtimeMs > windowMs) continue;
14053
14579
  const sib = {
14054
- dir: (0, import_node_path12.join)(dir, entry.name),
14580
+ dir: (0, import_node_path11.join)(dir, entry.name),
14055
14581
  identity,
14056
14582
  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")
14583
+ foldPath: (0, import_node_path11.join)(dir, entry.name, "fold.json"),
14584
+ rotatedDir: (0, import_node_path11.join)(dir, entry.name, "rotated")
14059
14585
  };
14060
14586
  const state = readFoldCache(sib) ?? foldDossier(sib);
14061
14587
  sessions++;
@@ -14083,22 +14609,22 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
14083
14609
  let removed = 0;
14084
14610
  try {
14085
14611
  const mine = dossierDir(identity);
14086
- const userDir = (0, import_node_path12.dirname)((0, import_node_path12.dirname)(mine));
14612
+ const userDir = (0, import_node_path11.dirname)((0, import_node_path11.dirname)(mine));
14087
14613
  if (!(0, import_node_fs10.existsSync)(userDir)) return 0;
14088
14614
  const cutoff = Date.now() - maxAgeMs;
14089
14615
  for (const tree of (0, import_node_fs10.readdirSync)(userDir, { withFileTypes: true })) {
14090
14616
  if (!tree.isDirectory()) continue;
14091
- const treePath = (0, import_node_path12.join)(userDir, tree.name);
14617
+ const treePath = (0, import_node_path11.join)(userDir, tree.name);
14092
14618
  let live = 0;
14093
14619
  for (const entry of (0, import_node_fs10.readdirSync)(treePath, { withFileTypes: true })) {
14094
14620
  if (!entry.isDirectory()) continue;
14095
- const dir = (0, import_node_path12.join)(treePath, entry.name);
14621
+ const dir = (0, import_node_path11.join)(treePath, entry.name);
14096
14622
  if (dir === mine) {
14097
14623
  live++;
14098
14624
  continue;
14099
14625
  }
14100
14626
  try {
14101
- const log = (0, import_node_path12.join)(dir, "events.jsonl");
14627
+ const log = (0, import_node_path11.join)(dir, "events.jsonl");
14102
14628
  const at = (0, import_node_fs10.existsSync)(log) ? (0, import_node_fs10.statSync)(log).mtimeMs : (0, import_node_fs10.statSync)(dir).mtimeMs;
14103
14629
  if (at < cutoff) {
14104
14630
  (0, import_node_fs10.rmSync)(dir, { recursive: true, force: true });
@@ -14141,7 +14667,7 @@ function recordTurn(d, t) {
14141
14667
  for (const a of t.authored) {
14142
14668
  const origin = a.owner === "subagent" ? "subagent" : "edit_tool";
14143
14669
  const prior = t.known?.authored?.get(a.p);
14144
- const hash = fileHash((0, import_node_path12.join)(root, a.p));
14670
+ const hash = fileHash((0, import_node_path11.join)(root, a.p));
14145
14671
  const hunks = Math.max(0, a.h - (prior?.hunks ?? 0));
14146
14672
  const adds = Math.max(0, a.a - (prior?.adds ?? 0));
14147
14673
  const dels = Math.max(0, a.d - (prior?.dels ?? 0));
@@ -14174,7 +14700,7 @@ function recordTurn(d, t) {
14174
14700
  }
14175
14701
  const seenDivergence = t.known?.divergence ?? /* @__PURE__ */ new Set();
14176
14702
  for (const u of t.unobserved) {
14177
- const hash = fileHash((0, import_node_path12.join)(root, u.p));
14703
+ const hash = fileHash((0, import_node_path11.join)(root, u.p));
14178
14704
  if (seenDivergence.has(divergenceKey(u.p, hash))) continue;
14179
14705
  appendEvent(d, { k: "divergence", kind: "external_mutation", path: u.p, hash });
14180
14706
  }
@@ -14198,12 +14724,16 @@ function toStatus(n) {
14198
14724
  }
14199
14725
  function recordVerdict(d, v) {
14200
14726
  const root = repoRoot();
14727
+ if (v.intent?.verdict === "aligned") {
14728
+ const summary = (v.intent.implemented ?? "").trim().slice(0, 400);
14729
+ if (summary) appendEvent(d, { k: "goal_delivered", summary });
14730
+ }
14201
14731
  const lines = /* @__PURE__ */ new Map();
14202
14732
  for (const f of v.findings) {
14203
14733
  if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
14204
14734
  if (!lines.has(f.file)) {
14205
14735
  try {
14206
- const abs = (0, import_node_path12.join)(root, f.file);
14736
+ const abs = (0, import_node_path11.join)(root, f.file);
14207
14737
  lines.set(f.file, (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null);
14208
14738
  } catch {
14209
14739
  lines.set(f.file, null);
@@ -14231,9 +14761,17 @@ function recordVerdict(d, v) {
14231
14761
  head_sha: getCurrentCommit(),
14232
14762
  watermark_sha: v.watermarkSha,
14233
14763
  branch: v.branch,
14234
- decision: v.decision
14764
+ decision: v.decision,
14765
+ ...intentSignature(v.intent) && { intent_sig: intentSignature(v.intent) },
14766
+ ...v.intent?.verdict && { intent_verdict: v.intent.verdict },
14767
+ ...typeof v.intent?.score === "number" && { intent_score: v.intent.score }
14235
14768
  });
14236
14769
  }
14770
+ function intentSignature(intent) {
14771
+ if (!intent?.verdict) return null;
14772
+ if (intent.verdict !== "misaligned" && intent.verdict !== "partial") return null;
14773
+ return `${intent.verdict}:${lineSha(intent.gaps?.[0] ?? "")}`;
14774
+ }
14237
14775
  function toRegister(severity) {
14238
14776
  switch (severity) {
14239
14777
  case "critical":
@@ -14278,7 +14816,7 @@ function recallMemory(d, identity, opts) {
14278
14816
  budgetBytes: opts.budgetBytes,
14279
14817
  readFileLines: (file) => {
14280
14818
  try {
14281
- const abs = (0, import_node_path12.join)(root, file);
14819
+ const abs = (0, import_node_path11.join)(root, file);
14282
14820
  return (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null;
14283
14821
  } catch {
14284
14822
  return null;
@@ -14418,21 +14956,21 @@ async function fireClassify(prompt, sessionId, globals) {
14418
14956
 
14419
14957
  // src/commands/lifecycle.ts
14420
14958
  var import_node_fs14 = require("node:fs");
14421
- var import_node_path16 = require("node:path");
14959
+ var import_node_path15 = require("node:path");
14422
14960
 
14423
14961
  // src/lib/baseline.ts
14424
14962
  var import_node_fs13 = require("node:fs");
14425
- var import_node_path15 = require("node:path");
14963
+ var import_node_path14 = require("node:path");
14426
14964
  var import_node_crypto7 = require("node:crypto");
14427
14965
 
14428
14966
  // src/lib/snapshot.ts
14429
14967
  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");
14968
+ var import_node_path13 = require("node:path");
14969
+ var import_node_child_process6 = require("node:child_process");
14432
14970
 
14433
14971
  // src/lib/files.ts
14434
14972
  var import_node_fs11 = require("node:fs");
14435
- var import_node_path13 = require("node:path");
14973
+ var import_node_path12 = require("node:path");
14436
14974
  var LANG_MAP = {
14437
14975
  // Analyzable (static analysis + Gemini)
14438
14976
  ts: "typescript",
@@ -14500,7 +15038,7 @@ var LANG_MAP = {
14500
15038
  mk: "make"
14501
15039
  };
14502
15040
  function detectLanguage(filepath) {
14503
- const ext = (0, import_node_path13.extname)(filepath).slice(1);
15041
+ const ext = (0, import_node_path12.extname)(filepath).slice(1);
14504
15042
  return LANG_MAP[ext] ?? ext;
14505
15043
  }
14506
15044
  function sortByMtime(files) {
@@ -14588,7 +15126,7 @@ function generateSnapshotDiffs(files) {
14588
15126
  }
14589
15127
  const diffs = [];
14590
15128
  for (const file of files) {
14591
- const snapshotPath = (0, import_node_path14.join)(SNAPSHOT_DIR, file.path);
15129
+ const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
14592
15130
  const language = file.language ?? detectLanguage(file.path);
14593
15131
  if ((0, import_node_fs12.existsSync)(snapshotPath)) {
14594
15132
  const oldContent = (0, import_node_fs12.readFileSync)(snapshotPath, "utf-8");
@@ -14615,21 +15153,21 @@ ${addedLines}`,
14615
15153
  function saveSnapshots(files) {
14616
15154
  const snapshotPaths = /* @__PURE__ */ new Set();
14617
15155
  for (const file of files) {
14618
- const snapshotPath = (0, import_node_path14.join)(SNAPSHOT_DIR, file.path);
15156
+ const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
14619
15157
  snapshotPaths.add(snapshotPath);
14620
- (0, import_node_fs12.mkdirSync)((0, import_node_path14.dirname)(snapshotPath), { recursive: true });
15158
+ (0, import_node_fs12.mkdirSync)((0, import_node_path13.dirname)(snapshotPath), { recursive: true });
14621
15159
  (0, import_node_fs12.writeFileSync)(snapshotPath, file.content);
14622
15160
  }
14623
15161
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
14624
15162
  }
14625
15163
  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");
15164
+ const tmpOld = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-old.tmp");
15165
+ const tmpNew = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-new.tmp");
14628
15166
  try {
14629
15167
  (0, import_node_fs12.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
14630
15168
  (0, import_node_fs12.writeFileSync)(tmpOld, oldContent);
14631
15169
  (0, import_node_fs12.writeFileSync)(tmpNew, newContent);
14632
- const result = (0, import_node_child_process7.execSync)(
15170
+ const result = (0, import_node_child_process6.execSync)(
14633
15171
  `git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
14634
15172
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
14635
15173
  );
@@ -14657,7 +15195,7 @@ function cleanStaleSnapshots(dir, keepSet) {
14657
15195
  const entries = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true });
14658
15196
  for (const entry of entries) {
14659
15197
  if (entry.name.startsWith(".")) continue;
14660
- const fullPath = (0, import_node_path14.join)(dir, entry.name);
15198
+ const fullPath = (0, import_node_path13.join)(dir, entry.name);
14661
15199
  if (entry.isDirectory()) {
14662
15200
  cleanStaleSnapshots(fullPath, keepSet);
14663
15201
  try {
@@ -14686,13 +15224,13 @@ function sessionKey(sessionId) {
14686
15224
  return (0, import_node_crypto7.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
14687
15225
  }
14688
15226
  function sessionDir(key) {
14689
- return (0, import_node_path15.join)(projectPath(BASELINE_DIR), key);
15227
+ return (0, import_node_path14.join)(projectPath(BASELINE_DIR), key);
14690
15228
  }
14691
15229
  function manifestPath(dir) {
14692
- return (0, import_node_path15.join)(dir, "manifest.json");
15230
+ return (0, import_node_path14.join)(dir, "manifest.json");
14693
15231
  }
14694
15232
  function mirrorPath(dir, repoRelPath) {
14695
- return (0, import_node_path15.join)(dir, "files", repoRelPath);
15233
+ return (0, import_node_path14.join)(dir, "files", repoRelPath);
14696
15234
  }
14697
15235
  var CARRY_FILE = `${BASELINE_DIR}/.carry`;
14698
15236
  var CARRY_WINDOW_MS = 12e4;
@@ -14754,7 +15292,7 @@ function captureBaseline(opts = {}) {
14754
15292
  (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
14755
15293
  } catch {
14756
15294
  }
14757
- const filesDir = (0, import_node_path15.join)(dir, "files");
15295
+ const filesDir = (0, import_node_path14.join)(dir, "files");
14758
15296
  const mirrored = [];
14759
15297
  try {
14760
15298
  (0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
@@ -14764,7 +15302,7 @@ function captureBaseline(opts = {}) {
14764
15302
  if (content === null) continue;
14765
15303
  const dest = mirrorPath(dir, p);
14766
15304
  try {
14767
- (0, import_node_fs13.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15305
+ (0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
14768
15306
  (0, import_node_fs13.writeFileSync)(dest, content);
14769
15307
  mirrored.push(p);
14770
15308
  } catch {
@@ -14892,7 +15430,7 @@ function pruneOldBaselines() {
14892
15430
  }
14893
15431
  const now = Date.now();
14894
15432
  for (const name of entries) {
14895
- const dir = (0, import_node_path15.join)(root, name);
15433
+ const dir = (0, import_node_path14.join)(root, name);
14896
15434
  const manifest = readManifest(dir);
14897
15435
  if (!manifest) {
14898
15436
  try {
@@ -15078,7 +15616,7 @@ function buildCompactionContext(session) {
15078
15616
  commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
15079
15617
  readFileLines: (file) => {
15080
15618
  try {
15081
- const abs = (0, import_node_path16.join)(root, file);
15619
+ const abs = (0, import_node_path15.join)(root, file);
15082
15620
  return (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null;
15083
15621
  } catch {
15084
15622
  return null;
@@ -15414,6 +15952,7 @@ function registerStatusCommand(program2) {
15414
15952
  }
15415
15953
  const token = tokenResult.data.token;
15416
15954
  const serviceUrl = urlResult.data;
15955
+ const who = await whoami(token, serviceUrl, globals.verbose);
15417
15956
  const memResult = await apiRequest({
15418
15957
  method: "GET",
15419
15958
  path: "/memory",
@@ -15421,14 +15960,21 @@ function registerStatusCommand(program2) {
15421
15960
  token,
15422
15961
  verbose: globals.verbose
15423
15962
  });
15424
- if (!memResult.ok) {
15963
+ const denial = memResult.ok ? null : authDenialRemedy(memResult.error);
15964
+ if (!memResult.ok && !denial) {
15425
15965
  printError(memResult.error);
15426
15966
  process.exit(1);
15427
15967
  }
15428
- const mem = memResult.data;
15968
+ const mem = memResult.ok ? memResult.data : null;
15429
15969
  if (opts.json) {
15430
- const output = { memory: mem };
15431
- if (opts.history) {
15970
+ const output = {
15971
+ auth: who.ok ? who.data : { error: who.error },
15972
+ memory: mem
15973
+ };
15974
+ if (denial && !memResult.ok) {
15975
+ output.error = { code: denial.code, message: memResult.error, remedy: denial.remedy };
15976
+ }
15977
+ if (opts.history && !denial) {
15432
15978
  const runsResult = await apiRequest({
15433
15979
  method: "GET",
15434
15980
  path: `/runs?limit=${opts.limit}`,
@@ -15443,23 +15989,30 @@ function registerStatusCommand(program2) {
15443
15989
  printJson(output);
15444
15990
  return;
15445
15991
  }
15446
- if (mem.configured === false) {
15992
+ if (mem?.configured === false) {
15447
15993
  printInfo("Verity is not configured for this project. Run /verity-setup.");
15448
15994
  return;
15449
15995
  }
15450
15996
  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) {
15997
+ if (who.ok && who.data.logged_in) {
15998
+ printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
15999
+ } else if (who.ok && who.data.anonymous) {
16000
+ printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
16001
+ } else if (tokenResult.data.userId != null) {
16002
+ printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} (cached \u2014 could not reach the Verity service) \u2713`);
16003
+ } else if (!who.ok) {
16004
+ printInfo(`Account: Unknown \u2014 could not reach the Verity service (${who.error})`);
16005
+ }
16006
+ if (who.ok) {
16007
+ const nudge = reverifyNudge(who.data);
16008
+ if (nudge) printWarn(` ${nudge}`);
16009
+ }
16010
+ if (denial) {
16011
+ printWarn(`Access: ${denial.remedy}`);
16012
+ printInfo(" Project status, history, and cloud memory stay unavailable until then.");
16013
+ }
16014
+ if (mem?.project_name) printInfo(`Project: ${mem.project_name}`);
16015
+ if (mem?.standard) {
15463
16016
  const s = mem.standard;
15464
16017
  printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
15465
16018
  printInfo(`Languages: ${s.languages.join(", ")}`);
@@ -15470,6 +16023,7 @@ function registerStatusCommand(program2) {
15470
16023
  if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
15471
16024
  if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
15472
16025
  printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
16026
+ if (!mem) return;
15473
16027
  if (mem.recent_runs) {
15474
16028
  const r = mem.recent_runs;
15475
16029
  printInfo("");
@@ -15621,7 +16175,7 @@ async function sendGeneralFeedback(message, opts, globals) {
15621
16175
 
15622
16176
  // src/commands/analyze.ts
15623
16177
  var import_node_fs24 = require("node:fs");
15624
- var import_node_path21 = require("node:path");
16178
+ var import_node_path20 = require("node:path");
15625
16179
 
15626
16180
  // src/lib/debounce.ts
15627
16181
  var import_node_fs15 = require("node:fs");
@@ -15758,7 +16312,7 @@ function writeIteration(iteration, commit, _contentHash) {
15758
16312
  }
15759
16313
 
15760
16314
  // src/lib/static-analysis.ts
15761
- var import_node_child_process8 = require("node:child_process");
16315
+ var import_node_child_process7 = require("node:child_process");
15762
16316
  var import_node_fs16 = require("node:fs");
15763
16317
  var SEVERITY_ORDER = {
15764
16318
  Error: 0,
@@ -15771,7 +16325,7 @@ var SEVERITY_ORDER = {
15771
16325
  };
15772
16326
  function isCodacyAvailable() {
15773
16327
  try {
15774
- (0, import_node_child_process8.execSync)("which codacy-analysis", { stdio: "pipe" });
16328
+ (0, import_node_child_process7.execSync)("which codacy-analysis", { stdio: "pipe" });
15775
16329
  return true;
15776
16330
  } catch {
15777
16331
  return false;
@@ -15795,7 +16349,7 @@ function runCodacyAnalysis(files) {
15795
16349
  const fileArgs = existingFiles.join(" ");
15796
16350
  let output;
15797
16351
  try {
15798
- output = (0, import_node_child_process8.execSync)(
16352
+ output = (0, import_node_child_process7.execSync)(
15799
16353
  `codacy-analysis analyze --install-dependencies --files ${fileArgs} --output-format json --log-level error --parallel-tools 3`,
15800
16354
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 }
15801
16355
  );
@@ -15849,7 +16403,7 @@ function runCodacyAnalysis(files) {
15849
16403
 
15850
16404
  // src/lib/specs.ts
15851
16405
  var import_node_fs17 = require("node:fs");
15852
- var import_node_path17 = require("node:path");
16406
+ var import_node_path16 = require("node:path");
15853
16407
  var SPEC_CANDIDATES = [
15854
16408
  "CLAUDE.md",
15855
16409
  "AGENTS.md",
@@ -15911,7 +16465,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
15911
16465
  try {
15912
16466
  const entries = (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true });
15913
16467
  for (const entry of entries) {
15914
- const fullPath = (0, import_node_path17.join)(dir, entry.name);
16468
+ const fullPath = (0, import_node_path16.join)(dir, entry.name);
15915
16469
  if (entry.isFile() && entry.name.endsWith(".md")) {
15916
16470
  result.push(fullPath);
15917
16471
  } else if (entry.isDirectory() && depth < maxDepth - 1) {
@@ -15923,7 +16477,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
15923
16477
  return result;
15924
16478
  }
15925
16479
  function discoverPlans() {
15926
- const homePlansDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".claude", "plans");
16480
+ const homePlansDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".claude", "plans");
15927
16481
  const localPlansDir = ".claude/plans";
15928
16482
  const candidates = [];
15929
16483
  const seen = /* @__PURE__ */ new Set();
@@ -15933,7 +16487,7 @@ function discoverPlans() {
15933
16487
  for (const f of (0, import_node_fs17.readdirSync)(plansDir)) {
15934
16488
  if (!f.endsWith(".md") || seen.has(f)) continue;
15935
16489
  seen.add(f);
15936
- const fullPath = (0, import_node_path17.join)(plansDir, f);
16490
+ const fullPath = (0, import_node_path16.join)(plansDir, f);
15937
16491
  try {
15938
16492
  const stat3 = (0, import_node_fs17.statSync)(fullPath);
15939
16493
  candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
@@ -15957,7 +16511,7 @@ function discoverPlans() {
15957
16511
  }
15958
16512
 
15959
16513
  // src/lib/task-context.ts
15960
- var import_node_child_process9 = require("node:child_process");
16514
+ var import_node_child_process8 = require("node:child_process");
15961
16515
  var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
15962
16516
  var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
15963
16517
  function parseLinkedIssue(sources) {
@@ -15973,7 +16527,7 @@ function parseLinkedIssue(sources) {
15973
16527
  }
15974
16528
  function safeExec(cmd, timeout) {
15975
16529
  try {
15976
- return (0, import_node_child_process9.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
16530
+ return (0, import_node_child_process8.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
15977
16531
  } catch {
15978
16532
  return "";
15979
16533
  }
@@ -16003,7 +16557,7 @@ function resolveTaskContext(opts) {
16003
16557
  // src/lib/cli-version.ts
16004
16558
  function cliVersion() {
16005
16559
  try {
16006
- return true ? "0.28.1-experimental.a57c8d9" : "dev";
16560
+ return true ? "0.28.1-experimental.af3c52d" : "dev";
16007
16561
  } catch {
16008
16562
  return "dev";
16009
16563
  }
@@ -16207,7 +16761,7 @@ function truthy(v) {
16207
16761
 
16208
16762
  // src/lib/fold.ts
16209
16763
  var import_node_fs20 = require("node:fs");
16210
- var import_node_path18 = require("node:path");
16764
+ var import_node_path17 = require("node:path");
16211
16765
  var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
16212
16766
  "user",
16213
16767
  "assistant",
@@ -16391,7 +16945,7 @@ function fold(transcriptPath, opts = {}) {
16391
16945
  return result;
16392
16946
  }
16393
16947
  try {
16394
- const sidecarDir = (0, import_node_path18.join)((0, import_node_path18.dirname)(transcriptPath), "subagents");
16948
+ const sidecarDir = (0, import_node_path17.join)((0, import_node_path17.dirname)(transcriptPath), "subagents");
16395
16949
  if ((0, import_node_fs20.existsSync)(sidecarDir)) {
16396
16950
  const maxFiles = opts.maxSidecars ?? 200;
16397
16951
  const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
@@ -16399,7 +16953,7 @@ function fold(transcriptPath, opts = {}) {
16399
16953
  const walk = (d, depth) => {
16400
16954
  if (depth > 4) return;
16401
16955
  for (const e of (0, import_node_fs20.readdirSync)(d, { withFileTypes: true })) {
16402
- const p = (0, import_node_path18.join)(d, e.name);
16956
+ const p = (0, import_node_path17.join)(d, e.name);
16403
16957
  if (e.isDirectory()) {
16404
16958
  walk(p, depth + 1);
16405
16959
  } else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
@@ -16554,9 +17108,16 @@ function buildAgentContext(input) {
16554
17108
  }
16555
17109
  if (input.intentVerdict === "misaligned" || input.intentVerdict === "partial") {
16556
17110
  const gap = input.intentGaps?.[0];
16557
- lines.push(
16558
- `- This does not look like the change that was asked for` + (gap ? `: ${gap}` : ".") + " Not blocking \u2014 but check it against what you were asked to do."
16559
- );
17111
+ const repeat = input.intentRepeat ?? 0;
17112
+ if (repeat > 0) {
17113
+ lines.push(
17114
+ `- Same intent flag as the last ${repeat === 1 ? "turn" : `${repeat} turns`}, on a goal that has not changed. Nothing new here \u2014 do not relay or re-explain it again; either act on it or carry on.`
17115
+ );
17116
+ } else {
17117
+ lines.push(
17118
+ `- This does not look like the change that was asked for` + (gap ? `: ${gap}` : ".") + " Not blocking \u2014 but check it against what you were asked to do."
17119
+ );
17120
+ }
16560
17121
  }
16561
17122
  const agentFindings = (input.findings ?? []).filter((f) => f.scope !== "pre-existing");
16562
17123
  for (const f of agentFindings) {
@@ -16606,7 +17167,7 @@ function buildHookOutput(gateDecision, systemMessage, agentContext) {
16606
17167
 
16607
17168
  // src/lib/cache-cleanup.ts
16608
17169
  var import_node_fs21 = require("node:fs");
16609
- var import_node_path19 = require("node:path");
17170
+ var import_node_path18 = require("node:path");
16610
17171
  var CACHE_TTL_DAYS = 7;
16611
17172
  function pruneStaleCache() {
16612
17173
  try {
@@ -16614,7 +17175,7 @@ function pruneStaleCache() {
16614
17175
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
16615
17176
  for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
16616
17177
  if (!entry.startsWith("pending-")) continue;
16617
- const path = (0, import_node_path19.join)(dir, entry);
17178
+ const path = (0, import_node_path18.join)(dir, entry);
16618
17179
  try {
16619
17180
  const stat3 = (0, import_node_fs21.statSync)(path);
16620
17181
  if (stat3.mtimeMs < cutoff) {
@@ -17061,7 +17622,7 @@ function isExplicitlyAutonomous(env = process.env) {
17061
17622
  // src/lib/seed-runner.ts
17062
17623
  var import_promises11 = require("node:fs/promises");
17063
17624
  var import_node_fs23 = require("node:fs");
17064
- var import_node_path20 = require("node:path");
17625
+ var import_node_path19 = require("node:path");
17065
17626
  var import_yaml2 = __toESM(require_dist());
17066
17627
 
17067
17628
  // src/lib/seed.ts
@@ -17343,7 +17904,7 @@ async function runSeed(opts) {
17343
17904
  if (candidates.length === 0) {
17344
17905
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
17345
17906
  }
17346
- const overviewPath = (0, import_node_path20.join)(MEMORY_DIR, "domain", "project-overview.md");
17907
+ const overviewPath = (0, import_node_path19.join)(MEMORY_DIR, "domain", "project-overview.md");
17347
17908
  if ((0, import_node_fs23.existsSync)(overviewPath) && !opts.force) {
17348
17909
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
17349
17910
  }
@@ -17379,9 +17940,9 @@ async function runSeed(opts) {
17379
17940
  }
17380
17941
  const nodeId = res.data.node_id;
17381
17942
  const filePathRel = res.data.file_path;
17382
- const targetPath = (0, import_node_path20.join)(MEMORY_DIR, filePathRel);
17943
+ const targetPath = (0, import_node_path19.join)(MEMORY_DIR, filePathRel);
17383
17944
  try {
17384
- await (0, import_promises11.mkdir)((0, import_node_path20.dirname)(targetPath), { recursive: true });
17945
+ await (0, import_promises11.mkdir)((0, import_node_path19.dirname)(targetPath), { recursive: true });
17385
17946
  await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
17386
17947
  created++;
17387
17948
  opts.onCreated?.(nodeId, filePathRel, c);
@@ -17430,10 +17991,11 @@ async function readStopHookStdin() {
17430
17991
  return empty;
17431
17992
  }
17432
17993
  }
17433
- function agentContextFor(response) {
17994
+ function agentContextFor(response, intentRepeat = 0) {
17434
17995
  const metadata = response.metadata ?? {};
17435
17996
  const intent = response.intent_alignment ?? {};
17436
17997
  return buildAgentContext({
17998
+ intentRepeat,
17437
17999
  gateDecision: String(response.gate_decision ?? ""),
17438
18000
  findings: response.findings ?? [],
17439
18001
  pendingItems: response.pending_items ?? [],
@@ -17707,7 +18269,7 @@ async function runAnalyze(opts, globals) {
17707
18269
  let autoSeedNotice = null;
17708
18270
  try {
17709
18271
  await ensureMemoryDir();
17710
- const seedMarker = (0, import_node_path21.join)(VERITY_DIR, ".seeded");
18272
+ const seedMarker = (0, import_node_path20.join)(VERITY_DIR, ".seeded");
17711
18273
  const hasStandard = (0, import_node_fs24.existsSync)(STANDARD_FILE);
17712
18274
  const alreadyTried = (0, import_node_fs24.existsSync)(seedMarker);
17713
18275
  if (hasStandard && !alreadyTried) {
@@ -17775,7 +18337,7 @@ async function runAnalyze(opts, globals) {
17775
18337
  const priorState = foldForMarks(memorySession.d);
17776
18338
  incrementReport = computeIncrement(
17777
18339
  allForReview,
17778
- (p) => fileHash((0, import_node_path21.join)(repoRoot(), p)),
18340
+ (p) => fileHash((0, import_node_path20.join)(repoRoot(), p)),
17779
18341
  priorState.authored_all.map((a) => ({
17780
18342
  path: a.path,
17781
18343
  hash_at_last_verdict: a.hash_at_last_verdict
@@ -17917,6 +18479,25 @@ async function runAnalyze(opts, globals) {
17917
18479
  projection: {
17918
18480
  v: 1,
17919
18481
  authored: foldResult?.authored ?? [],
18482
+ // ⚠ AUTHORED *SINCE THE LAST VERDICT* — a different question from `authored`.
18483
+ //
18484
+ // `authored` above is the fold of the session TRANSCRIPT, which is
18485
+ // cumulative: on turn 3 it still lists the files turn 2 edited. The
18486
+ // account's close-out reads it as "was this file edited since the
18487
+ // statement was raised", and those are not the same set.
18488
+ //
18489
+ // Measured 2026-08-03 (shirt-seller): four real vulnerabilities were
18490
+ // raised on the turn that wrote them, then marked `fixed` 36 seconds
18491
+ // later by a SUMMARY turn that edited nothing — `authored` still said 2,
18492
+ // the turn carried 0 findings, so "gone + file authored" resolved to
18493
+ // fixed. The vulnerabilities were still on disk. A silent false `fixed`
18494
+ // is worse than a false `open`: it retires the statement the Account
18495
+ // exists to keep.
18496
+ //
18497
+ // `hash_at_last_verdict` is frozen at each verdict and `hash_now` tracks
18498
+ // disk, so their inequality IS "changed since we last spoke" — already
18499
+ // computed, already maintained by the divergence machinery.
18500
+ authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
17920
18501
  unobserved: foldResult?.unobserved ?? [],
17921
18502
  commands: foldResult?.commands ?? [],
17922
18503
  unknown_types: foldResult?.unknownTypes ?? [],
@@ -18030,6 +18611,12 @@ async function runAnalyze(opts, globals) {
18030
18611
  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
18612
  } else if (result.category === "network" || result.category === "timeout") {
18032
18613
  message = `Verity offline \u2014 ${result.error}`;
18614
+ } else if (result.error.startsWith("STALE_VERIFICATION")) {
18615
+ message = "Verity: your GitHub verification expired \u2014 run `verity login` to re-verify (local analysis this run)";
18616
+ } else if (result.error.startsWith("FORBIDDEN")) {
18617
+ message = "Verity: no access grant for this repository \u2014 run `verity login` to refresh your grants (local analysis this run)";
18618
+ } else if (result.error.startsWith("INVALID_TOKEN")) {
18619
+ message = "Verity: your login expired or was revoked \u2014 run `verity login` to sign in again (local analysis this run)";
18033
18620
  } else if (result.http_status && result.http_status >= 400 && result.http_status < 500) {
18034
18621
  message = `Verity: request rejected (HTTP ${result.http_status}) \u2014 ${result.error}`;
18035
18622
  } else if (result.http_status && result.http_status >= 500) {
@@ -18046,6 +18633,7 @@ async function runAnalyze(opts, globals) {
18046
18633
  const sentPaths = codeDelta.files.map((f) => f.path);
18047
18634
  const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
18048
18635
  const watermarkIsPartial = !!codeDelta.truncated;
18636
+ let intentRepeatCount = 0;
18049
18637
  if (memorySession) {
18050
18638
  try {
18051
18639
  recordVerdict(memorySession.d, {
@@ -18059,8 +18647,10 @@ async function runAnalyze(opts, globals) {
18059
18647
  pattern_id: f.pattern_id ?? f.rule_id,
18060
18648
  title: f.title,
18061
18649
  severity: f.severity
18062
- })) ?? []
18650
+ })) ?? [],
18651
+ intent: response.intent_alignment ?? null
18063
18652
  });
18653
+ intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
18064
18654
  } catch {
18065
18655
  }
18066
18656
  }
@@ -18153,6 +18743,11 @@ async function runAnalyze(opts, globals) {
18153
18743
  }
18154
18744
  saveSnapshots(codeDelta.files.map((f) => ({ path: f.path, content: f.content })));
18155
18745
  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." : "";
18746
+ const grantWarning = reverifyNudge({
18747
+ grant_status: response.grant_status,
18748
+ reverify_by: response.reverify_by
18749
+ });
18750
+ const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
18156
18751
  switch (decision) {
18157
18752
  case "FAIL": {
18158
18753
  writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
@@ -18228,6 +18823,9 @@ async function runAnalyze(opts, globals) {
18228
18823
  `);
18229
18824
  if (loginNudge) process.stderr.write(`
18230
18825
  ${YELLOW}${loginNudge.trim()}${NC}
18826
+ `);
18827
+ if (grantNudge) process.stderr.write(`
18828
+ ${YELLOW}${grantNudge.trim()}${NC}
18231
18829
  `);
18232
18830
  process.exit(2);
18233
18831
  break;
@@ -18240,9 +18838,9 @@ ${YELLOW}${loginNudge.trim()}${NC}
18240
18838
  const viewUrl = response.view_url ?? "";
18241
18839
  if (viewUrl) userSummary += ` Report: ${viewUrl}`;
18242
18840
  if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
18243
- userSummary += loginNudge;
18841
+ userSummary += loginNudge + grantNudge;
18244
18842
  printJsonCompact(
18245
- buildHookOutput("PASS", userSummary, agentContextFor(response))
18843
+ buildHookOutput("PASS", userSummary, agentContextFor(response, intentRepeatCount))
18246
18844
  );
18247
18845
  process.exit(0);
18248
18846
  break;
@@ -18254,16 +18852,16 @@ ${YELLOW}${loginNudge.trim()}${NC}
18254
18852
  const viewUrl = response.view_url ?? "";
18255
18853
  if (viewUrl) userSummary += ` Report: ${viewUrl}`;
18256
18854
  if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
18257
- userSummary += loginNudge;
18855
+ userSummary += loginNudge + grantNudge;
18258
18856
  printJsonCompact(
18259
- buildHookOutput("WARN", userSummary, agentContextFor(response))
18857
+ buildHookOutput("WARN", userSummary, agentContextFor(response, intentRepeatCount))
18260
18858
  );
18261
18859
  process.exit(0);
18262
18860
  break;
18263
18861
  }
18264
18862
  default: {
18265
18863
  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;
18864
+ const msg = (autoSeedNotice ? `${autoSeedNotice} Verity: unrecognised verdict \u2014 treating as WARN` : "Verity: unrecognised verdict \u2014 treating as WARN") + loginNudge + grantNudge;
18267
18865
  process.stderr.write(
18268
18866
  `Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
18269
18867
  `
@@ -18430,9 +19028,9 @@ async function runReview(opts, globals) {
18430
19028
 
18431
19029
  // src/commands/guard.ts
18432
19030
  var import_node_fs27 = require("node:fs");
18433
- var import_node_path22 = require("node:path");
19031
+ var import_node_path21 = require("node:path");
18434
19032
  var GUARD_BLOCK_CAP = 2;
18435
- var GUARD_ITER_FILE = (0, import_node_path22.join)(VERITY_DIR, ".guard-iteration");
19033
+ var GUARD_ITER_FILE = (0, import_node_path21.join)(VERITY_DIR, ".guard-iteration");
18436
19034
  function readPreToolUseStdin() {
18437
19035
  const empty = { command: "", cwd: null, sessionId: null };
18438
19036
  return new Promise((resolve2) => {
@@ -18674,9 +19272,10 @@ async function runGuard(opts, globals) {
18674
19272
  cmd: "guard"
18675
19273
  });
18676
19274
  if (!result.ok) {
19275
+ 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." : result.error.startsWith("INVALID_TOKEN") ? " Your Verity login expired or was revoked \u2014 run `verity login` to sign in again." : "";
18677
19276
  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.`
19277
+ `\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "service offline"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
19278
+ `Verity ${moment}: ${authRemedy ? "not authorized" : "service unavailable"} (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${authRemedy}`
18680
19279
  );
18681
19280
  }
18682
19281
  if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
@@ -18748,14 +19347,14 @@ function writeBlockMessage(moment, response) {
18748
19347
  // src/commands/init.ts
18749
19348
  var import_node_fs29 = require("node:fs");
18750
19349
  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");
19350
+ var import_node_path23 = require("node:path");
19351
+ var import_node_child_process10 = require("node:child_process");
18753
19352
  var readline2 = __toESM(require("node:readline/promises"));
18754
19353
 
18755
19354
  // src/commands/migrate.ts
18756
19355
  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");
19356
+ var import_node_path22 = require("node:path");
19357
+ var import_node_child_process9 = require("node:child_process");
18759
19358
 
18760
19359
  // src/lib/telemetry.ts
18761
19360
  var import_promises12 = require("node:fs/promises");
@@ -18850,11 +19449,11 @@ async function uninstallTelemetry() {
18850
19449
  // src/commands/migrate.ts
18851
19450
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
18852
19451
  function defaultNpmRemover(pkg) {
18853
- (0, import_node_child_process10.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
19452
+ (0, import_node_child_process9.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
18854
19453
  }
18855
19454
  function isGitTracked(cwd, relPath) {
18856
19455
  try {
18857
- (0, import_node_child_process10.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
19456
+ (0, import_node_child_process9.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
18858
19457
  return true;
18859
19458
  } catch {
18860
19459
  return false;
@@ -18862,7 +19461,7 @@ function isGitTracked(cwd, relPath) {
18862
19461
  }
18863
19462
  function isGitRepo(cwd) {
18864
19463
  try {
18865
- (0, import_node_child_process10.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
19464
+ (0, import_node_child_process9.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
18866
19465
  return true;
18867
19466
  } catch {
18868
19467
  return false;
@@ -18883,8 +19482,8 @@ async function runMigration(opts = {}) {
18883
19482
  return { actions, migrated: actions.length > 0 };
18884
19483
  }
18885
19484
  function migrateProjectDir(root, actions) {
18886
- const gateDir = (0, import_node_path23.join)(root, ".gate");
18887
- const verityDir = (0, import_node_path23.join)(root, ".verity");
19485
+ const gateDir = (0, import_node_path22.join)(root, ".gate");
19486
+ const verityDir = (0, import_node_path22.join)(root, ".verity");
18888
19487
  if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) {
18889
19488
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
18890
19489
  }
@@ -18902,7 +19501,7 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
18902
19501
  );
18903
19502
  }
18904
19503
  try {
18905
- (0, import_node_child_process10.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
19504
+ (0, import_node_child_process9.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
18906
19505
  actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
18907
19506
  moved = true;
18908
19507
  } catch {
@@ -18938,11 +19537,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
18938
19537
  }
18939
19538
  function migrateGlobalCredentials(home, actions) {
18940
19539
  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");
19540
+ const gateCreds = (0, import_node_path22.join)(home, ".gate", "credentials");
19541
+ const verityCreds = (0, import_node_path22.join)(home, ".verity", "credentials");
18943
19542
  if (!(0, import_node_fs28.existsSync)(gateCreds)) return;
18944
19543
  if (!(0, import_node_fs28.existsSync)(verityCreds)) {
18945
- (0, import_node_fs28.mkdirSync)((0, import_node_path23.join)(home, ".verity"), { recursive: true });
19544
+ (0, import_node_fs28.mkdirSync)((0, import_node_path22.join)(home, ".verity"), { recursive: true });
18946
19545
  moveFile(gateCreds, verityCreds);
18947
19546
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
18948
19547
  return;
@@ -18964,7 +19563,7 @@ async function migrateLegacyHooks(root, actions) {
18964
19563
  }
18965
19564
  }
18966
19565
  async function migrateClaudeMd(root, actions) {
18967
- const claudeMd = (0, import_node_path23.join)(root, "CLAUDE.md");
19566
+ const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
18968
19567
  const hadLegacyBlock = (0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
18969
19568
  if (!hadLegacyBlock) return;
18970
19569
  try {
@@ -18975,13 +19574,13 @@ async function migrateClaudeMd(root, actions) {
18975
19574
  }
18976
19575
  }
18977
19576
  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");
19577
+ const gateMd = (0, import_node_path22.join)(root, "GATE.md");
19578
+ const verityMd = (0, import_node_path22.join)(root, "VERITY.md");
18980
19579
  if (!(0, import_node_fs28.existsSync)(gateMd) || (0, import_node_fs28.existsSync)(verityMd)) return;
18981
19580
  let moved = false;
18982
19581
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
18983
19582
  try {
18984
- (0, import_node_child_process10.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
19583
+ (0, import_node_child_process9.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
18985
19584
  moved = true;
18986
19585
  } catch {
18987
19586
  }
@@ -18993,7 +19592,7 @@ function migrateStandardFile(root, actions) {
18993
19592
  actions.push("Renamed GATE.md \u2192 VERITY.md");
18994
19593
  }
18995
19594
  async function migrateTelemetryHeaders(root, actions) {
18996
- const file = (0, import_node_path23.join)(root, ".claude", "settings.local.json");
19595
+ const file = (0, import_node_path22.join)(root, ".claude", "settings.local.json");
18997
19596
  if (!(0, import_node_fs28.existsSync)(file)) return;
18998
19597
  let settings;
18999
19598
  try {
@@ -19056,7 +19655,7 @@ function readFileSyncSafe(path) {
19056
19655
  }
19057
19656
  function hasStagedChanges(root) {
19058
19657
  try {
19059
- (0, import_node_child_process10.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
19658
+ (0, import_node_child_process9.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
19060
19659
  return false;
19061
19660
  } catch {
19062
19661
  return true;
@@ -19083,15 +19682,15 @@ function moveFile(from, to) {
19083
19682
  function carryLegacyContents(gateDir, verityDir) {
19084
19683
  let copied = 0;
19085
19684
  const walk = (relDir) => {
19086
- const srcDir = (0, import_node_path23.join)(gateDir, relDir);
19685
+ const srcDir = (0, import_node_path22.join)(gateDir, relDir);
19087
19686
  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);
19687
+ const rel = relDir ? (0, import_node_path22.join)(relDir, entry) : entry;
19688
+ const src = (0, import_node_path22.join)(gateDir, rel);
19689
+ const dest = (0, import_node_path22.join)(verityDir, rel);
19091
19690
  if ((0, import_node_fs28.statSync)(src).isDirectory()) {
19092
19691
  walk(rel);
19093
19692
  } else if (!(0, import_node_fs28.existsSync)(dest)) {
19094
- (0, import_node_fs28.mkdirSync)((0, import_node_path23.dirname)(dest), { recursive: true });
19693
+ (0, import_node_fs28.mkdirSync)((0, import_node_path22.dirname)(dest), { recursive: true });
19095
19694
  (0, import_node_fs28.cpSync)(src, dest);
19096
19695
  copied++;
19097
19696
  }
@@ -19101,22 +19700,22 @@ function carryLegacyContents(gateDir, verityDir) {
19101
19700
  return copied;
19102
19701
  }
19103
19702
  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");
19703
+ const gateDir = (0, import_node_path22.join)(root, ".gate");
19704
+ const verityDir = (0, import_node_path22.join)(root, ".verity");
19106
19705
  if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) return true;
19107
19706
  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"))) {
19707
+ 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
19708
  return true;
19110
19709
  }
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"))) {
19710
+ 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
19711
  return true;
19113
19712
  }
19114
19713
  }
19115
- const claudeMd = (0, import_node_path23.join)(root, "CLAUDE.md");
19714
+ const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
19116
19715
  if ((0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
19117
19716
  return true;
19118
19717
  }
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"))) {
19718
+ 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
19719
  return true;
19121
19720
  }
19122
19721
  if (await hasLegacyHooksAt(root)) return true;
@@ -19152,12 +19751,25 @@ async function promptYes(question) {
19152
19751
  rl.close();
19153
19752
  }
19154
19753
  }
19155
- async function confirmExistingLogin(serviceUrl, opts) {
19754
+ async function confirmExistingLogin(serviceUrl, remote, opts) {
19156
19755
  const existing = await resolveToken(opts.token);
19157
19756
  if (!existing.ok) return "drive-login";
19158
19757
  const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
19159
19758
  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.`);
19759
+ const identity = who.data.email ?? `user #${who.data.user_id}`;
19760
+ const covered = who.data.grant_status != null;
19761
+ if (!remote) {
19762
+ printInfo(`Logged in as ${identity} \u2713`);
19763
+ printInfo(" This directory has no git remote, so there is no project here to sync to.");
19764
+ } else if (covered) {
19765
+ printInfo(`Logged in as ${identity} \u2713 \u2014 runs & memory sync to Verity.`);
19766
+ } else {
19767
+ printInfo(`Logged in as ${identity} \u2713`);
19768
+ printWarn(" This repository is NOT covered by your Verity access grants \u2014 nothing will sync.");
19769
+ printInfo(' Grant the Verity GitHub App access to it, then run "verity login" to refresh your grants.');
19770
+ }
19771
+ const nudge = reverifyNudge(who.data);
19772
+ if (nudge) printWarn(` ${nudge}`);
19161
19773
  return "handled";
19162
19774
  }
19163
19775
  if (!who.ok) {
@@ -19186,18 +19798,18 @@ async function runOptionalAuth(resolution, opts = {}) {
19186
19798
  printInfo(" Log in below to re-register this project and repair ~/.verity/credentials.");
19187
19799
  }
19188
19800
  }
19189
- if (!healed) {
19190
- const state = await confirmExistingLogin(serviceUrl, opts);
19191
- if (state === "handled") return;
19192
- }
19193
19801
  let remote = "";
19194
19802
  try {
19195
- remote = (0, import_node_child_process11.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
19803
+ remote = (0, import_node_child_process10.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
19196
19804
  } catch {
19197
19805
  }
19806
+ if (!healed) {
19807
+ const state = await confirmExistingLogin(serviceUrl, remote, opts);
19808
+ if (state === "handled") return;
19809
+ }
19198
19810
  const localOnlyNote = () => {
19199
19811
  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".');
19812
+ printInfo(' Authenticate anytime: run "verity login" (one login covers every repo you can write to).');
19201
19813
  };
19202
19814
  if (process.stdin.isTTY && process.stdout.isTTY) {
19203
19815
  console.log("");
@@ -19223,7 +19835,7 @@ async function runOptionalAuth(resolution, opts = {}) {
19223
19835
  localOnlyNote();
19224
19836
  return;
19225
19837
  }
19226
- const projectName = parseRemote(remote)?.repo ?? (0, import_node_path24.basename)(process.cwd());
19838
+ const projectName = parseRemote(remote)?.repo ?? (0, import_node_path23.basename)(process.cwd());
19227
19839
  printInfo("Authenticating with GitHub\u2026");
19228
19840
  const result = await registerProject({ projectName, remote, serviceUrl, verbose: opts.verbose });
19229
19841
  if (result.ok) {
@@ -19236,15 +19848,15 @@ async function runOptionalAuth(resolution, opts = {}) {
19236
19848
  }
19237
19849
  function resolveDataDir() {
19238
19850
  const candidates = [
19239
- (0, import_node_path24.join)(__dirname, "..", "data"),
19851
+ (0, import_node_path23.join)(__dirname, "..", "data"),
19240
19852
  // installed: node_modules/@codacy/verity-cli/data
19241
- (0, import_node_path24.join)(__dirname, "..", "..", "data"),
19853
+ (0, import_node_path23.join)(__dirname, "..", "..", "data"),
19242
19854
  // edge case: nested resolution
19243
- (0, import_node_path24.join)(process.cwd(), "cli", "data")
19855
+ (0, import_node_path23.join)(process.cwd(), "cli", "data")
19244
19856
  // local dev: running from repo root
19245
19857
  ];
19246
19858
  for (const candidate of candidates) {
19247
- if ((0, import_node_fs29.existsSync)((0, import_node_path24.join)(candidate, "skills"))) {
19859
+ if ((0, import_node_fs29.existsSync)((0, import_node_path23.join)(candidate, "skills"))) {
19248
19860
  return candidate;
19249
19861
  }
19250
19862
  }
@@ -19288,30 +19900,30 @@ function registerInitCommand(program2) {
19288
19900
  }
19289
19901
  printInfo(` Node.js ${nodeVersion} \u2713`);
19290
19902
  try {
19291
- const gitVersion = (0, import_node_child_process11.execSync)("git --version", { encoding: "utf-8" }).trim();
19903
+ const gitVersion = (0, import_node_child_process10.execSync)("git --version", { encoding: "utf-8" }).trim();
19292
19904
  printInfo(` ${gitVersion} \u2713`);
19293
19905
  } catch {
19294
19906
  printError("git is required but not installed. Install from https://git-scm.com");
19295
19907
  process.exit(1);
19296
19908
  }
19297
19909
  try {
19298
- (0, import_node_child_process11.execSync)("which claude", { encoding: "utf-8" });
19910
+ (0, import_node_child_process10.execSync)("which claude", { encoding: "utf-8" });
19299
19911
  printInfo(" Claude Code \u2713");
19300
19912
  } catch {
19301
19913
  printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
19302
19914
  }
19303
19915
  try {
19304
- (0, import_node_child_process11.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
19916
+ (0, import_node_child_process10.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
19305
19917
  printInfo(" @codacy/analysis-cli \u2713");
19306
19918
  } catch {
19307
19919
  printInfo(" Installing @codacy/analysis-cli...");
19308
19920
  try {
19309
- (0, import_node_child_process11.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
19921
+ (0, import_node_child_process10.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
19310
19922
  printInfo(" @codacy/analysis-cli installed \u2713");
19311
19923
  } catch {
19312
19924
  try {
19313
19925
  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 });
19926
+ (0, import_node_child_process10.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
19315
19927
  printInfo(" @codacy/analysis-cli installed \u2713");
19316
19928
  } catch {
19317
19929
  printWarn(" Could not install @codacy/analysis-cli automatically.");
@@ -19323,20 +19935,20 @@ function registerInitCommand(program2) {
19323
19935
  console.log("");
19324
19936
  printInfo("Installing skills...");
19325
19937
  const dataDir = resolveDataDir();
19326
- const skillsSource = (0, import_node_path24.join)(dataDir, "skills");
19938
+ const skillsSource = (0, import_node_path23.join)(dataDir, "skills");
19327
19939
  const skillsDest = ".claude/skills";
19328
19940
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
19329
19941
  let skillsInstalled = 0;
19330
19942
  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);
19943
+ const src = (0, import_node_path23.join)(skillsSource, skill);
19944
+ const dest = (0, import_node_path23.join)(skillsDest, skill);
19333
19945
  if (!(0, import_node_fs29.existsSync)(src)) {
19334
19946
  printWarn(` Skill data not found: ${skill}`);
19335
19947
  continue;
19336
19948
  }
19337
19949
  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");
19950
+ const srcSkill = (0, import_node_path23.join)(src, "SKILL.md");
19951
+ const destSkill = (0, import_node_path23.join)(dest, "SKILL.md");
19340
19952
  if ((0, import_node_fs29.existsSync)(destSkill)) {
19341
19953
  try {
19342
19954
  const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
@@ -19374,7 +19986,7 @@ function registerInitCommand(program2) {
19374
19986
  } catch (err) {
19375
19987
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
19376
19988
  }
19377
- const globalVerityDir = (0, import_node_path24.join)(process.env.HOME ?? "", ".verity");
19989
+ const globalVerityDir = (0, import_node_path23.join)(process.env.HOME ?? "", ".verity");
19378
19990
  await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
19379
19991
  console.log("");
19380
19992
  try {
@@ -19403,14 +20015,14 @@ function registerInitCommand(program2) {
19403
20015
  console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
19404
20016
  console.log("");
19405
20017
  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".)');
20018
+ console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity login".)');
19407
20019
  console.log("");
19408
20020
  });
19409
20021
  }
19410
20022
 
19411
20023
  // src/commands/uninstall.ts
19412
20024
  var import_node_fs30 = require("node:fs");
19413
- var import_node_path25 = require("node:path");
20025
+ var import_node_path24 = require("node:path");
19414
20026
  var SKILL_NAMES = [
19415
20027
  "verity-setup",
19416
20028
  "verity-analyze",
@@ -19429,7 +20041,7 @@ function registerUninstallCommand(program2) {
19429
20041
  const actions = [];
19430
20042
  const skillsRoot = projectPath(".claude/skills");
19431
20043
  for (const name of SKILL_NAMES) {
19432
- const dir = (0, import_node_path25.join)(skillsRoot, name);
20044
+ const dir = (0, import_node_path24.join)(skillsRoot, name);
19433
20045
  if ((0, import_node_fs30.existsSync)(dir)) {
19434
20046
  actions.push({
19435
20047
  label: `Remove .claude/skills/${name}/`,
@@ -19475,7 +20087,7 @@ function registerUninstallCommand(program2) {
19475
20087
  }
19476
20088
  });
19477
20089
  const home = process.env.HOME ?? "";
19478
- const globalVerityDir = (0, import_node_path25.join)(home, ".verity");
20090
+ const globalVerityDir = (0, import_node_path24.join)(home, ".verity");
19479
20091
  if (purgeGlobal && (0, import_node_fs30.existsSync)(globalVerityDir)) {
19480
20092
  actions.push({
19481
20093
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
@@ -19674,7 +20286,7 @@ function registerTaskCommands(program2) {
19674
20286
 
19675
20287
  // src/commands/reset.ts
19676
20288
  var import_node_fs31 = require("node:fs");
19677
- var import_node_path26 = require("node:path");
20289
+ var import_node_path25 = require("node:path");
19678
20290
  function registerResetCommand(program2) {
19679
20291
  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
20292
  const globals = program2.opts();
@@ -19715,7 +20327,7 @@ function registerResetCommand(program2) {
19715
20327
  for (const entry of (0, import_node_fs31.readdirSync)(cacheDir)) {
19716
20328
  if (entry.startsWith("pending-")) {
19717
20329
  try {
19718
- (0, import_node_fs31.unlinkSync)((0, import_node_path26.join)(cacheDir, entry));
20330
+ (0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(cacheDir, entry));
19719
20331
  purged++;
19720
20332
  } catch {
19721
20333
  }
@@ -19742,7 +20354,7 @@ function registerResetCommand(program2) {
19742
20354
  if ((0, import_node_fs31.existsSync)(logsDir)) {
19743
20355
  for (const entry of (0, import_node_fs31.readdirSync)(logsDir)) {
19744
20356
  try {
19745
- (0, import_node_fs31.unlinkSync)((0, import_node_path26.join)(logsDir, entry));
20357
+ (0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(logsDir, entry));
19746
20358
  } catch {
19747
20359
  }
19748
20360
  }
@@ -20010,7 +20622,11 @@ function registerTelemetryCommands(program2) {
20010
20622
  const globals = program2.opts();
20011
20623
  const tokenResult = await resolveToken(globals.token);
20012
20624
  if (tokenResult.ok) {
20013
- printJsonCompact({ Authorization: `Bearer ${tokenResult.data.token}` });
20625
+ const remote = requestRemote();
20626
+ printJsonCompact({
20627
+ Authorization: `Bearer ${tokenResult.data.token}`,
20628
+ ...remote ? { "X-Verity-Remote": remote } : {}
20629
+ });
20014
20630
  } else {
20015
20631
  printJsonCompact({});
20016
20632
  }
@@ -20040,7 +20656,7 @@ function registerTelemetryCommands(program2) {
20040
20656
  }
20041
20657
 
20042
20658
  // 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 () => {
20659
+ program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.af3c52d").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
20660
  try {
20045
20661
  await foldLegacyLocalCredential();
20046
20662
  } catch {
@@ -20048,6 +20664,9 @@ program.name("verity").description("CLI for Verity quality gate service").versio
20048
20664
  });
20049
20665
  registerAuthCommands(program);
20050
20666
  registerLoginCommand(program);
20667
+ registerTokenCommand(program);
20668
+ registerSessionsCommands(program);
20669
+ registerLogoutCommand(program);
20051
20670
  registerHooksCommands(program);
20052
20671
  registerIntentCommands(program);
20053
20672
  registerLifecycleCommands(program);