@codacy/verity-cli 0.26.0-experimental.d7dfc00 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/verity.js CHANGED
@@ -10325,7 +10325,9 @@ var {
10325
10325
  } = import_index.default;
10326
10326
 
10327
10327
  // src/commands/auth.ts
10328
- var import_node_child_process4 = require("node:child_process");
10328
+ var import_promises3 = require("node:fs/promises");
10329
+ var import_node_child_process3 = require("node:child_process");
10330
+ var import_node_path2 = require("node:path");
10329
10331
 
10330
10332
  // src/lib/auth.ts
10331
10333
  var import_promises = require("node:fs/promises");
@@ -10473,11 +10475,7 @@ var SECURITY_PATTERNS = [
10473
10475
  /Dockerfile/
10474
10476
  ];
10475
10477
  var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10476
- var DEFAULT_SERVICE_URL = "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1".length > 0 ? "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1" : PROD_SERVICE_URL;
10477
- var GITHUB_CLIENT_ID = "Ov23liBpj42KMTtUtN10";
10478
- var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
10479
- var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
10480
- var GITHUB_OAUTH_SCOPES = "repo user:email";
10478
+ var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
10481
10479
 
10482
10480
  // src/lib/auth.ts
10483
10481
  async function resolveToken(flagToken) {
@@ -10643,8 +10641,7 @@ async function apiRequest(options) {
10643
10641
  timeout = 9e4,
10644
10642
  cmd = "unknown",
10645
10643
  retry = false,
10646
- encodeBody = false,
10647
- extraHeaders
10644
+ encodeBody = false
10648
10645
  } = options;
10649
10646
  const url = `${serviceUrl}${path}`;
10650
10647
  const headers = {
@@ -10653,9 +10650,6 @@ async function apiRequest(options) {
10653
10650
  if (token) {
10654
10651
  headers["Authorization"] = `Bearer ${token}`;
10655
10652
  }
10656
- if (extraHeaders) {
10657
- Object.assign(headers, extraHeaders);
10658
- }
10659
10653
  const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
10660
10654
  if (testMockScenario) {
10661
10655
  headers["X-Verity-Mock-Scenario"] = testMockScenario;
@@ -10746,388 +10740,6 @@ function analyzeRequest(options) {
10746
10740
  });
10747
10741
  }
10748
10742
 
10749
- // src/lib/register.ts
10750
- var import_promises3 = require("node:fs/promises");
10751
- var import_node_path3 = require("node:path");
10752
-
10753
- // src/lib/provider-auth.ts
10754
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10755
- var form = (fields) => new URLSearchParams(fields).toString();
10756
- async function githubDeviceFlow() {
10757
- const override = process.env.VERITY_PROVIDER_TOKEN;
10758
- if (override) return { ok: true, data: override };
10759
- let dc;
10760
- try {
10761
- const res = await fetch(GITHUB_DEVICE_CODE_URL, {
10762
- method: "POST",
10763
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
10764
- body: form({ client_id: GITHUB_CLIENT_ID, scope: GITHUB_OAUTH_SCOPES })
10765
- });
10766
- if (!res.ok) {
10767
- return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
10768
- }
10769
- dc = await res.json();
10770
- } catch (err) {
10771
- return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
10772
- }
10773
- if (!dc.device_code || !dc.user_code) {
10774
- return {
10775
- ok: false,
10776
- error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
10777
- };
10778
- }
10779
- printInfo("");
10780
- printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
10781
- printInfo(`And enter the code: ${dc.user_code}`);
10782
- printInfo("Waiting for authorization\u2026");
10783
- const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
10784
- let interval = dc.interval || 5;
10785
- while (Date.now() < deadline) {
10786
- await sleep(interval * 1e3);
10787
- let data;
10788
- try {
10789
- const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
10790
- method: "POST",
10791
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
10792
- body: form({
10793
- client_id: GITHUB_CLIENT_ID,
10794
- device_code: dc.device_code,
10795
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
10796
- })
10797
- });
10798
- data = await res.json().catch(() => ({}));
10799
- } catch {
10800
- continue;
10801
- }
10802
- if (data.access_token) return { ok: true, data: data.access_token };
10803
- switch (data.error) {
10804
- case "authorization_pending":
10805
- break;
10806
- case "slow_down":
10807
- interval += 5;
10808
- break;
10809
- case "access_denied":
10810
- return { ok: false, error: "Authorization was denied on GitHub." };
10811
- case "expired_token":
10812
- return { ok: false, error: "The authorization code expired. Re-run register." };
10813
- default:
10814
- if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
10815
- }
10816
- }
10817
- return { ok: false, error: "Timed out waiting for GitHub authorization." };
10818
- }
10819
-
10820
- // src/lib/git.ts
10821
- var import_node_child_process3 = require("node:child_process");
10822
- var import_node_fs2 = require("node:fs");
10823
- var import_node_path2 = require("node:path");
10824
- function resolveFile(relpath) {
10825
- if ((0, import_node_fs2.existsSync)(relpath)) return relpath;
10826
- if ((0, import_node_fs2.existsSync)(".claude/worktrees")) {
10827
- try {
10828
- const entries = (0, import_node_fs2.readdirSync)(".claude/worktrees", { withFileTypes: true });
10829
- for (const entry of entries) {
10830
- if (!entry.isDirectory()) continue;
10831
- const candidate = (0, import_node_path2.join)(".claude/worktrees", entry.name, relpath);
10832
- if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
10833
- }
10834
- } catch {
10835
- }
10836
- }
10837
- return null;
10838
- }
10839
- function execGit(cmd) {
10840
- try {
10841
- return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10842
- } catch {
10843
- return "";
10844
- }
10845
- }
10846
- function splitLines(s) {
10847
- return s.split("\n").filter((l) => l.length > 0);
10848
- }
10849
- var SHA_RE = /^[0-9a-f]{40}$/;
10850
- function readBaselineSha() {
10851
- if (!(0, import_node_fs2.existsSync)(BASELINE_SHA_FILE)) return null;
10852
- let sha;
10853
- try {
10854
- sha = (0, import_node_fs2.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
10855
- } catch {
10856
- return null;
10857
- }
10858
- if (!SHA_RE.test(sha)) return null;
10859
- const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
10860
- if (!reachable) {
10861
- try {
10862
- (0, import_node_fs2.unlinkSync)(BASELINE_SHA_FILE);
10863
- } catch {
10864
- }
10865
- return null;
10866
- }
10867
- return sha;
10868
- }
10869
- function writeBaselineSha(sha) {
10870
- if (!SHA_RE.test(sha)) return;
10871
- try {
10872
- (0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(BASELINE_SHA_FILE), { recursive: true });
10873
- (0, import_node_fs2.writeFileSync)(BASELINE_SHA_FILE, sha);
10874
- } catch {
10875
- }
10876
- }
10877
- function getChangedFiles() {
10878
- const sets = /* @__PURE__ */ new Set();
10879
- let hasRecentCommitFiles = false;
10880
- for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
10881
- for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
10882
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
10883
- const baseline = readBaselineSha();
10884
- if (baseline) {
10885
- const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
10886
- if (committed.length > 0) {
10887
- hasRecentCommitFiles = true;
10888
- for (const f of committed) sets.add(f);
10889
- }
10890
- } else {
10891
- const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
10892
- const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
10893
- const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
10894
- const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
10895
- if (commitAge < 120 && !hasUnstaged && !hasStaged) {
10896
- const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
10897
- if (recentFiles.length > 0) {
10898
- hasRecentCommitFiles = true;
10899
- for (const f of recentFiles) sets.add(f);
10900
- }
10901
- }
10902
- }
10903
- for (const f of getWorktreeFiles()) sets.add(f);
10904
- const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10905
- return { files: filtered, hasRecentCommitFiles };
10906
- }
10907
- function getStagedFiles() {
10908
- return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10909
- }
10910
- function getDirtyFiles() {
10911
- const set = /* @__PURE__ */ new Set();
10912
- for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
10913
- for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
10914
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
10915
- return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10916
- }
10917
- function showContentAtRef(ref, repoRelPath) {
10918
- if (!ref || ref === "no-git") return null;
10919
- const normalizedPath = repoRelPath.replace(/\\/g, "/");
10920
- try {
10921
- return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
10922
- encoding: "utf-8",
10923
- maxBuffer: 64 * 1024 * 1024,
10924
- stdio: ["pipe", "pipe", "pipe"]
10925
- });
10926
- } catch {
10927
- return null;
10928
- }
10929
- }
10930
- function getPushRangeFiles() {
10931
- const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10932
- const resolvers = [
10933
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
10934
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
10935
- () => {
10936
- const branch = execGit("git rev-parse --abbrev-ref HEAD");
10937
- return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
10938
- }
10939
- ];
10940
- for (const resolve of resolvers) {
10941
- const range = resolve();
10942
- if (range) return { files: diff(range), range };
10943
- }
10944
- const baseline = readBaselineSha();
10945
- if (baseline) {
10946
- const files = diff(`${baseline}..HEAD`);
10947
- if (files.length > 0) return { files, range: `${baseline}..HEAD` };
10948
- }
10949
- const last = diff("HEAD~1..HEAD");
10950
- return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
10951
- }
10952
- function getPushRangeMessages() {
10953
- const { range } = getPushRangeFiles();
10954
- if (!range) return "";
10955
- return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
10956
- }
10957
- function getWorktreeFiles() {
10958
- const result = [];
10959
- const worktreeDir = ".claude/worktrees";
10960
- if (!(0, import_node_fs2.existsSync)(worktreeDir)) return result;
10961
- try {
10962
- const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
10963
- const entries = (0, import_node_fs2.readdirSync)(worktreeDir, { withFileTypes: true });
10964
- for (const entry of entries) {
10965
- if (!entry.isDirectory()) continue;
10966
- const wtDir = (0, import_node_path2.join)(worktreeDir, entry.name);
10967
- scanDir(wtDir, wtDir, fiveMinAgo, result);
10968
- }
10969
- } catch {
10970
- }
10971
- return result;
10972
- }
10973
- function scanDir(baseDir, dir, minMtime, result) {
10974
- try {
10975
- const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
10976
- for (const entry of entries) {
10977
- const fullPath = (0, import_node_path2.join)(dir, entry.name);
10978
- if (entry.isDirectory()) {
10979
- if (entry.name === "node_modules" || entry.name === ".git") continue;
10980
- scanDir(baseDir, fullPath, minMtime, result);
10981
- } else if (entry.isFile()) {
10982
- const ext = (0, import_node_path2.extname)(entry.name).slice(1);
10983
- if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
10984
- try {
10985
- const stat3 = (0, import_node_fs2.statSync)(fullPath);
10986
- if (stat3.mtimeMs >= minMtime) {
10987
- const relPath = fullPath.slice(baseDir.length + 1);
10988
- result.push(relPath);
10989
- }
10990
- } catch {
10991
- }
10992
- }
10993
- }
10994
- } catch {
10995
- }
10996
- }
10997
- function filterAnalyzable(files) {
10998
- return files.filter((f) => {
10999
- const ext = (0, import_node_path2.extname)(f).slice(1);
11000
- return ANALYZABLE_EXTENSIONS.has(ext);
11001
- });
11002
- }
11003
- function filterReviewable(files) {
11004
- return files.filter((f) => {
11005
- const ext = (0, import_node_path2.extname)(f).slice(1);
11006
- if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
11007
- if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
11008
- const basename3 = f.split("/").pop() ?? "";
11009
- if (REVIEWABLE_FILENAMES.has(basename3)) return true;
11010
- if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
11011
- return false;
11012
- });
11013
- }
11014
- function filterSecurity(files) {
11015
- return files.filter(
11016
- (f) => SECURITY_PATTERNS.some((p) => p.test(f))
11017
- );
11018
- }
11019
- function getCurrentCommit() {
11020
- return execGit("git rev-parse HEAD") || "no-git";
11021
- }
11022
- function detectProvider(host) {
11023
- const h = host.toLowerCase();
11024
- if (h.includes("github")) return "github";
11025
- if (h.includes("gitlab")) return "gitlab";
11026
- if (h.includes("bitbucket")) return "bitbucket";
11027
- return "unknown";
11028
- }
11029
- function parseRemote(raw) {
11030
- if (!raw || typeof raw !== "string") return null;
11031
- let s = raw.trim();
11032
- if (!s) return null;
11033
- let host = "";
11034
- let path = "";
11035
- const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
11036
- if (scp) {
11037
- host = scp[1];
11038
- path = scp[2];
11039
- } else {
11040
- s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
11041
- s = s.replace(/^[^/@]+@/, "");
11042
- const slash = s.indexOf("/");
11043
- if (slash === -1) return null;
11044
- host = s.slice(0, slash);
11045
- path = s.slice(slash + 1);
11046
- }
11047
- host = host.toLowerCase().trim();
11048
- path = path.replace(/\/+$/, "").replace(/\.git$/, "");
11049
- if (!host || !path) return null;
11050
- const segments = path.split("/").filter(Boolean);
11051
- if (segments.length < 2) return null;
11052
- const owner = segments[0];
11053
- const repo = segments[segments.length - 1];
11054
- if (!owner || !repo) return null;
11055
- return {
11056
- host,
11057
- owner,
11058
- repo,
11059
- provider: detectProvider(host),
11060
- orgUrl: `https://${host}/${owner}`,
11061
- orgName: owner
11062
- };
11063
- }
11064
- function listTrackedFiles() {
11065
- const set = /* @__PURE__ */ new Set();
11066
- for (const f of splitLines(execGit("git ls-files"))) set.add(f);
11067
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
11068
- return Array.from(set);
11069
- }
11070
-
11071
- // src/lib/register.ts
11072
- async function registerProject(opts) {
11073
- const parsed = parseRemote(opts.remote);
11074
- if (!parsed) {
11075
- return { ok: false, error: `Could not parse git remote: ${opts.remote}` };
11076
- }
11077
- if (parsed.provider !== "github") {
11078
- return { ok: false, error: `Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.` };
11079
- }
11080
- const providerAuth = await githubDeviceFlow();
11081
- if (!providerAuth.ok) {
11082
- return { ok: false, error: providerAuth.error };
11083
- }
11084
- const providerToken = providerAuth.data;
11085
- const result = await apiRequest({
11086
- method: "POST",
11087
- path: "/auth/register",
11088
- serviceUrl: opts.serviceUrl,
11089
- body: { project_name: opts.projectName, git_remote_url: opts.remote },
11090
- extraHeaders: { "X-Provider-Token": providerToken },
11091
- verbose: opts.verbose
11092
- });
11093
- if (!result.ok) {
11094
- return { ok: false, error: result.error };
11095
- }
11096
- const { project_id, token, service_url, user } = result.data;
11097
- try {
11098
- await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
11099
- await (0, import_promises3.writeFile)(
11100
- CREDENTIALS_FILE,
11101
- `token: ${token}
11102
- service_url: ${service_url}
11103
- provider_token: ${providerToken}
11104
- `,
11105
- { mode: 384 }
11106
- );
11107
- await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
11108
- });
11109
- } catch (err) {
11110
- return {
11111
- ok: false,
11112
- error: `Registered with the Verity service, but could not save credentials to ${CREDENTIALS_FILE}: ${err.message}. Check filesystem permissions and re-run "verity auth register".`
11113
- };
11114
- }
11115
- try {
11116
- await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
11117
- await (0, import_promises3.appendFile)(
11118
- GLOBAL_CREDENTIALS_FILE,
11119
- `${opts.remote} token: ${token}
11120
- ${opts.remote} provider_token: ${providerToken}
11121
- `,
11122
- { mode: 384 }
11123
- );
11124
- await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
11125
- });
11126
- } catch {
11127
- }
11128
- return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email: user?.email } };
11129
- }
11130
-
11131
10743
  // src/commands/auth.ts
11132
10744
  function registerAuthCommands(program2) {
11133
10745
  const auth = program2.command("auth").description("Manage project authentication");
@@ -11137,26 +10749,36 @@ function registerAuthCommands(program2) {
11137
10749
  let remote = opts.remote;
11138
10750
  if (!remote) {
11139
10751
  try {
11140
- remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10752
+ remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
11141
10753
  } catch {
11142
10754
  printError("No git remote found. Use --remote to specify one.");
11143
10755
  process.exit(1);
11144
10756
  }
11145
10757
  }
11146
- const result = await registerProject({
11147
- projectName: opts.project,
11148
- remote,
10758
+ const result = await apiRequest({
10759
+ method: "POST",
10760
+ path: "/auth/register",
11149
10761
  serviceUrl,
10762
+ body: { project_name: opts.project, git_remote_url: remote },
11150
10763
  verbose: globals.verbose
11151
10764
  });
11152
10765
  if (!result.ok) {
11153
10766
  printError(result.error);
11154
10767
  process.exit(1);
11155
10768
  }
11156
- const { projectId, serviceUrl: resolvedUrl, email } = result.data;
11157
- printInfo(`Project registered: ${projectId}`);
11158
- if (email) printInfo(`Authenticated as: ${email}`);
11159
- printJson({ project_id: projectId, service_url: resolvedUrl });
10769
+ const { project_id, token, service_url } = result.data;
10770
+ await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
10771
+ await (0, import_promises3.writeFile)(CREDENTIALS_FILE, `token: ${token}
10772
+ service_url: ${service_url}
10773
+ `);
10774
+ try {
10775
+ await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
10776
+ await (0, import_promises3.appendFile)(GLOBAL_CREDENTIALS_FILE, `${remote} token: ${token}
10777
+ `);
10778
+ } catch {
10779
+ }
10780
+ printInfo(`Project registered: ${project_id}`);
10781
+ printJson({ project_id, service_url });
11160
10782
  });
11161
10783
  auth.command("verify").description("Verify the current token is valid").action(async () => {
11162
10784
  const globals = program2.opts();
@@ -11189,7 +10811,7 @@ function registerAuthCommands(program2) {
11189
10811
  let remote = opts.remote;
11190
10812
  if (!remote) {
11191
10813
  try {
11192
- remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10814
+ remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
11193
10815
  } catch {
11194
10816
  printError("No git remote found. Use --remote to specify one.");
11195
10817
  process.exit(1);
@@ -11217,11 +10839,11 @@ function registerAuthCommands(program2) {
11217
10839
 
11218
10840
  // src/lib/hooks.ts
11219
10841
  var import_promises5 = require("node:fs/promises");
11220
- var import_node_path5 = require("node:path");
10842
+ var import_node_path4 = require("node:path");
11221
10843
 
11222
10844
  // src/lib/json-file.ts
11223
10845
  var import_promises4 = require("node:fs/promises");
11224
- var import_node_path4 = require("node:path");
10846
+ var import_node_path3 = require("node:path");
11225
10847
  function jsonSemanticEqual(a, b) {
11226
10848
  if (a === b) return true;
11227
10849
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
@@ -11267,7 +10889,7 @@ async function writeJsonFilePreservingStyle(file, value) {
11267
10889
  const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
11268
10890
  const next = JSON.stringify(value, null, indent) + "\n";
11269
10891
  if (next === currentRaw) return false;
11270
- await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
10892
+ await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(file), { recursive: true });
11271
10893
  await (0, import_promises4.writeFile)(file, next);
11272
10894
  return true;
11273
10895
  }
@@ -11428,13 +11050,13 @@ async function writeSettings(settings) {
11428
11050
  }
11429
11051
  async function readSettingsAt(root) {
11430
11052
  try {
11431
- return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11053
+ return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11432
11054
  } catch {
11433
11055
  return {};
11434
11056
  }
11435
11057
  }
11436
11058
  async function writeSettingsAt(root, settings) {
11437
- await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
11059
+ await writeJsonFilePreservingStyle((0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE), settings);
11438
11060
  }
11439
11061
  async function hasLegacyHooksAt(root) {
11440
11062
  const settings = await readSettingsAt(root);
@@ -11674,8 +11296,8 @@ var import_node_crypto3 = require("node:crypto");
11674
11296
 
11675
11297
  // src/lib/conversation-buffer.ts
11676
11298
  var import_promises6 = require("node:fs/promises");
11677
- var import_node_fs3 = require("node:fs");
11678
- var import_node_child_process5 = require("node:child_process");
11299
+ var import_node_fs2 = require("node:fs");
11300
+ var import_node_child_process4 = require("node:child_process");
11679
11301
  var import_node_crypto = require("node:crypto");
11680
11302
  function stripImageReferences(text) {
11681
11303
  return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
@@ -11710,7 +11332,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
11710
11332
  }
11711
11333
  async function readAndClearConversationBuffer(currentSessionId) {
11712
11334
  try {
11713
- if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
11335
+ if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
11714
11336
  const entries = await readBufferEntries();
11715
11337
  let mine = entries;
11716
11338
  let others = [];
@@ -11734,7 +11356,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
11734
11356
  };
11735
11357
  }
11736
11358
  }
11737
- if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
11359
+ if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
11738
11360
  try {
11739
11361
  const content = await (0, import_promises6.readFile)(INTENT_FILE, "utf-8");
11740
11362
  await (0, import_promises6.unlink)(INTENT_FILE).catch(() => {
@@ -11778,7 +11400,7 @@ async function readBufferEntries() {
11778
11400
  }
11779
11401
  function getRecentCommitMessages() {
11780
11402
  try {
11781
- const output = (0, import_node_child_process5.execSync)(
11403
+ const output = (0, import_node_child_process4.execSync)(
11782
11404
  'git log --since="30 minutes ago" --format="%s" -5',
11783
11405
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
11784
11406
  ).trim();
@@ -11791,8 +11413,8 @@ function getRecentCommitMessages() {
11791
11413
 
11792
11414
  // src/lib/task-context-buffer.ts
11793
11415
  var import_promises7 = require("node:fs/promises");
11794
- var import_node_fs4 = require("node:fs");
11795
- var import_node_path6 = require("node:path");
11416
+ var import_node_fs3 = require("node:fs");
11417
+ var import_node_path5 = require("node:path");
11796
11418
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
11797
11419
  var MAX_BUFFER_BYTES = 500 * 1024;
11798
11420
  var MAX_PROMPT_CHARS = 2e3;
@@ -11831,7 +11453,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
11831
11453
  }
11832
11454
  async function readTaskContextBuffer(taskId) {
11833
11455
  const filePath = bufferPath(taskId);
11834
- if (!(0, import_node_fs4.existsSync)(filePath)) return null;
11456
+ if (!(0, import_node_fs3.existsSync)(filePath)) return null;
11835
11457
  try {
11836
11458
  const content = await (0, import_promises7.readFile)(filePath, "utf-8");
11837
11459
  if (!content.trim()) return null;
@@ -11865,12 +11487,12 @@ async function readTaskContextBuffer(taskId) {
11865
11487
  }
11866
11488
  async function cleanupTaskContextBuffers() {
11867
11489
  try {
11868
- if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
11490
+ if (!(0, import_node_fs3.existsSync)(TASK_CONTEXT_DIR)) return;
11869
11491
  const files = await (0, import_promises7.readdir)(TASK_CONTEXT_DIR);
11870
11492
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
11871
11493
  for (const file of files) {
11872
11494
  if (!file.endsWith(".jsonl")) continue;
11873
- const filePath = (0, import_node_path6.join)(TASK_CONTEXT_DIR, file);
11495
+ const filePath = (0, import_node_path5.join)(TASK_CONTEXT_DIR, file);
11874
11496
  try {
11875
11497
  const stats = await (0, import_promises7.stat)(filePath);
11876
11498
  if (stats.mtimeMs < cutoffMs) {
@@ -11884,13 +11506,13 @@ async function cleanupTaskContextBuffers() {
11884
11506
  }
11885
11507
  function bufferPath(taskId) {
11886
11508
  const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
11887
- return (0, import_node_path6.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11509
+ return (0, import_node_path5.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11888
11510
  }
11889
11511
  async function appendEntry(taskId, entry) {
11890
11512
  try {
11891
11513
  await (0, import_promises7.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
11892
11514
  const filePath = bufferPath(taskId);
11893
- if ((0, import_node_fs4.existsSync)(filePath)) {
11515
+ if ((0, import_node_fs3.existsSync)(filePath)) {
11894
11516
  const stats = await (0, import_promises7.stat)(filePath);
11895
11517
  if (stats.size >= MAX_BUFFER_BYTES) {
11896
11518
  const content = await (0, import_promises7.readFile)(filePath, "utf-8");
@@ -11901,7 +11523,7 @@ async function appendEntry(taskId, entry) {
11901
11523
  }
11902
11524
  }
11903
11525
  const line = JSON.stringify(entry) + "\n";
11904
- const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
11526
+ const existing = (0, import_node_fs3.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
11905
11527
  await (0, import_promises7.writeFile)(filePath, existing + line);
11906
11528
  } catch {
11907
11529
  }
@@ -11909,8 +11531,8 @@ async function appendEntry(taskId, entry) {
11909
11531
 
11910
11532
  // src/lib/memory-retrieval.ts
11911
11533
  var import_promises8 = require("node:fs/promises");
11912
- var import_node_fs5 = require("node:fs");
11913
- var import_node_path7 = require("node:path");
11534
+ var import_node_fs4 = require("node:fs");
11535
+ var import_node_path6 = require("node:path");
11914
11536
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
11915
11537
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
11916
11538
  var DEFAULT_BUDGET_TOKENS = 2e3;
@@ -12003,19 +11625,19 @@ function parseFrontmatter(content) {
12003
11625
  return { fm, body: match[2].trim() };
12004
11626
  }
12005
11627
  async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
12006
- if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
11628
+ if (!(0, import_node_fs4.existsSync)(memoryDir())) return null;
12007
11629
  const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
12008
11630
  const promptTokens = tokenize(promptText);
12009
11631
  const nodes = [];
12010
11632
  for (const domain of DOMAINS) {
12011
- const domainDir = (0, import_node_path7.join)(memoryDir(), domain);
12012
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
11633
+ const domainDir = (0, import_node_path6.join)(memoryDir(), domain);
11634
+ if (!(0, import_node_fs4.existsSync)(domainDir)) continue;
12013
11635
  try {
12014
11636
  const files = await (0, import_promises8.readdir)(domainDir);
12015
11637
  for (const file of files) {
12016
11638
  if (!file.endsWith(".md")) continue;
12017
11639
  try {
12018
- const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
11640
+ const content = await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
12019
11641
  const { fm, body } = parseFrontmatter(content);
12020
11642
  if (fm.status && fm.status !== "active") continue;
12021
11643
  nodes.push({
@@ -12074,8 +11696,8 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
12074
11696
 
12075
11697
  // src/lib/memory-sync.ts
12076
11698
  var import_promises9 = require("node:fs/promises");
12077
- var import_node_fs6 = require("node:fs");
12078
- var import_node_path8 = require("node:path");
11699
+ var import_node_fs5 = require("node:fs");
11700
+ var import_node_path7 = require("node:path");
12079
11701
  var import_node_crypto2 = require("node:crypto");
12080
11702
 
12081
11703
  // src/lib/glob-match.ts
@@ -12146,32 +11768,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
12146
11768
  async function ensureMemoryDir() {
12147
11769
  await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
12148
11770
  for (const domain of DOMAINS2) {
12149
- await (0, import_promises9.mkdir)((0, import_node_path8.join)(memoryDir2(), domain), { recursive: true });
11771
+ await (0, import_promises9.mkdir)((0, import_node_path7.join)(memoryDir2(), domain), { recursive: true });
12150
11772
  }
12151
- if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"))) {
12152
- await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
11773
+ if (!(0, import_node_fs5.existsSync)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"))) {
11774
+ await (0, import_promises9.writeFile)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12153
11775
  }
12154
- if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "index.md"))) {
12155
- await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
11776
+ if (!(0, import_node_fs5.existsSync)((0, import_node_path7.join)(memoryDir2(), "index.md"))) {
11777
+ await (0, import_promises9.writeFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
12156
11778
  }
12157
- if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md"))) {
12158
- await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
11779
+ if (!(0, import_node_fs5.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md"))) {
11780
+ await (0, import_promises9.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12159
11781
  }
12160
11782
  }
12161
11783
  async function buildManifest() {
12162
- if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
11784
+ if (!(0, import_node_fs5.existsSync)(memoryDir2())) {
12163
11785
  return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
12164
11786
  }
12165
11787
  const nodes = [];
12166
11788
  for (const domain of DOMAINS2) {
12167
- const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12168
- if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11789
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
11790
+ if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12169
11791
  try {
12170
11792
  const files = await (0, import_promises9.readdir)(domainDir);
12171
11793
  for (const file of files) {
12172
11794
  if (!file.endsWith(".md")) continue;
12173
11795
  const filePath = `${domain}/${file}`;
12174
- const fullPath = (0, import_node_path8.join)(memoryDir2(), filePath);
11796
+ const fullPath = (0, import_node_path7.join)(memoryDir2(), filePath);
12175
11797
  try {
12176
11798
  const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
12177
11799
  const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
@@ -12184,13 +11806,13 @@ async function buildManifest() {
12184
11806
  }
12185
11807
  let indexHash = null;
12186
11808
  try {
12187
- const indexContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "utf-8");
11809
+ const indexContent = await (0, import_promises9.readFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "utf-8");
12188
11810
  indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
12189
11811
  } catch {
12190
11812
  }
12191
11813
  let logLength = 0;
12192
11814
  try {
12193
- const logContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8");
11815
+ const logContent = await (0, import_promises9.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8");
12194
11816
  logLength = logContent.split("\n").length;
12195
11817
  } catch {
12196
11818
  }
@@ -12201,15 +11823,15 @@ function hashContent(content) {
12201
11823
  }
12202
11824
  async function readOnDiskNodes() {
12203
11825
  const out = /* @__PURE__ */ new Map();
12204
- if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
11826
+ if (!(0, import_node_fs5.existsSync)(memoryDir2())) return out;
12205
11827
  for (const domain of DOMAINS2) {
12206
- const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12207
- if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11828
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
11829
+ if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12208
11830
  try {
12209
11831
  for (const file of await (0, import_promises9.readdir)(domainDir)) {
12210
11832
  if (!file.endsWith(".md")) continue;
12211
11833
  try {
12212
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8")));
11834
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8")));
12213
11835
  } catch {
12214
11836
  }
12215
11837
  }
@@ -12255,8 +11877,8 @@ async function computeEditedNodeUploads() {
12255
11877
  const uploads = [];
12256
11878
  for (const [path, prevHash] of prev) {
12257
11879
  if (prevHash == null) continue;
12258
- const full = (0, import_node_path8.join)(memoryDir2(), path);
12259
- if (!(0, import_node_fs6.existsSync)(full)) continue;
11880
+ const full = (0, import_node_path7.join)(memoryDir2(), path);
11881
+ if (!(0, import_node_fs5.existsSync)(full)) continue;
12260
11882
  let content;
12261
11883
  try {
12262
11884
  content = await (0, import_promises9.readFile)(full, "utf-8");
@@ -12292,15 +11914,15 @@ async function applyMemoryWrites(writes, opts = {}) {
12292
11914
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
12293
11915
  for (const n of notes) logLines.push(` - ${n}`);
12294
11916
  try {
12295
- const existing = (0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
12296
- await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
11917
+ const existing = (0, import_node_fs5.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
11918
+ await (0, import_promises9.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
12297
11919
  } catch {
12298
11920
  }
12299
11921
  await recordSyncedNodePaths();
12300
11922
  return count;
12301
11923
  }
12302
11924
  async function applyOneWrite(write, treePaths) {
12303
- const fullPath = (0, import_node_path8.join)(memoryDir2(), write.path);
11925
+ const fullPath = (0, import_node_path7.join)(memoryDir2(), write.path);
12304
11926
  const notes = [];
12305
11927
  let content = write.content;
12306
11928
  if (treePaths && treePaths.length > 0) {
@@ -12310,7 +11932,7 @@ async function applyOneWrite(write, treePaths) {
12310
11932
  notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
12311
11933
  }
12312
11934
  }
12313
- if ((0, import_node_fs6.existsSync)(fullPath)) {
11935
+ if ((0, import_node_fs5.existsSync)(fullPath)) {
12314
11936
  let existing = "";
12315
11937
  try {
12316
11938
  existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
@@ -12322,7 +11944,7 @@ async function applyOneWrite(write, treePaths) {
12322
11944
  return { written: false, notes };
12323
11945
  }
12324
11946
  }
12325
- await (0, import_promises9.mkdir)((0, import_node_path8.dirname)(fullPath), { recursive: true });
11947
+ await (0, import_promises9.mkdir)((0, import_node_path7.dirname)(fullPath), { recursive: true });
12326
11948
  await (0, import_promises9.writeFile)(fullPath, content);
12327
11949
  return { written: true, notes };
12328
11950
  }
@@ -12363,8 +11985,8 @@ async function regenerateIndex() {
12363
11985
  ];
12364
11986
  let totalNodes = 0;
12365
11987
  for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
12366
- const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12367
- if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11988
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
11989
+ if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12368
11990
  try {
12369
11991
  const files = await (0, import_promises9.readdir)(domainDir);
12370
11992
  const mdFiles = files.filter((f) => f.endsWith(".md"));
@@ -12374,7 +11996,7 @@ async function regenerateIndex() {
12374
11996
  for (const file of mdFiles.sort()) {
12375
11997
  const slug = file.replace(/\.md$/, "");
12376
11998
  try {
12377
- const content = await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
11999
+ const content = await (0, import_promises9.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
12378
12000
  const title = pickFrontmatter(content, "title") ?? slug;
12379
12001
  const kind = pickFrontmatter(content, "kind") ?? "-";
12380
12002
  const confidence = pickFrontmatter(content, "confidence");
@@ -12398,7 +12020,7 @@ async function regenerateIndex() {
12398
12020
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
12399
12021
  }
12400
12022
  const next = lines.join("\n") + "\n";
12401
- const indexPath = (0, import_node_path8.join)(memoryDir2(), "index.md");
12023
+ const indexPath = (0, import_node_path7.join)(memoryDir2(), "index.md");
12402
12024
  let existing = null;
12403
12025
  try {
12404
12026
  existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
@@ -12480,9 +12102,9 @@ function hasLegacyMemoryBlock(text) {
12480
12102
  return findMarker(text, LEGACY_MD_START) !== -1;
12481
12103
  }
12482
12104
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
12483
- const claudeMdPath = (0, import_node_path8.join)(cwd, "CLAUDE.md");
12105
+ const claudeMdPath = (0, import_node_path7.join)(cwd, "CLAUDE.md");
12484
12106
  let existing = "";
12485
- if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
12107
+ if ((0, import_node_fs5.existsSync)(claudeMdPath)) {
12486
12108
  existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
12487
12109
  }
12488
12110
  let startTag = CLAUDE_MD_START;
@@ -12612,7 +12234,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
12612
12234
  `;
12613
12235
 
12614
12236
  // src/commands/intent.ts
12615
- var import_node_fs7 = require("node:fs");
12237
+ var import_node_fs6 = require("node:fs");
12616
12238
  function registerIntentCommands(program2) {
12617
12239
  const intent = program2.command("intent").description("Manage intent capture");
12618
12240
  intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
@@ -12621,7 +12243,7 @@ function registerIntentCommands(program2) {
12621
12243
  process.chdir(repoRoot());
12622
12244
  } catch {
12623
12245
  }
12624
- if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
12246
+ if (!(0, import_node_fs6.existsSync)(VERITY_DIR)) {
12625
12247
  process.exit(0);
12626
12248
  }
12627
12249
  const chunks = [];
@@ -13200,6 +12822,215 @@ async function sendGeneralFeedback(message, opts, globals) {
13200
12822
  var import_node_fs19 = require("node:fs");
13201
12823
  var import_node_path15 = require("node:path");
13202
12824
 
12825
+ // src/lib/git.ts
12826
+ var import_node_child_process5 = require("node:child_process");
12827
+ var import_node_fs7 = require("node:fs");
12828
+ var import_node_path8 = require("node:path");
12829
+ function resolveFile(relpath) {
12830
+ if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
12831
+ if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
12832
+ try {
12833
+ const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
12834
+ for (const entry of entries) {
12835
+ if (!entry.isDirectory()) continue;
12836
+ const candidate = (0, import_node_path8.join)(".claude/worktrees", entry.name, relpath);
12837
+ if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
12838
+ }
12839
+ } catch {
12840
+ }
12841
+ }
12842
+ return null;
12843
+ }
12844
+ function execGit(cmd) {
12845
+ try {
12846
+ return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
12847
+ } catch {
12848
+ return "";
12849
+ }
12850
+ }
12851
+ function splitLines(s) {
12852
+ return s.split("\n").filter((l) => l.length > 0);
12853
+ }
12854
+ var SHA_RE = /^[0-9a-f]{40}$/;
12855
+ function readBaselineSha() {
12856
+ if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
12857
+ let sha;
12858
+ try {
12859
+ sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
12860
+ } catch {
12861
+ return null;
12862
+ }
12863
+ if (!SHA_RE.test(sha)) return null;
12864
+ const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
12865
+ if (!reachable) {
12866
+ try {
12867
+ (0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
12868
+ } catch {
12869
+ }
12870
+ return null;
12871
+ }
12872
+ return sha;
12873
+ }
12874
+ function writeBaselineSha(sha) {
12875
+ if (!SHA_RE.test(sha)) return;
12876
+ try {
12877
+ (0, import_node_fs7.mkdirSync)((0, import_node_path8.dirname)(BASELINE_SHA_FILE), { recursive: true });
12878
+ (0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
12879
+ } catch {
12880
+ }
12881
+ }
12882
+ function getChangedFiles() {
12883
+ const sets = /* @__PURE__ */ new Set();
12884
+ let hasRecentCommitFiles = false;
12885
+ for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
12886
+ for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
12887
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
12888
+ const baseline = readBaselineSha();
12889
+ if (baseline) {
12890
+ const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
12891
+ if (committed.length > 0) {
12892
+ hasRecentCommitFiles = true;
12893
+ for (const f of committed) sets.add(f);
12894
+ }
12895
+ } else {
12896
+ const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
12897
+ const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
12898
+ const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
12899
+ const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
12900
+ if (commitAge < 120 && !hasUnstaged && !hasStaged) {
12901
+ const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
12902
+ if (recentFiles.length > 0) {
12903
+ hasRecentCommitFiles = true;
12904
+ for (const f of recentFiles) sets.add(f);
12905
+ }
12906
+ }
12907
+ }
12908
+ for (const f of getWorktreeFiles()) sets.add(f);
12909
+ const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12910
+ return { files: filtered, hasRecentCommitFiles };
12911
+ }
12912
+ function getStagedFiles() {
12913
+ return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12914
+ }
12915
+ function getDirtyFiles() {
12916
+ const set = /* @__PURE__ */ new Set();
12917
+ for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
12918
+ for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
12919
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
12920
+ return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12921
+ }
12922
+ function showContentAtRef(ref, repoRelPath) {
12923
+ if (!ref || ref === "no-git") return null;
12924
+ const normalizedPath = repoRelPath.replace(/\\/g, "/");
12925
+ try {
12926
+ return (0, import_node_child_process5.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
12927
+ encoding: "utf-8",
12928
+ maxBuffer: 64 * 1024 * 1024,
12929
+ stdio: ["pipe", "pipe", "pipe"]
12930
+ });
12931
+ } catch {
12932
+ return null;
12933
+ }
12934
+ }
12935
+ function getPushRangeFiles() {
12936
+ const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12937
+ const resolvers = [
12938
+ () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
12939
+ () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
12940
+ () => {
12941
+ const branch = execGit("git rev-parse --abbrev-ref HEAD");
12942
+ return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
12943
+ }
12944
+ ];
12945
+ for (const resolve of resolvers) {
12946
+ const range = resolve();
12947
+ if (range) return { files: diff(range), range };
12948
+ }
12949
+ const baseline = readBaselineSha();
12950
+ if (baseline) {
12951
+ const files = diff(`${baseline}..HEAD`);
12952
+ if (files.length > 0) return { files, range: `${baseline}..HEAD` };
12953
+ }
12954
+ const last = diff("HEAD~1..HEAD");
12955
+ return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
12956
+ }
12957
+ function getPushRangeMessages() {
12958
+ const { range } = getPushRangeFiles();
12959
+ if (!range) return "";
12960
+ return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
12961
+ }
12962
+ function getWorktreeFiles() {
12963
+ const result = [];
12964
+ const worktreeDir = ".claude/worktrees";
12965
+ if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
12966
+ try {
12967
+ const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
12968
+ const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
12969
+ for (const entry of entries) {
12970
+ if (!entry.isDirectory()) continue;
12971
+ const wtDir = (0, import_node_path8.join)(worktreeDir, entry.name);
12972
+ scanDir(wtDir, wtDir, fiveMinAgo, result);
12973
+ }
12974
+ } catch {
12975
+ }
12976
+ return result;
12977
+ }
12978
+ function scanDir(baseDir, dir, minMtime, result) {
12979
+ try {
12980
+ const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
12981
+ for (const entry of entries) {
12982
+ const fullPath = (0, import_node_path8.join)(dir, entry.name);
12983
+ if (entry.isDirectory()) {
12984
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
12985
+ scanDir(baseDir, fullPath, minMtime, result);
12986
+ } else if (entry.isFile()) {
12987
+ const ext = (0, import_node_path8.extname)(entry.name).slice(1);
12988
+ if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
12989
+ try {
12990
+ const stat3 = (0, import_node_fs7.statSync)(fullPath);
12991
+ if (stat3.mtimeMs >= minMtime) {
12992
+ const relPath = fullPath.slice(baseDir.length + 1);
12993
+ result.push(relPath);
12994
+ }
12995
+ } catch {
12996
+ }
12997
+ }
12998
+ }
12999
+ } catch {
13000
+ }
13001
+ }
13002
+ function filterAnalyzable(files) {
13003
+ return files.filter((f) => {
13004
+ const ext = (0, import_node_path8.extname)(f).slice(1);
13005
+ return ANALYZABLE_EXTENSIONS.has(ext);
13006
+ });
13007
+ }
13008
+ function filterReviewable(files) {
13009
+ return files.filter((f) => {
13010
+ const ext = (0, import_node_path8.extname)(f).slice(1);
13011
+ if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
13012
+ if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
13013
+ const basename2 = f.split("/").pop() ?? "";
13014
+ if (REVIEWABLE_FILENAMES.has(basename2)) return true;
13015
+ if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
13016
+ return false;
13017
+ });
13018
+ }
13019
+ function filterSecurity(files) {
13020
+ return files.filter(
13021
+ (f) => SECURITY_PATTERNS.some((p) => p.test(f))
13022
+ );
13023
+ }
13024
+ function getCurrentCommit() {
13025
+ return execGit("git rev-parse HEAD") || "no-git";
13026
+ }
13027
+ function listTrackedFiles() {
13028
+ const set = /* @__PURE__ */ new Set();
13029
+ for (const f of splitLines(execGit("git ls-files"))) set.add(f);
13030
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
13031
+ return Array.from(set);
13032
+ }
13033
+
13203
13034
  // src/lib/files.ts
13204
13035
  var import_node_fs8 = require("node:fs");
13205
13036
  var import_node_path9 = require("node:path");
@@ -14807,27 +14638,6 @@ function passAndExit(reason) {
14807
14638
  printJsonCompact({ gate_decision: "PASS", systemMessage: `Verity: ${reason}` });
14808
14639
  process.exit(0);
14809
14640
  }
14810
- var EMPTY_STATIC = {
14811
- tool: "@codacy/analysis-cli",
14812
- findings: [],
14813
- summary: { total_findings: 0, by_severity: {}, tools_run: [] }
14814
- };
14815
- function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
14816
- if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
14817
- let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14818
- if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
14819
- if (scannable.length === 0) return EMPTY_STATIC;
14820
- return runCodacyAnalysis(scannable);
14821
- }
14822
- function localOnlyAndExit(staticResults) {
14823
- printJsonCompact({
14824
- gate_decision: "PASS",
14825
- systemMessage: "Verity: not authenticated \u2014 ran a local static-only check (no deep review, no upload). Run `verity init` to authenticate and enable the full quality gate.",
14826
- unauthenticated: true,
14827
- static_results: staticResults
14828
- });
14829
- process.exit(0);
14830
- }
14831
14641
  function registerAnalyzeCommand(program2) {
14832
14642
  program2.command("analyze").description("Run Verity analysis on changed files (stop hook)").option("--debounce <seconds>", "Skip if last analysis was within N seconds", "30").option("--max-iterations <n>", "Force PASS after N FAIL cycles", "2").option("--max-files <n>", "Max files to send for review", "20").option("--max-file-size <bytes>", "Skip files larger than N bytes", "51200").option("--max-total-size <bytes>", "Stop collecting files at N total bytes", "194560").option("--skip-static", "Skip codacy-analysis").option("--mode <mode>", "Force analysis mode (standard|plan|debug|skip)").option("--json", "Output raw JSON response").action(async (opts) => {
14833
14643
  const globals = program2.opts();
@@ -14878,9 +14688,12 @@ async function runAnalyze(opts, globals) {
14878
14688
  passAndExit("Reflection-prompt turn \u2014 skipping analysis");
14879
14689
  }
14880
14690
  const tokenResult = await resolveToken(globals.token);
14691
+ if (!tokenResult.ok) {
14692
+ passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14693
+ }
14881
14694
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
14882
- if (!tokenResult.ok || !urlResult.ok) {
14883
- localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
14695
+ if (!urlResult.ok) {
14696
+ passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14884
14697
  }
14885
14698
  const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
14886
14699
  let contextFilePaths = [];
@@ -15454,14 +15267,13 @@ async function runReview(opts, globals) {
15454
15267
  }
15455
15268
  const codeDelta = collectCodeDelta(allFiles);
15456
15269
  const tokenResult = await resolveToken(globals.token);
15270
+ if (!tokenResult.ok) {
15271
+ printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
15272
+ process.exit(0);
15273
+ }
15457
15274
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
15458
- if (!tokenResult.ok || !urlResult.ok) {
15459
- printJsonCompact({
15460
- gate_decision: "PASS",
15461
- systemMessage: "Verity: not authenticated \u2014 showing local static results only (no deep review, no upload). Run `verity init` to authenticate and unlock the full review.",
15462
- unauthenticated: true,
15463
- static_results: staticResults
15464
- });
15275
+ if (!urlResult.ok) {
15276
+ printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
15465
15277
  process.exit(0);
15466
15278
  }
15467
15279
  let specs;
@@ -15850,7 +15662,6 @@ var import_node_fs24 = require("node:fs");
15850
15662
  var import_promises13 = require("node:fs/promises");
15851
15663
  var import_node_path18 = require("node:path");
15852
15664
  var import_node_child_process9 = require("node:child_process");
15853
- var readline = __toESM(require("node:readline/promises"));
15854
15665
 
15855
15666
  // src/commands/migrate.ts
15856
15667
  var import_node_fs23 = require("node:fs");
@@ -16130,66 +15941,6 @@ function registerMigrateCommand(program2) {
16130
15941
  }
16131
15942
 
16132
15943
  // src/commands/init.ts
16133
- async function promptYes(question) {
16134
- if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
16135
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
16136
- try {
16137
- const answer = (await rl.question(question)).trim().toLowerCase();
16138
- return answer === "" || answer === "y" || answer === "yes";
16139
- } finally {
16140
- rl.close();
16141
- }
16142
- }
16143
- async function runOptionalAuth() {
16144
- const existing = await resolveToken();
16145
- if (existing.ok) {
16146
- printInfo("Already authenticated \u2014 results will upload to the Verity service. \u2713");
16147
- return;
16148
- }
16149
- let remote = "";
16150
- try {
16151
- remote = (0, import_node_child_process9.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
16152
- } catch {
16153
- }
16154
- const localOnlyNote = () => {
16155
- printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
16156
- printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
16157
- };
16158
- if (process.stdin.isTTY && process.stdout.isTTY) {
16159
- console.log("");
16160
- console.log(" Signing in is optional. What it does:");
16161
- console.log(" - Confirms you have write access to this repository. The GitHub token");
16162
- console.log(" is used once to verify that, then discarded \u2014 Verity never stores it.");
16163
- console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
16164
- console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
16165
- console.log(" - It is required to store and access run history for this repo");
16166
- console.log(" (past results, trends, and shareable reports).");
16167
- console.log(" - Skip and Verity still works fully locally: the gate runs and shows");
16168
- console.log(" findings, but nothing is uploaded.");
16169
- console.log("");
16170
- }
16171
- const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
16172
- if (!wantsAuth) {
16173
- printInfo("Skipped authentication.");
16174
- localOnlyNote();
16175
- return;
16176
- }
16177
- if (!remote) {
16178
- printWarn("No git remote found \u2014 cannot authenticate yet.");
16179
- localOnlyNote();
16180
- return;
16181
- }
16182
- const projectName = parseRemote(remote)?.repo ?? (0, import_node_path18.basename)(process.cwd());
16183
- printInfo("Authenticating with GitHub\u2026");
16184
- const result = await registerProject({ projectName, remote, serviceUrl: DEFAULT_SERVICE_URL });
16185
- if (result.ok) {
16186
- printInfo(`Project registered: ${result.data.projectId} \u2713`);
16187
- if (result.data.email) printInfo(` Authenticated as: ${result.data.email}`);
16188
- } else {
16189
- printWarn(`Authentication did not complete: ${result.error}`);
16190
- localOnlyNote();
16191
- }
16192
- }
16193
15944
  function resolveDataDir() {
16194
15945
  const candidates = [
16195
15946
  (0, import_node_path18.join)(__dirname, "..", "data"),
@@ -16333,12 +16084,6 @@ function registerInitCommand(program2) {
16333
16084
  const globalVerityDir = (0, import_node_path18.join)(process.env.HOME ?? "", ".verity");
16334
16085
  await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
16335
16086
  console.log("");
16336
- try {
16337
- await runOptionalAuth();
16338
- } catch (err) {
16339
- printWarn(`Authentication step skipped: ${err.message}`);
16340
- }
16341
- console.log("");
16342
16087
  printInfo("Verity initialized!");
16343
16088
  console.log("");
16344
16089
  console.log(" Installed:");
@@ -16354,7 +16099,6 @@ function registerInitCommand(program2) {
16354
16099
  console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
16355
16100
  console.log("");
16356
16101
  console.log(" Next step: open this project in Claude Code and run /verity-setup");
16357
- console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
16358
16102
  console.log("");
16359
16103
  });
16360
16104
  }
@@ -17083,7 +16827,7 @@ function registerTelemetryCommands(program2) {
17083
16827
  }
17084
16828
 
17085
16829
  // src/cli.ts
17086
- program.name("verity").description("CLI for Verity quality gate service").version("0.26.0-experimental.d7dfc00").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16830
+ program.name("verity").description("CLI for Verity quality gate service").version("0.26.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
17087
16831
  registerAuthCommands(program);
17088
16832
  registerHooksCommands(program);
17089
16833
  registerIntentCommands(program);
@@ -15,13 +15,7 @@ You are running an on-demand Verity analysis. This is like a "second opinion"
15
15
 
16
16
  1. Verify `.verity/standard.yaml` exists. If not: "Run `/verity-setup` first."
17
17
  2. Verify `verity` CLI is available: `which verity`. If not: "Re-run the Verity installer: `curl -fsSL https://raw.githubusercontent.com/codacy/verity/main/install.sh | bash`"
18
- 3. Run `verity auth verify` to check the token is valid.
19
- - **Valid**: proceed with the full deep review.
20
- - **Fails / not authenticated**: don't stop. Tell the user Verity is in
21
- **local-only mode**, so the review will run static analysis and show findings
22
- but won't perform the deep (LLM) review or upload anything. To unlock the deep
23
- review they can authenticate with `verity init` (or `verity auth register`).
24
- Continue — `verity review` degrades to a local static-only report on its own.
18
+ 3. Run `verity auth verify` to check token is valid. If it fails: "Verity not configured. Run `/verity-setup` first."
25
19
 
26
20
  ---
27
21
 
@@ -310,60 +310,25 @@ Expected: single-digit findings per file, not hundreds. If you see 50+ issues fr
310
310
 
311
311
  ---
312
312
 
313
- ## Step 6: Verify authentication
314
-
315
- Login now happens in `verity init` (an optional, skippable step), **not** here.
316
- This step only checks whether the user authenticated during init, and branches
317
- the rest of setup accordingly.
318
-
319
- **If the user asks what signing in does or why it matters, tell them:**
320
- - It confirms they have **write access to this repository** — the GitHub token is
321
- used **once** to verify that, then discarded. Verity never stores it.
322
- - It does **not** give Verity access to their code. Code checked by the gate is
323
- analyzed **in memory and discarded** — Verity never stores their code.
324
- - It is **required to store and access run history** for the repo (past results,
325
- trends, and shareable reports).
326
- - Skipping keeps Verity fully **local-only**: the gate still runs and shows
327
- findings, but nothing is uploaded.
313
+ ## Step 6: Register with Verity service
314
+
315
+ Use the `verity` CLI to register. It handles credential storage and service URL automatically.
328
316
 
329
317
  ```bash
330
- verity auth verify
318
+ verity auth register --project "PROJECT_NAME" --remote "GIT_REMOTE_URL"
331
319
  ```
332
320
 
333
- - **Token valid** (prints the project name): The user authenticated during
334
- `verity init`. Continue to Step 7 the Standard, config, and knowledge base
335
- will upload.
336
- - **Not authenticated / no token**: The user skipped login in `verity init` (or
337
- lacks write access). Verity runs in **local-only mode** — the gate still runs on
338
- every stop and surfaces static findings, but nothing uploads and no
339
- history/org/repo data is stored. **Skip Steps 7 and 7b** (they require a token)
340
- and continue to Step 8. Tell the user they can authenticate anytime to unlock
341
- deep review, history, and shareable reports by running:
342
-
343
- ```bash
344
- verity init # re-runs init; offers the auth prompt again
345
- # or, directly:
346
- verity auth register --project "PROJECT_NAME" --remote "GIT_REMOTE_URL"
347
- ```
348
-
349
- Registration is **provider-gated** (GitHub today): the CLI runs a GitHub OAuth
350
- **device flow** ("open https://github.com/login/device and enter code
351
- WXYZ-1234"), proving the user has **write access** to the repo before the
352
- service issues a token.
353
-
354
- When authenticated, `.verity/credentials` contains `token`, `service_url`, and
355
- `provider_token` — all subsequent `verity` upload commands work.
321
+ This command:
322
+ - **New project**: Registers, stores token + service URL in `.verity/credentials`, prints `project_id`. Continue.
323
+ - **Already registered**: Automatically discovers the project, ensures `.verity/credentials` has the service URL. If a token already exists in credentials, it updates the file and succeeds. If no token exists, it asks you to paste one.
324
+ - **Other errors**: Show the error and stop.
325
+
326
+ After this step, `.verity/credentials` will contain both `token` and `service_url` all subsequent `verity` commands will work.
356
327
 
357
328
  ---
358
329
 
359
330
  ## Step 7: Upload Standard and config
360
331
 
361
- > **Skip this entire step if the user is not authenticated** (Step 6 reported
362
- > local-only mode). These commands require a token and will fail without one. The
363
- > `.verity/standard.yaml` and `.codacy/codacy.config.json` you generated locally
364
- > still drive the gate; they'll upload the next time the user authenticates and
365
- > re-runs setup.
366
-
367
332
  ### Upload the Standard
368
333
 
369
334
  The `verity` CLI handles YAML→JSON conversion automatically:
@@ -394,10 +359,6 @@ This derives a small set of descriptive memory nodes from what you already analy
394
359
 
395
360
  ## Step 7b: Enable telemetry (only if the user opted in at Step 3b)
396
361
 
397
- > **Skip this step if the user is not authenticated** (local-only mode) — telemetry
398
- > export requires the token. Note that `/usage` stays empty until they authenticate
399
- > and run `verity telemetry install`.
400
-
401
362
  If — and only if — the user said **Yes** in Step 3b, enable the Claude Code telemetry export
402
363
  now (the token from Step 6 must already exist):
403
364
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.26.0-experimental.d7dfc00",
3
+ "version": "0.26.0",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "homepage": "https://verity.md",
6
6
  "repository": {