@codacy/verity-cli 0.25.0 → 0.26.0-experimental.a2dc844

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,9 +10325,7 @@ var {
10325
10325
  } = import_index.default;
10326
10326
 
10327
10327
  // src/commands/auth.ts
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");
10328
+ var import_node_child_process4 = require("node:child_process");
10331
10329
 
10332
10330
  // src/lib/auth.ts
10333
10331
  var import_promises = require("node:fs/promises");
@@ -10475,7 +10473,11 @@ var SECURITY_PATTERNS = [
10475
10473
  /Dockerfile/
10476
10474
  ];
10477
10475
  var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10478
- var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
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";
10479
10481
 
10480
10482
  // src/lib/auth.ts
10481
10483
  async function resolveToken(flagToken) {
@@ -10641,7 +10643,8 @@ async function apiRequest(options) {
10641
10643
  timeout = 9e4,
10642
10644
  cmd = "unknown",
10643
10645
  retry = false,
10644
- encodeBody = false
10646
+ encodeBody = false,
10647
+ extraHeaders
10645
10648
  } = options;
10646
10649
  const url = `${serviceUrl}${path}`;
10647
10650
  const headers = {
@@ -10650,6 +10653,9 @@ async function apiRequest(options) {
10650
10653
  if (token) {
10651
10654
  headers["Authorization"] = `Bearer ${token}`;
10652
10655
  }
10656
+ if (extraHeaders) {
10657
+ Object.assign(headers, extraHeaders);
10658
+ }
10653
10659
  const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
10654
10660
  if (testMockScenario) {
10655
10661
  headers["X-Verity-Mock-Scenario"] = testMockScenario;
@@ -10740,6 +10746,386 @@ function analyzeRequest(options) {
10740
10746
  });
10741
10747
  }
10742
10748
 
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
+ `,
11104
+ { mode: 384 }
11105
+ );
11106
+ await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
11107
+ });
11108
+ } catch (err) {
11109
+ return {
11110
+ ok: false,
11111
+ 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".`
11112
+ };
11113
+ }
11114
+ try {
11115
+ await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
11116
+ await (0, import_promises3.appendFile)(
11117
+ GLOBAL_CREDENTIALS_FILE,
11118
+ `${opts.remote} token: ${token}
11119
+ `,
11120
+ { mode: 384 }
11121
+ );
11122
+ await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
11123
+ });
11124
+ } catch {
11125
+ }
11126
+ return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email: user?.email } };
11127
+ }
11128
+
10743
11129
  // src/commands/auth.ts
10744
11130
  function registerAuthCommands(program2) {
10745
11131
  const auth = program2.command("auth").description("Manage project authentication");
@@ -10749,36 +11135,26 @@ function registerAuthCommands(program2) {
10749
11135
  let remote = opts.remote;
10750
11136
  if (!remote) {
10751
11137
  try {
10752
- remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
11138
+ remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10753
11139
  } catch {
10754
11140
  printError("No git remote found. Use --remote to specify one.");
10755
11141
  process.exit(1);
10756
11142
  }
10757
11143
  }
10758
- const result = await apiRequest({
10759
- method: "POST",
10760
- path: "/auth/register",
11144
+ const result = await registerProject({
11145
+ projectName: opts.project,
11146
+ remote,
10761
11147
  serviceUrl,
10762
- body: { project_name: opts.project, git_remote_url: remote },
10763
11148
  verbose: globals.verbose
10764
11149
  });
10765
11150
  if (!result.ok) {
10766
11151
  printError(result.error);
10767
11152
  process.exit(1);
10768
11153
  }
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 });
11154
+ const { projectId, serviceUrl: resolvedUrl, email } = result.data;
11155
+ printInfo(`Project registered: ${projectId}`);
11156
+ if (email) printInfo(`Authenticated as: ${email}`);
11157
+ printJson({ project_id: projectId, service_url: resolvedUrl });
10782
11158
  });
10783
11159
  auth.command("verify").description("Verify the current token is valid").action(async () => {
10784
11160
  const globals = program2.opts();
@@ -10811,7 +11187,7 @@ service_url: ${service_url}
10811
11187
  let remote = opts.remote;
10812
11188
  if (!remote) {
10813
11189
  try {
10814
- remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
11190
+ remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10815
11191
  } catch {
10816
11192
  printError("No git remote found. Use --remote to specify one.");
10817
11193
  process.exit(1);
@@ -10838,8 +11214,63 @@ service_url: ${service_url}
10838
11214
  }
10839
11215
 
10840
11216
  // src/lib/hooks.ts
10841
- var import_promises4 = require("node:fs/promises");
10842
- var import_node_path3 = require("node:path");
11217
+ var import_promises5 = require("node:fs/promises");
11218
+ var import_node_path5 = require("node:path");
11219
+
11220
+ // src/lib/json-file.ts
11221
+ var import_promises4 = require("node:fs/promises");
11222
+ var import_node_path4 = require("node:path");
11223
+ function jsonSemanticEqual(a, b) {
11224
+ if (a === b) return true;
11225
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
11226
+ return a === b;
11227
+ }
11228
+ const aIsArr = Array.isArray(a);
11229
+ const bIsArr = Array.isArray(b);
11230
+ if (aIsArr || bIsArr) {
11231
+ if (!aIsArr || !bIsArr || a.length !== b.length) return false;
11232
+ for (let i = 0; i < a.length; i++) {
11233
+ if (!jsonSemanticEqual(a[i], b[i])) return false;
11234
+ }
11235
+ return true;
11236
+ }
11237
+ const ao = a;
11238
+ const bo = b;
11239
+ const aKeys = Object.keys(ao).filter((k) => ao[k] !== void 0);
11240
+ const bKeys = Object.keys(bo).filter((k) => bo[k] !== void 0);
11241
+ if (aKeys.length !== bKeys.length) return false;
11242
+ for (const k of aKeys) {
11243
+ if (bo[k] === void 0) return false;
11244
+ if (!jsonSemanticEqual(ao[k], bo[k])) return false;
11245
+ }
11246
+ return true;
11247
+ }
11248
+ function detectJsonIndent(raw) {
11249
+ const m = raw.match(/\n([ \t]+)\S/);
11250
+ return m ? m[1] : 2;
11251
+ }
11252
+ async function writeJsonFilePreservingStyle(file, value) {
11253
+ let currentRaw = null;
11254
+ try {
11255
+ currentRaw = await (0, import_promises4.readFile)(file, "utf-8");
11256
+ } catch {
11257
+ currentRaw = null;
11258
+ }
11259
+ if (currentRaw !== null) {
11260
+ try {
11261
+ if (jsonSemanticEqual(JSON.parse(currentRaw), value)) return false;
11262
+ } catch {
11263
+ }
11264
+ }
11265
+ const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
11266
+ const next = JSON.stringify(value, null, indent) + "\n";
11267
+ if (next === currentRaw) return false;
11268
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
11269
+ await (0, import_promises4.writeFile)(file, next);
11270
+ return true;
11271
+ }
11272
+
11273
+ // src/lib/hooks.ts
10843
11274
  var VERITY_STOP_HOOK = {
10844
11275
  type: "command",
10845
11276
  command: "verity analyze",
@@ -10917,7 +11348,7 @@ function globalSettingsFile() {
10917
11348
  }
10918
11349
  async function readSettings() {
10919
11350
  try {
10920
- const content = await (0, import_promises4.readFile)(CLAUDE_SETTINGS_FILE, "utf-8");
11351
+ const content = await (0, import_promises5.readFile)(CLAUDE_SETTINGS_FILE, "utf-8");
10921
11352
  return JSON.parse(content);
10922
11353
  } catch {
10923
11354
  return {};
@@ -10928,7 +11359,7 @@ async function readAllSettings() {
10928
11359
  const out = [];
10929
11360
  for (const f of files) {
10930
11361
  try {
10931
- out.push(JSON.parse(await (0, import_promises4.readFile)(f, "utf-8")));
11362
+ out.push(JSON.parse(await (0, import_promises5.readFile)(f, "utf-8")));
10932
11363
  } catch {
10933
11364
  }
10934
11365
  }
@@ -10957,7 +11388,7 @@ async function checkExternalVerityHooks() {
10957
11388
  for (const f of [SETTINGS_LOCAL_FILE, globalSettingsFile()]) {
10958
11389
  let settings;
10959
11390
  try {
10960
- settings = JSON.parse(await (0, import_promises4.readFile)(f, "utf-8"));
11391
+ settings = JSON.parse(await (0, import_promises5.readFile)(f, "utf-8"));
10961
11392
  } catch {
10962
11393
  continue;
10963
11394
  }
@@ -10991,20 +11422,17 @@ async function checkAllVerityHooksDetailed() {
10991
11422
  return { stop, intent, baseline, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
10992
11423
  }
10993
11424
  async function writeSettings(settings) {
10994
- await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
10995
- await (0, import_promises4.writeFile)(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
11425
+ await writeJsonFilePreservingStyle(CLAUDE_SETTINGS_FILE, settings);
10996
11426
  }
10997
11427
  async function readSettingsAt(root) {
10998
11428
  try {
10999
- return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11429
+ return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11000
11430
  } catch {
11001
11431
  return {};
11002
11432
  }
11003
11433
  }
11004
11434
  async function writeSettingsAt(root, settings) {
11005
- const file = (0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE);
11006
- await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(file), { recursive: true });
11007
- await (0, import_promises4.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
11435
+ await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
11008
11436
  }
11009
11437
  async function hasLegacyHooksAt(root) {
11010
11438
  const settings = await readSettingsAt(root);
@@ -11243,9 +11671,9 @@ function registerHooksCommands(program2) {
11243
11671
  var import_node_crypto3 = require("node:crypto");
11244
11672
 
11245
11673
  // src/lib/conversation-buffer.ts
11246
- var import_promises5 = require("node:fs/promises");
11247
- var import_node_fs2 = require("node:fs");
11248
- var import_node_child_process4 = require("node:child_process");
11674
+ var import_promises6 = require("node:fs/promises");
11675
+ var import_node_fs3 = require("node:fs");
11676
+ var import_node_child_process5 = require("node:child_process");
11249
11677
  var import_node_crypto = require("node:crypto");
11250
11678
  function stripImageReferences(text) {
11251
11679
  return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
@@ -11255,7 +11683,7 @@ function bufferTmpPath() {
11255
11683
  }
11256
11684
  async function appendToConversationBuffer(prompt, sessionId) {
11257
11685
  try {
11258
- await (0, import_promises5.mkdir)(VERITY_DIR, { recursive: true });
11686
+ await (0, import_promises6.mkdir)(VERITY_DIR, { recursive: true });
11259
11687
  let sanitized = prompt.length > MAX_INTENT_CHARS ? prompt.slice(0, MAX_INTENT_CHARS) : prompt;
11260
11688
  sanitized = stripImageReferences(sanitized);
11261
11689
  const entry = {
@@ -11273,14 +11701,14 @@ async function appendToConversationBuffer(prompt, sessionId) {
11273
11701
  const capped = recent.slice(-CONVERSATION_MAX_ENTRIES);
11274
11702
  const content = capped.map((e) => JSON.stringify(e)).join("\n") + "\n";
11275
11703
  const tmpFile = bufferTmpPath();
11276
- await (0, import_promises5.writeFile)(tmpFile, content);
11277
- await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11704
+ await (0, import_promises6.writeFile)(tmpFile, content);
11705
+ await (0, import_promises6.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11278
11706
  } catch {
11279
11707
  }
11280
11708
  }
11281
11709
  async function readAndClearConversationBuffer(currentSessionId) {
11282
11710
  try {
11283
- if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
11711
+ if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
11284
11712
  const entries = await readBufferEntries();
11285
11713
  let mine = entries;
11286
11714
  let others = [];
@@ -11291,10 +11719,10 @@ async function readAndClearConversationBuffer(currentSessionId) {
11291
11719
  if (others.length > 0) {
11292
11720
  const remaining = others.map((e) => JSON.stringify(e)).join("\n") + "\n";
11293
11721
  const tmpFile = bufferTmpPath();
11294
- await (0, import_promises5.writeFile)(tmpFile, remaining);
11295
- await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11722
+ await (0, import_promises6.writeFile)(tmpFile, remaining);
11723
+ await (0, import_promises6.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11296
11724
  } else {
11297
- await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11725
+ await (0, import_promises6.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11298
11726
  });
11299
11727
  }
11300
11728
  if (mine.length > 0) {
@@ -11304,10 +11732,10 @@ async function readAndClearConversationBuffer(currentSessionId) {
11304
11732
  };
11305
11733
  }
11306
11734
  }
11307
- if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
11735
+ if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
11308
11736
  try {
11309
- const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
11310
- await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
11737
+ const content = await (0, import_promises6.readFile)(INTENT_FILE, "utf-8");
11738
+ await (0, import_promises6.unlink)(INTENT_FILE).catch(() => {
11311
11739
  });
11312
11740
  const data = JSON.parse(content);
11313
11741
  if (data.prompt) {
@@ -11330,7 +11758,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
11330
11758
  }
11331
11759
  async function readBufferEntries() {
11332
11760
  try {
11333
- const content = await (0, import_promises5.readFile)(CONVERSATION_BUFFER_FILE, "utf-8");
11761
+ const content = await (0, import_promises6.readFile)(CONVERSATION_BUFFER_FILE, "utf-8");
11334
11762
  const entries = [];
11335
11763
  for (const line of content.split("\n")) {
11336
11764
  const trimmed = line.trim();
@@ -11348,7 +11776,7 @@ async function readBufferEntries() {
11348
11776
  }
11349
11777
  function getRecentCommitMessages() {
11350
11778
  try {
11351
- const output = (0, import_node_child_process4.execSync)(
11779
+ const output = (0, import_node_child_process5.execSync)(
11352
11780
  'git log --since="30 minutes ago" --format="%s" -5',
11353
11781
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
11354
11782
  ).trim();
@@ -11360,9 +11788,9 @@ function getRecentCommitMessages() {
11360
11788
  }
11361
11789
 
11362
11790
  // src/lib/task-context-buffer.ts
11363
- var import_promises6 = require("node:fs/promises");
11364
- var import_node_fs3 = require("node:fs");
11365
- var import_node_path4 = require("node:path");
11791
+ var import_promises7 = require("node:fs/promises");
11792
+ var import_node_fs4 = require("node:fs");
11793
+ var import_node_path6 = require("node:path");
11366
11794
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
11367
11795
  var MAX_BUFFER_BYTES = 500 * 1024;
11368
11796
  var MAX_PROMPT_CHARS = 2e3;
@@ -11401,9 +11829,9 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
11401
11829
  }
11402
11830
  async function readTaskContextBuffer(taskId) {
11403
11831
  const filePath = bufferPath(taskId);
11404
- if (!(0, import_node_fs3.existsSync)(filePath)) return null;
11832
+ if (!(0, import_node_fs4.existsSync)(filePath)) return null;
11405
11833
  try {
11406
- const content = await (0, import_promises6.readFile)(filePath, "utf-8");
11834
+ const content = await (0, import_promises7.readFile)(filePath, "utf-8");
11407
11835
  if (!content.trim()) return null;
11408
11836
  const lines = content.split("\n").filter((l) => l.trim());
11409
11837
  const formatted = [];
@@ -11435,16 +11863,16 @@ async function readTaskContextBuffer(taskId) {
11435
11863
  }
11436
11864
  async function cleanupTaskContextBuffers() {
11437
11865
  try {
11438
- if (!(0, import_node_fs3.existsSync)(TASK_CONTEXT_DIR)) return;
11439
- const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
11866
+ if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
11867
+ const files = await (0, import_promises7.readdir)(TASK_CONTEXT_DIR);
11440
11868
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
11441
11869
  for (const file of files) {
11442
11870
  if (!file.endsWith(".jsonl")) continue;
11443
- const filePath = (0, import_node_path4.join)(TASK_CONTEXT_DIR, file);
11871
+ const filePath = (0, import_node_path6.join)(TASK_CONTEXT_DIR, file);
11444
11872
  try {
11445
- const stats = await (0, import_promises6.stat)(filePath);
11873
+ const stats = await (0, import_promises7.stat)(filePath);
11446
11874
  if (stats.mtimeMs < cutoffMs) {
11447
- await (0, import_promises6.unlink)(filePath);
11875
+ await (0, import_promises7.unlink)(filePath);
11448
11876
  }
11449
11877
  } catch {
11450
11878
  }
@@ -11454,33 +11882,33 @@ async function cleanupTaskContextBuffers() {
11454
11882
  }
11455
11883
  function bufferPath(taskId) {
11456
11884
  const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
11457
- return (0, import_node_path4.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11885
+ return (0, import_node_path6.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11458
11886
  }
11459
11887
  async function appendEntry(taskId, entry) {
11460
11888
  try {
11461
- await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
11889
+ await (0, import_promises7.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
11462
11890
  const filePath = bufferPath(taskId);
11463
- if ((0, import_node_fs3.existsSync)(filePath)) {
11464
- const stats = await (0, import_promises6.stat)(filePath);
11891
+ if ((0, import_node_fs4.existsSync)(filePath)) {
11892
+ const stats = await (0, import_promises7.stat)(filePath);
11465
11893
  if (stats.size >= MAX_BUFFER_BYTES) {
11466
- const content = await (0, import_promises6.readFile)(filePath, "utf-8");
11894
+ const content = await (0, import_promises7.readFile)(filePath, "utf-8");
11467
11895
  const lines = content.split("\n").filter((l) => l.trim());
11468
11896
  const keepFrom = Math.floor(lines.length * 0.25);
11469
11897
  const pruned = lines.slice(keepFrom).join("\n") + "\n";
11470
- await (0, import_promises6.writeFile)(filePath, pruned);
11898
+ await (0, import_promises7.writeFile)(filePath, pruned);
11471
11899
  }
11472
11900
  }
11473
11901
  const line = JSON.stringify(entry) + "\n";
11474
- const existing = (0, import_node_fs3.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
11475
- await (0, import_promises6.writeFile)(filePath, existing + line);
11902
+ const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
11903
+ await (0, import_promises7.writeFile)(filePath, existing + line);
11476
11904
  } catch {
11477
11905
  }
11478
11906
  }
11479
11907
 
11480
11908
  // src/lib/memory-retrieval.ts
11481
- var import_promises7 = require("node:fs/promises");
11482
- var import_node_fs4 = require("node:fs");
11483
- var import_node_path5 = require("node:path");
11909
+ var import_promises8 = require("node:fs/promises");
11910
+ var import_node_fs5 = require("node:fs");
11911
+ var import_node_path7 = require("node:path");
11484
11912
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
11485
11913
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
11486
11914
  var DEFAULT_BUDGET_TOKENS = 2e3;
@@ -11573,19 +12001,19 @@ function parseFrontmatter(content) {
11573
12001
  return { fm, body: match[2].trim() };
11574
12002
  }
11575
12003
  async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
11576
- if (!(0, import_node_fs4.existsSync)(memoryDir())) return null;
12004
+ if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
11577
12005
  const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
11578
12006
  const promptTokens = tokenize(promptText);
11579
12007
  const nodes = [];
11580
12008
  for (const domain of DOMAINS) {
11581
- const domainDir = (0, import_node_path5.join)(memoryDir(), domain);
11582
- if (!(0, import_node_fs4.existsSync)(domainDir)) continue;
12009
+ const domainDir = (0, import_node_path7.join)(memoryDir(), domain);
12010
+ if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
11583
12011
  try {
11584
- const files = await (0, import_promises7.readdir)(domainDir);
12012
+ const files = await (0, import_promises8.readdir)(domainDir);
11585
12013
  for (const file of files) {
11586
12014
  if (!file.endsWith(".md")) continue;
11587
12015
  try {
11588
- const content = await (0, import_promises7.readFile)((0, import_node_path5.join)(domainDir, file), "utf-8");
12016
+ const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
11589
12017
  const { fm, body } = parseFrontmatter(content);
11590
12018
  if (fm.status && fm.status !== "active") continue;
11591
12019
  nodes.push({
@@ -11643,9 +12071,9 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
11643
12071
  }
11644
12072
 
11645
12073
  // src/lib/memory-sync.ts
11646
- var import_promises8 = require("node:fs/promises");
11647
- var import_node_fs5 = require("node:fs");
11648
- var import_node_path6 = require("node:path");
12074
+ var import_promises9 = require("node:fs/promises");
12075
+ var import_node_fs6 = require("node:fs");
12076
+ var import_node_path8 = require("node:path");
11649
12077
  var import_node_crypto2 = require("node:crypto");
11650
12078
 
11651
12079
  // src/lib/glob-match.ts
@@ -11714,36 +12142,36 @@ var memoryDir2 = () => projectPath(`${VERITY_DIR}/memory`);
11714
12142
  var DOMAINS2 = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations", "_archive"];
11715
12143
  var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
11716
12144
  async function ensureMemoryDir() {
11717
- await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
12145
+ await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
11718
12146
  for (const domain of DOMAINS2) {
11719
- await (0, import_promises8.mkdir)((0, import_node_path6.join)(memoryDir2(), domain), { recursive: true });
12147
+ await (0, import_promises9.mkdir)((0, import_node_path8.join)(memoryDir2(), domain), { recursive: true });
11720
12148
  }
11721
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"))) {
11722
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12149
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"))) {
12150
+ await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
11723
12151
  }
11724
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "index.md"))) {
11725
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
12152
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "index.md"))) {
12153
+ 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");
11726
12154
  }
11727
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "log.md"))) {
11728
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12155
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md"))) {
12156
+ await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
11729
12157
  }
11730
12158
  }
11731
12159
  async function buildManifest() {
11732
- if (!(0, import_node_fs5.existsSync)(memoryDir2())) {
12160
+ if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
11733
12161
  return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
11734
12162
  }
11735
12163
  const nodes = [];
11736
12164
  for (const domain of DOMAINS2) {
11737
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11738
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12165
+ const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12166
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11739
12167
  try {
11740
- const files = await (0, import_promises8.readdir)(domainDir);
12168
+ const files = await (0, import_promises9.readdir)(domainDir);
11741
12169
  for (const file of files) {
11742
12170
  if (!file.endsWith(".md")) continue;
11743
12171
  const filePath = `${domain}/${file}`;
11744
- const fullPath = (0, import_node_path6.join)(memoryDir2(), filePath);
12172
+ const fullPath = (0, import_node_path8.join)(memoryDir2(), filePath);
11745
12173
  try {
11746
- const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
12174
+ const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
11747
12175
  const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
11748
12176
  nodes.push({ path: filePath, content_hash: `sha256:${hash}` });
11749
12177
  } catch {
@@ -11754,13 +12182,13 @@ async function buildManifest() {
11754
12182
  }
11755
12183
  let indexHash = null;
11756
12184
  try {
11757
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "index.md"), "utf-8");
12185
+ const indexContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "utf-8");
11758
12186
  indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
11759
12187
  } catch {
11760
12188
  }
11761
12189
  let logLength = 0;
11762
12190
  try {
11763
- const logContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "utf-8");
12191
+ const logContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8");
11764
12192
  logLength = logContent.split("\n").length;
11765
12193
  } catch {
11766
12194
  }
@@ -11771,15 +12199,15 @@ function hashContent(content) {
11771
12199
  }
11772
12200
  async function readOnDiskNodes() {
11773
12201
  const out = /* @__PURE__ */ new Map();
11774
- if (!(0, import_node_fs5.existsSync)(memoryDir2())) return out;
12202
+ if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
11775
12203
  for (const domain of DOMAINS2) {
11776
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11777
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12204
+ const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12205
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11778
12206
  try {
11779
- for (const file of await (0, import_promises8.readdir)(domainDir)) {
12207
+ for (const file of await (0, import_promises9.readdir)(domainDir)) {
11780
12208
  if (!file.endsWith(".md")) continue;
11781
12209
  try {
11782
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8")));
12210
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8")));
11783
12211
  } catch {
11784
12212
  }
11785
12213
  }
@@ -11791,7 +12219,7 @@ async function readOnDiskNodes() {
11791
12219
  async function readSyncBaseline() {
11792
12220
  const out = /* @__PURE__ */ new Map();
11793
12221
  try {
11794
- const parsed = JSON.parse(await (0, import_promises8.readFile)(syncStateFile(), "utf-8"));
12222
+ const parsed = JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
11795
12223
  if (Array.isArray(parsed?.nodes)) {
11796
12224
  for (const n of parsed.nodes) if (n?.path) out.set(n.path, n.hash ?? null);
11797
12225
  } else if (Array.isArray(parsed?.paths)) {
@@ -11807,12 +12235,12 @@ async function recordSyncedNodePaths() {
11807
12235
  const next = JSON.stringify({ schema: 2, nodes }) + "\n";
11808
12236
  let existing = "";
11809
12237
  try {
11810
- existing = await (0, import_promises8.readFile)(syncStateFile(), "utf-8");
12238
+ existing = await (0, import_promises9.readFile)(syncStateFile(), "utf-8");
11811
12239
  } catch {
11812
12240
  }
11813
12241
  if (existing === next) return;
11814
- await (0, import_promises8.mkdir)(projectPath(VERITY_DIR), { recursive: true });
11815
- await (0, import_promises8.writeFile)(syncStateFile(), next);
12242
+ await (0, import_promises9.mkdir)(projectPath(VERITY_DIR), { recursive: true });
12243
+ await (0, import_promises9.writeFile)(syncStateFile(), next);
11816
12244
  } catch {
11817
12245
  }
11818
12246
  }
@@ -11825,11 +12253,11 @@ async function computeEditedNodeUploads() {
11825
12253
  const uploads = [];
11826
12254
  for (const [path, prevHash] of prev) {
11827
12255
  if (prevHash == null) continue;
11828
- const full = (0, import_node_path6.join)(memoryDir2(), path);
11829
- if (!(0, import_node_fs5.existsSync)(full)) continue;
12256
+ const full = (0, import_node_path8.join)(memoryDir2(), path);
12257
+ if (!(0, import_node_fs6.existsSync)(full)) continue;
11830
12258
  let content;
11831
12259
  try {
11832
- content = await (0, import_promises8.readFile)(full, "utf-8");
12260
+ content = await (0, import_promises9.readFile)(full, "utf-8");
11833
12261
  } catch {
11834
12262
  continue;
11835
12263
  }
@@ -11862,15 +12290,15 @@ async function applyMemoryWrites(writes, opts = {}) {
11862
12290
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
11863
12291
  for (const n of notes) logLines.push(` - ${n}`);
11864
12292
  try {
11865
- const existing = (0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
11866
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
12293
+ 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";
12294
+ await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
11867
12295
  } catch {
11868
12296
  }
11869
12297
  await recordSyncedNodePaths();
11870
12298
  return count;
11871
12299
  }
11872
12300
  async function applyOneWrite(write, treePaths) {
11873
- const fullPath = (0, import_node_path6.join)(memoryDir2(), write.path);
12301
+ const fullPath = (0, import_node_path8.join)(memoryDir2(), write.path);
11874
12302
  const notes = [];
11875
12303
  let content = write.content;
11876
12304
  if (treePaths && treePaths.length > 0) {
@@ -11880,10 +12308,10 @@ async function applyOneWrite(write, treePaths) {
11880
12308
  notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
11881
12309
  }
11882
12310
  }
11883
- if ((0, import_node_fs5.existsSync)(fullPath)) {
12311
+ if ((0, import_node_fs6.existsSync)(fullPath)) {
11884
12312
  let existing = "";
11885
12313
  try {
11886
- existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
12314
+ existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
11887
12315
  } catch {
11888
12316
  }
11889
12317
  if (existing === content) return { written: false, notes };
@@ -11892,8 +12320,8 @@ async function applyOneWrite(write, treePaths) {
11892
12320
  return { written: false, notes };
11893
12321
  }
11894
12322
  }
11895
- await (0, import_promises8.mkdir)((0, import_node_path6.dirname)(fullPath), { recursive: true });
11896
- await (0, import_promises8.writeFile)(fullPath, content);
12323
+ await (0, import_promises9.mkdir)((0, import_node_path8.dirname)(fullPath), { recursive: true });
12324
+ await (0, import_promises9.writeFile)(fullPath, content);
11897
12325
  return { written: true, notes };
11898
12326
  }
11899
12327
  function groundFileGlobs(content, treePaths) {
@@ -11933,10 +12361,10 @@ async function regenerateIndex() {
11933
12361
  ];
11934
12362
  let totalNodes = 0;
11935
12363
  for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
11936
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11937
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12364
+ const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12365
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11938
12366
  try {
11939
- const files = await (0, import_promises8.readdir)(domainDir);
12367
+ const files = await (0, import_promises9.readdir)(domainDir);
11940
12368
  const mdFiles = files.filter((f) => f.endsWith(".md"));
11941
12369
  if (mdFiles.length === 0) continue;
11942
12370
  lines.push(`## ${domain}/ (${mdFiles.length})`);
@@ -11944,7 +12372,7 @@ async function regenerateIndex() {
11944
12372
  for (const file of mdFiles.sort()) {
11945
12373
  const slug = file.replace(/\.md$/, "");
11946
12374
  try {
11947
- const content = await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
12375
+ const content = await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
11948
12376
  const title = pickFrontmatter(content, "title") ?? slug;
11949
12377
  const kind = pickFrontmatter(content, "kind") ?? "-";
11950
12378
  const confidence = pickFrontmatter(content, "confidence");
@@ -11968,14 +12396,14 @@ async function regenerateIndex() {
11968
12396
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
11969
12397
  }
11970
12398
  const next = lines.join("\n") + "\n";
11971
- const indexPath = (0, import_node_path6.join)(memoryDir2(), "index.md");
12399
+ const indexPath = (0, import_node_path8.join)(memoryDir2(), "index.md");
11972
12400
  let existing = null;
11973
12401
  try {
11974
- existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
12402
+ existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
11975
12403
  } catch {
11976
12404
  }
11977
12405
  if (existing === next) return;
11978
- await (0, import_promises8.writeFile)(indexPath, next);
12406
+ await (0, import_promises9.writeFile)(indexPath, next);
11979
12407
  }
11980
12408
  function pickFrontmatter(content, key) {
11981
12409
  const re = new RegExp(`^${key}:\\s*"?([^"\\n]+?)"?\\s*$`, "m");
@@ -12050,10 +12478,10 @@ function hasLegacyMemoryBlock(text) {
12050
12478
  return findMarker(text, LEGACY_MD_START) !== -1;
12051
12479
  }
12052
12480
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
12053
- const claudeMdPath = (0, import_node_path6.join)(cwd, "CLAUDE.md");
12481
+ const claudeMdPath = (0, import_node_path8.join)(cwd, "CLAUDE.md");
12054
12482
  let existing = "";
12055
- if ((0, import_node_fs5.existsSync)(claudeMdPath)) {
12056
- existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
12483
+ if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
12484
+ existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
12057
12485
  }
12058
12486
  let startTag = CLAUDE_MD_START;
12059
12487
  let endTag = CLAUDE_MD_END;
@@ -12109,7 +12537,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
12109
12537
  next = existing.replace(/\n*$/, "") + "\n\n" + block + "\n";
12110
12538
  }
12111
12539
  if (next === existing) return;
12112
- await (0, import_promises8.writeFile)(claudeMdPath, next);
12540
+ await (0, import_promises9.writeFile)(claudeMdPath, next);
12113
12541
  }
12114
12542
  function extractPreserveContent(interior) {
12115
12543
  for (const [start, end] of [
@@ -12182,7 +12610,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
12182
12610
  `;
12183
12611
 
12184
12612
  // src/commands/intent.ts
12185
- var import_node_fs6 = require("node:fs");
12613
+ var import_node_fs7 = require("node:fs");
12186
12614
  function registerIntentCommands(program2) {
12187
12615
  const intent = program2.command("intent").description("Manage intent capture");
12188
12616
  intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
@@ -12191,7 +12619,7 @@ function registerIntentCommands(program2) {
12191
12619
  process.chdir(repoRoot());
12192
12620
  } catch {
12193
12621
  }
12194
- if (!(0, import_node_fs6.existsSync)(VERITY_DIR)) {
12622
+ if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
12195
12623
  process.exit(0);
12196
12624
  }
12197
12625
  const chunks = [];
@@ -12292,7 +12720,7 @@ async function fireClassify(prompt, sessionId) {
12292
12720
  }
12293
12721
 
12294
12722
  // src/commands/standard.ts
12295
- var import_promises9 = require("node:fs/promises");
12723
+ var import_promises10 = require("node:fs/promises");
12296
12724
  var import_yaml = __toESM(require_dist());
12297
12725
  function registerStandardCommands(program2) {
12298
12726
  const standard = program2.command("standard").description("Manage the project Standard");
@@ -12310,7 +12738,7 @@ function registerStandardCommands(program2) {
12310
12738
  }
12311
12739
  let yamlContent;
12312
12740
  try {
12313
- yamlContent = await (0, import_promises9.readFile)(opts.file, "utf-8");
12741
+ yamlContent = await (0, import_promises10.readFile)(opts.file, "utf-8");
12314
12742
  } catch {
12315
12743
  printError(`Cannot read ${opts.file}`);
12316
12744
  process.exit(1);
@@ -12401,7 +12829,7 @@ function registerStandardCommands(program2) {
12401
12829
  }
12402
12830
 
12403
12831
  // src/commands/config.ts
12404
- var import_promises10 = require("node:fs/promises");
12832
+ var import_promises11 = require("node:fs/promises");
12405
12833
  function registerConfigCommands(program2) {
12406
12834
  const config = program2.command("config").description("Manage analysis configuration");
12407
12835
  config.command("push").description("Upload the analysis config to the service").option("--file <path>", "Path to config file", CODACY_CONFIG_FILE).action(async (opts) => {
@@ -12418,7 +12846,7 @@ function registerConfigCommands(program2) {
12418
12846
  }
12419
12847
  let content;
12420
12848
  try {
12421
- const raw = await (0, import_promises10.readFile)(opts.file, "utf-8");
12849
+ const raw = await (0, import_promises11.readFile)(opts.file, "utf-8");
12422
12850
  content = JSON.parse(raw);
12423
12851
  } catch {
12424
12852
  printError(`Cannot read or parse ${opts.file}`);
@@ -12618,370 +13046,161 @@ function registerStatusCommand(program2) {
12618
13046
  if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
12619
13047
  printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
12620
13048
  if (mem.recent_runs) {
12621
- const r = mem.recent_runs;
12622
- printInfo("");
12623
- printInfo("--- Last Run ---");
12624
- printInfo(`Decision: ${r.last_gate_decision ?? "none"}`);
12625
- printInfo(`Quality: ${r.last_quality_score ?? "-"}/10`);
12626
- printInfo(`Security: ${r.last_security_score ?? "-"}/10`);
12627
- printInfo(`Trend: ${r.trend}`);
12628
- printInfo(`Runs: ${r.count} recorded`);
12629
- }
12630
- if (mem.pending_items && mem.pending_items.length > 0) {
12631
- printInfo("");
12632
- printInfo("--- Pending Items ---");
12633
- for (const item of mem.pending_items) {
12634
- printInfo(` [${item.priority.toUpperCase()}] ${item.description}`);
12635
- }
12636
- }
12637
- const recentTasks = mem.recent_tasks;
12638
- const currentTask = mem.current_task;
12639
- const contextFiles = mem.context_files;
12640
- if (recentTasks && recentTasks.length > 0) {
12641
- printInfo("");
12642
- printInfo("--- Active Tasks ---");
12643
- for (const task of recentTasks) {
12644
- const isCurrent = currentTask && task.id === currentTask.id;
12645
- const marker = isCurrent ? "\u25CF" : "\u25CB";
12646
- const runCount = task.run_count ?? 0;
12647
- const lastAt = task.last_run_at ? timeAgo(task.last_run_at) : "no runs";
12648
- printInfo(` ${marker} ${task.label} ${runCount} run${runCount === 1 ? "" : "s"}, ${lastAt}`);
12649
- }
12650
- if (currentTask) {
12651
- printInfo("");
12652
- printInfo(`Current task: ${currentTask.label}`);
12653
- if (contextFiles && contextFiles.length > 0) {
12654
- printInfo(` Files in task: ${contextFiles.length} (${contextFiles.slice(0, 3).join(", ")}${contextFiles.length > 3 ? "..." : ""})`);
12655
- }
12656
- }
12657
- }
12658
- if (opts.history) {
12659
- const runsResult = await apiRequest({
12660
- method: "GET",
12661
- path: `/runs?limit=${opts.limit}`,
12662
- serviceUrl,
12663
- token,
12664
- verbose: globals.verbose
12665
- });
12666
- if (runsResult.ok && runsResult.data.runs.length > 0) {
12667
- printInfo("");
12668
- printInfo("--- Recent Runs ---");
12669
- printInfo(`${"Run ID".padEnd(32)} ${"Decision".padEnd(10)}${"Q".padEnd(4)}${"S".padEnd(4)}${"Findings".padEnd(32)}Date`);
12670
- for (const run of runsResult.data.runs) {
12671
- const q = run.quality_score != null ? `${run.quality_score}` : "-";
12672
- const s = run.security_score != null ? `${run.security_score}` : "-";
12673
- const findings = formatFindingsSummary(run.findings_count);
12674
- const date = run.created_at.slice(0, 19).replace("T", " ");
12675
- printInfo(`${run.run_id.padEnd(32)} ${run.gate_decision.padEnd(10)}${q.padEnd(4)}${s.padEnd(4)}${findings.padEnd(32)}${date}`);
12676
- }
12677
- }
12678
- }
12679
- });
12680
- }
12681
-
12682
- // src/commands/feedback.ts
12683
- function registerFeedbackCommand(program2) {
12684
- const feedbackCmd = program2.command("feedback").description("Send feedback to the Verity team");
12685
- feedbackCmd.command("message <text>").description("Send general feedback").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
12686
- const globals = program2.opts();
12687
- await sendGeneralFeedback(message, opts, globals);
12688
- });
12689
- feedbackCmd.command("finding <run-id> <pattern-id> <action> [note]").description("Submit per-finding feedback (false_positive, acknowledged, useful, etc.)").option("--file <path>", "File path the finding applies to").option("--line <n>", "Line number", parseInt).action(async (runId, patternId, action, note, opts) => {
12690
- const globals = program2.opts();
12691
- const validActions = ["false_positive", "acknowledged", "will_fix_later", "wrong_severity", "useful"];
12692
- if (!validActions.includes(action)) {
12693
- printError(`Invalid action "${action}". Must be one of: ${validActions.join(", ")}`);
12694
- process.exit(1);
12695
- }
12696
- const tokenResult = await resolveToken(globals.token);
12697
- if (!tokenResult.ok) {
12698
- printError(tokenResult.error);
12699
- process.exit(1);
12700
- }
12701
- const urlResult = await resolveServiceUrl(globals.serviceUrl);
12702
- if (!urlResult.ok) {
12703
- printError(urlResult.error);
12704
- process.exit(1);
12705
- }
12706
- const body = {
12707
- run_id: runId,
12708
- pattern_id: patternId,
12709
- action
12710
- };
12711
- if (note) body.note = note;
12712
- if (opts.file) body.file_path = opts.file;
12713
- if (opts.line != null) body.line = opts.line;
12714
- const result = await apiRequest({
12715
- method: "POST",
12716
- path: "/feedback/findings",
12717
- serviceUrl: urlResult.data,
12718
- token: tokenResult.data.token,
12719
- body,
12720
- verbose: globals.verbose
12721
- });
12722
- if (!result.ok) {
12723
- printError(`Couldn't submit finding feedback: ${result.error}`);
12724
- process.exit(1);
12725
- }
12726
- const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
12727
- printInfo(status);
12728
- });
12729
- feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
12730
- if (!message) return;
12731
- const globals = program2.opts();
12732
- await sendGeneralFeedback(message, opts, globals);
12733
- });
12734
- }
12735
- async function sendGeneralFeedback(message, opts, globals) {
12736
- const tokenResult = await resolveToken(globals.token);
12737
- if (!tokenResult.ok) {
12738
- printError(tokenResult.error);
12739
- printInfo(`Your message: ${message}`);
12740
- process.exit(1);
12741
- }
12742
- const urlResult = await resolveServiceUrl(globals.serviceUrl);
12743
- if (!urlResult.ok) {
12744
- printError(urlResult.error);
12745
- printInfo(`Your message: ${message}`);
12746
- process.exit(1);
12747
- }
12748
- const body = { message };
12749
- const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
12750
- const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
12751
- if (sessionId) body.session_id = sessionId;
12752
- if (model) body.agent_model = model;
12753
- const result = await apiRequest({
12754
- method: "POST",
12755
- path: "/feedback",
12756
- serviceUrl: urlResult.data,
12757
- token: tokenResult.data.token,
12758
- body,
12759
- verbose: globals.verbose
12760
- });
12761
- if (!result.ok) {
12762
- printError(`Couldn't send feedback: ${result.error}`);
12763
- printInfo(`Your message: ${message}`);
12764
- process.exit(1);
12765
- }
12766
- printInfo("Thanks, feedback sent!");
12767
- }
12768
-
12769
- // src/commands/analyze.ts
12770
- var import_node_fs19 = require("node:fs");
12771
- var import_node_path14 = require("node:path");
12772
-
12773
- // src/lib/git.ts
12774
- var import_node_child_process5 = require("node:child_process");
12775
- var import_node_fs7 = require("node:fs");
12776
- var import_node_path7 = require("node:path");
12777
- function resolveFile(relpath) {
12778
- if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
12779
- if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
12780
- try {
12781
- const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
12782
- for (const entry of entries) {
12783
- if (!entry.isDirectory()) continue;
12784
- const candidate = (0, import_node_path7.join)(".claude/worktrees", entry.name, relpath);
12785
- if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
12786
- }
12787
- } catch {
12788
- }
12789
- }
12790
- return null;
12791
- }
12792
- function execGit(cmd) {
12793
- try {
12794
- return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
12795
- } catch {
12796
- return "";
12797
- }
12798
- }
12799
- function splitLines(s) {
12800
- return s.split("\n").filter((l) => l.length > 0);
12801
- }
12802
- var SHA_RE = /^[0-9a-f]{40}$/;
12803
- function readBaselineSha() {
12804
- if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
12805
- let sha;
12806
- try {
12807
- sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
12808
- } catch {
12809
- return null;
12810
- }
12811
- if (!SHA_RE.test(sha)) return null;
12812
- const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
12813
- if (!reachable) {
12814
- try {
12815
- (0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
12816
- } catch {
12817
- }
12818
- return null;
12819
- }
12820
- return sha;
12821
- }
12822
- function writeBaselineSha(sha) {
12823
- if (!SHA_RE.test(sha)) return;
12824
- try {
12825
- (0, import_node_fs7.mkdirSync)((0, import_node_path7.dirname)(BASELINE_SHA_FILE), { recursive: true });
12826
- (0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
12827
- } catch {
12828
- }
12829
- }
12830
- function getChangedFiles() {
12831
- const sets = /* @__PURE__ */ new Set();
12832
- let hasRecentCommitFiles = false;
12833
- for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
12834
- for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
12835
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
12836
- const baseline = readBaselineSha();
12837
- if (baseline) {
12838
- const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
12839
- if (committed.length > 0) {
12840
- hasRecentCommitFiles = true;
12841
- for (const f of committed) sets.add(f);
12842
- }
12843
- } else {
12844
- const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
12845
- const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
12846
- const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
12847
- const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
12848
- if (commitAge < 120 && !hasUnstaged && !hasStaged) {
12849
- const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
12850
- if (recentFiles.length > 0) {
12851
- hasRecentCommitFiles = true;
12852
- for (const f of recentFiles) sets.add(f);
12853
- }
12854
- }
12855
- }
12856
- for (const f of getWorktreeFiles()) sets.add(f);
12857
- const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12858
- return { files: filtered, hasRecentCommitFiles };
12859
- }
12860
- function getStagedFiles() {
12861
- return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12862
- }
12863
- function getDirtyFiles() {
12864
- const set = /* @__PURE__ */ new Set();
12865
- for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
12866
- for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
12867
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
12868
- return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12869
- }
12870
- function showContentAtRef(ref, repoRelPath) {
12871
- if (!ref || ref === "no-git") return null;
12872
- const normalizedPath = repoRelPath.replace(/\\/g, "/");
12873
- try {
12874
- return (0, import_node_child_process5.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
12875
- encoding: "utf-8",
12876
- maxBuffer: 64 * 1024 * 1024,
12877
- stdio: ["pipe", "pipe", "pipe"]
12878
- });
12879
- } catch {
12880
- return null;
12881
- }
12882
- }
12883
- function getPushRangeFiles() {
12884
- const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12885
- const resolvers = [
12886
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
12887
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
12888
- () => {
12889
- const branch = execGit("git rev-parse --abbrev-ref HEAD");
12890
- return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
12891
- }
12892
- ];
12893
- for (const resolve of resolvers) {
12894
- const range = resolve();
12895
- if (range) return { files: diff(range), range };
12896
- }
12897
- const baseline = readBaselineSha();
12898
- if (baseline) {
12899
- const files = diff(`${baseline}..HEAD`);
12900
- if (files.length > 0) return { files, range: `${baseline}..HEAD` };
12901
- }
12902
- const last = diff("HEAD~1..HEAD");
12903
- return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
12904
- }
12905
- function getPushRangeMessages() {
12906
- const { range } = getPushRangeFiles();
12907
- if (!range) return "";
12908
- return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
12909
- }
12910
- function getWorktreeFiles() {
12911
- const result = [];
12912
- const worktreeDir = ".claude/worktrees";
12913
- if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
12914
- try {
12915
- const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
12916
- const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
12917
- for (const entry of entries) {
12918
- if (!entry.isDirectory()) continue;
12919
- const wtDir = (0, import_node_path7.join)(worktreeDir, entry.name);
12920
- scanDir(wtDir, wtDir, fiveMinAgo, result);
13049
+ const r = mem.recent_runs;
13050
+ printInfo("");
13051
+ printInfo("--- Last Run ---");
13052
+ printInfo(`Decision: ${r.last_gate_decision ?? "none"}`);
13053
+ printInfo(`Quality: ${r.last_quality_score ?? "-"}/10`);
13054
+ printInfo(`Security: ${r.last_security_score ?? "-"}/10`);
13055
+ printInfo(`Trend: ${r.trend}`);
13056
+ printInfo(`Runs: ${r.count} recorded`);
12921
13057
  }
12922
- } catch {
12923
- }
12924
- return result;
12925
- }
12926
- function scanDir(baseDir, dir, minMtime, result) {
12927
- try {
12928
- const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
12929
- for (const entry of entries) {
12930
- const fullPath = (0, import_node_path7.join)(dir, entry.name);
12931
- if (entry.isDirectory()) {
12932
- if (entry.name === "node_modules" || entry.name === ".git") continue;
12933
- scanDir(baseDir, fullPath, minMtime, result);
12934
- } else if (entry.isFile()) {
12935
- const ext = (0, import_node_path7.extname)(entry.name).slice(1);
12936
- if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
12937
- try {
12938
- const stat3 = (0, import_node_fs7.statSync)(fullPath);
12939
- if (stat3.mtimeMs >= minMtime) {
12940
- const relPath = fullPath.slice(baseDir.length + 1);
12941
- result.push(relPath);
12942
- }
12943
- } catch {
13058
+ if (mem.pending_items && mem.pending_items.length > 0) {
13059
+ printInfo("");
13060
+ printInfo("--- Pending Items ---");
13061
+ for (const item of mem.pending_items) {
13062
+ printInfo(` [${item.priority.toUpperCase()}] ${item.description}`);
13063
+ }
13064
+ }
13065
+ const recentTasks = mem.recent_tasks;
13066
+ const currentTask = mem.current_task;
13067
+ const contextFiles = mem.context_files;
13068
+ if (recentTasks && recentTasks.length > 0) {
13069
+ printInfo("");
13070
+ printInfo("--- Active Tasks ---");
13071
+ for (const task of recentTasks) {
13072
+ const isCurrent = currentTask && task.id === currentTask.id;
13073
+ const marker = isCurrent ? "\u25CF" : "\u25CB";
13074
+ const runCount = task.run_count ?? 0;
13075
+ const lastAt = task.last_run_at ? timeAgo(task.last_run_at) : "no runs";
13076
+ printInfo(` ${marker} ${task.label} ${runCount} run${runCount === 1 ? "" : "s"}, ${lastAt}`);
13077
+ }
13078
+ if (currentTask) {
13079
+ printInfo("");
13080
+ printInfo(`Current task: ${currentTask.label}`);
13081
+ if (contextFiles && contextFiles.length > 0) {
13082
+ printInfo(` Files in task: ${contextFiles.length} (${contextFiles.slice(0, 3).join(", ")}${contextFiles.length > 3 ? "..." : ""})`);
13083
+ }
13084
+ }
13085
+ }
13086
+ if (opts.history) {
13087
+ const runsResult = await apiRequest({
13088
+ method: "GET",
13089
+ path: `/runs?limit=${opts.limit}`,
13090
+ serviceUrl,
13091
+ token,
13092
+ verbose: globals.verbose
13093
+ });
13094
+ if (runsResult.ok && runsResult.data.runs.length > 0) {
13095
+ printInfo("");
13096
+ printInfo("--- Recent Runs ---");
13097
+ printInfo(`${"Run ID".padEnd(32)} ${"Decision".padEnd(10)}${"Q".padEnd(4)}${"S".padEnd(4)}${"Findings".padEnd(32)}Date`);
13098
+ for (const run of runsResult.data.runs) {
13099
+ const q = run.quality_score != null ? `${run.quality_score}` : "-";
13100
+ const s = run.security_score != null ? `${run.security_score}` : "-";
13101
+ const findings = formatFindingsSummary(run.findings_count);
13102
+ const date = run.created_at.slice(0, 19).replace("T", " ");
13103
+ printInfo(`${run.run_id.padEnd(32)} ${run.gate_decision.padEnd(10)}${q.padEnd(4)}${s.padEnd(4)}${findings.padEnd(32)}${date}`);
12944
13104
  }
12945
13105
  }
12946
13106
  }
12947
- } catch {
12948
- }
12949
- }
12950
- function filterAnalyzable(files) {
12951
- return files.filter((f) => {
12952
- const ext = (0, import_node_path7.extname)(f).slice(1);
12953
- return ANALYZABLE_EXTENSIONS.has(ext);
12954
13107
  });
12955
13108
  }
12956
- function filterReviewable(files) {
12957
- return files.filter((f) => {
12958
- const ext = (0, import_node_path7.extname)(f).slice(1);
12959
- if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
12960
- if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
12961
- const basename2 = f.split("/").pop() ?? "";
12962
- if (REVIEWABLE_FILENAMES.has(basename2)) return true;
12963
- if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
12964
- return false;
13109
+
13110
+ // src/commands/feedback.ts
13111
+ function registerFeedbackCommand(program2) {
13112
+ const feedbackCmd = program2.command("feedback").description("Send feedback to the Verity team");
13113
+ feedbackCmd.command("message <text>").description("Send general feedback").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
13114
+ const globals = program2.opts();
13115
+ await sendGeneralFeedback(message, opts, globals);
13116
+ });
13117
+ feedbackCmd.command("finding <run-id> <pattern-id> <action> [note]").description("Submit per-finding feedback (false_positive, acknowledged, useful, etc.)").option("--file <path>", "File path the finding applies to").option("--line <n>", "Line number", parseInt).action(async (runId, patternId, action, note, opts) => {
13118
+ const globals = program2.opts();
13119
+ const validActions = ["false_positive", "acknowledged", "will_fix_later", "wrong_severity", "useful"];
13120
+ if (!validActions.includes(action)) {
13121
+ printError(`Invalid action "${action}". Must be one of: ${validActions.join(", ")}`);
13122
+ process.exit(1);
13123
+ }
13124
+ const tokenResult = await resolveToken(globals.token);
13125
+ if (!tokenResult.ok) {
13126
+ printError(tokenResult.error);
13127
+ process.exit(1);
13128
+ }
13129
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
13130
+ if (!urlResult.ok) {
13131
+ printError(urlResult.error);
13132
+ process.exit(1);
13133
+ }
13134
+ const body = {
13135
+ run_id: runId,
13136
+ pattern_id: patternId,
13137
+ action
13138
+ };
13139
+ if (note) body.note = note;
13140
+ if (opts.file) body.file_path = opts.file;
13141
+ if (opts.line != null) body.line = opts.line;
13142
+ const result = await apiRequest({
13143
+ method: "POST",
13144
+ path: "/feedback/findings",
13145
+ serviceUrl: urlResult.data,
13146
+ token: tokenResult.data.token,
13147
+ body,
13148
+ verbose: globals.verbose
13149
+ });
13150
+ if (!result.ok) {
13151
+ printError(`Couldn't submit finding feedback: ${result.error}`);
13152
+ process.exit(1);
13153
+ }
13154
+ const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
13155
+ printInfo(status);
13156
+ });
13157
+ feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
13158
+ if (!message) return;
13159
+ const globals = program2.opts();
13160
+ await sendGeneralFeedback(message, opts, globals);
12965
13161
  });
12966
13162
  }
12967
- function filterSecurity(files) {
12968
- return files.filter(
12969
- (f) => SECURITY_PATTERNS.some((p) => p.test(f))
12970
- );
12971
- }
12972
- function getCurrentCommit() {
12973
- return execGit("git rev-parse HEAD") || "no-git";
12974
- }
12975
- function listTrackedFiles() {
12976
- const set = /* @__PURE__ */ new Set();
12977
- for (const f of splitLines(execGit("git ls-files"))) set.add(f);
12978
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
12979
- return Array.from(set);
13163
+ async function sendGeneralFeedback(message, opts, globals) {
13164
+ const tokenResult = await resolveToken(globals.token);
13165
+ if (!tokenResult.ok) {
13166
+ printError(tokenResult.error);
13167
+ printInfo(`Your message: ${message}`);
13168
+ process.exit(1);
13169
+ }
13170
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
13171
+ if (!urlResult.ok) {
13172
+ printError(urlResult.error);
13173
+ printInfo(`Your message: ${message}`);
13174
+ process.exit(1);
13175
+ }
13176
+ const body = { message };
13177
+ const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
13178
+ const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
13179
+ if (sessionId) body.session_id = sessionId;
13180
+ if (model) body.agent_model = model;
13181
+ const result = await apiRequest({
13182
+ method: "POST",
13183
+ path: "/feedback",
13184
+ serviceUrl: urlResult.data,
13185
+ token: tokenResult.data.token,
13186
+ body,
13187
+ verbose: globals.verbose
13188
+ });
13189
+ if (!result.ok) {
13190
+ printError(`Couldn't send feedback: ${result.error}`);
13191
+ printInfo(`Your message: ${message}`);
13192
+ process.exit(1);
13193
+ }
13194
+ printInfo("Thanks, feedback sent!");
12980
13195
  }
12981
13196
 
13197
+ // src/commands/analyze.ts
13198
+ var import_node_fs19 = require("node:fs");
13199
+ var import_node_path15 = require("node:path");
13200
+
12982
13201
  // src/lib/files.ts
12983
13202
  var import_node_fs8 = require("node:fs");
12984
- var import_node_path8 = require("node:path");
13203
+ var import_node_path9 = require("node:path");
12985
13204
  var LANG_MAP = {
12986
13205
  // Analyzable (static analysis + Gemini)
12987
13206
  ts: "typescript",
@@ -13049,7 +13268,7 @@ var LANG_MAP = {
13049
13268
  mk: "make"
13050
13269
  };
13051
13270
  function detectLanguage(filepath) {
13052
- const ext = (0, import_node_path8.extname)(filepath).slice(1);
13271
+ const ext = (0, import_node_path9.extname)(filepath).slice(1);
13053
13272
  return LANG_MAP[ext] ?? ext;
13054
13273
  }
13055
13274
  function sortByMtime(files) {
@@ -13335,7 +13554,7 @@ function runCodacyAnalysis(files) {
13335
13554
 
13336
13555
  // src/lib/specs.ts
13337
13556
  var import_node_fs11 = require("node:fs");
13338
- var import_node_path9 = require("node:path");
13557
+ var import_node_path10 = require("node:path");
13339
13558
  var SPEC_CANDIDATES = [
13340
13559
  "CLAUDE.md",
13341
13560
  "AGENTS.md",
@@ -13397,7 +13616,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
13397
13616
  try {
13398
13617
  const entries = (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true });
13399
13618
  for (const entry of entries) {
13400
- const fullPath = (0, import_node_path9.join)(dir, entry.name);
13619
+ const fullPath = (0, import_node_path10.join)(dir, entry.name);
13401
13620
  if (entry.isFile() && entry.name.endsWith(".md")) {
13402
13621
  result.push(fullPath);
13403
13622
  } else if (entry.isDirectory() && depth < maxDepth - 1) {
@@ -13409,7 +13628,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
13409
13628
  return result;
13410
13629
  }
13411
13630
  function discoverPlans() {
13412
- const homePlansDir = (0, import_node_path9.join)(process.env.HOME ?? "", ".claude", "plans");
13631
+ const homePlansDir = (0, import_node_path10.join)(process.env.HOME ?? "", ".claude", "plans");
13413
13632
  const localPlansDir = ".claude/plans";
13414
13633
  const candidates = [];
13415
13634
  const seen = /* @__PURE__ */ new Set();
@@ -13419,7 +13638,7 @@ function discoverPlans() {
13419
13638
  for (const f of (0, import_node_fs11.readdirSync)(plansDir)) {
13420
13639
  if (!f.endsWith(".md") || seen.has(f)) continue;
13421
13640
  seen.add(f);
13422
- const fullPath = (0, import_node_path9.join)(plansDir, f);
13641
+ const fullPath = (0, import_node_path10.join)(plansDir, f);
13423
13642
  try {
13424
13643
  const stat3 = (0, import_node_fs11.statSync)(fullPath);
13425
13644
  candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
@@ -13444,7 +13663,7 @@ function discoverPlans() {
13444
13663
 
13445
13664
  // src/lib/snapshot.ts
13446
13665
  var import_node_fs12 = require("node:fs");
13447
- var import_node_path10 = require("node:path");
13666
+ var import_node_path11 = require("node:path");
13448
13667
  var import_node_child_process7 = require("node:child_process");
13449
13668
  function generateSnapshotDiffs(files) {
13450
13669
  if (!(0, import_node_fs12.existsSync)(SNAPSHOT_DIR)) {
@@ -13452,7 +13671,7 @@ function generateSnapshotDiffs(files) {
13452
13671
  }
13453
13672
  const diffs = [];
13454
13673
  for (const file of files) {
13455
- const snapshotPath = (0, import_node_path10.join)(SNAPSHOT_DIR, file.path);
13674
+ const snapshotPath = (0, import_node_path11.join)(SNAPSHOT_DIR, file.path);
13456
13675
  const language = file.language ?? detectLanguage(file.path);
13457
13676
  if ((0, import_node_fs12.existsSync)(snapshotPath)) {
13458
13677
  const oldContent = (0, import_node_fs12.readFileSync)(snapshotPath, "utf-8");
@@ -13479,16 +13698,16 @@ ${addedLines}`,
13479
13698
  function saveSnapshots(files) {
13480
13699
  const snapshotPaths = /* @__PURE__ */ new Set();
13481
13700
  for (const file of files) {
13482
- const snapshotPath = (0, import_node_path10.join)(SNAPSHOT_DIR, file.path);
13701
+ const snapshotPath = (0, import_node_path11.join)(SNAPSHOT_DIR, file.path);
13483
13702
  snapshotPaths.add(snapshotPath);
13484
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(snapshotPath), { recursive: true });
13703
+ (0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(snapshotPath), { recursive: true });
13485
13704
  (0, import_node_fs12.writeFileSync)(snapshotPath, file.content);
13486
13705
  }
13487
13706
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
13488
13707
  }
13489
13708
  function computeDiff(oldContent, newContent, filePath) {
13490
- const tmpOld = (0, import_node_path10.join)(SNAPSHOT_DIR, ".diff-old.tmp");
13491
- const tmpNew = (0, import_node_path10.join)(SNAPSHOT_DIR, ".diff-new.tmp");
13709
+ const tmpOld = (0, import_node_path11.join)(SNAPSHOT_DIR, ".diff-old.tmp");
13710
+ const tmpNew = (0, import_node_path11.join)(SNAPSHOT_DIR, ".diff-new.tmp");
13492
13711
  try {
13493
13712
  (0, import_node_fs12.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
13494
13713
  (0, import_node_fs12.writeFileSync)(tmpOld, oldContent);
@@ -13521,7 +13740,7 @@ function cleanStaleSnapshots(dir, keepSet) {
13521
13740
  const entries = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true });
13522
13741
  for (const entry of entries) {
13523
13742
  if (entry.name.startsWith(".")) continue;
13524
- const fullPath = (0, import_node_path10.join)(dir, entry.name);
13743
+ const fullPath = (0, import_node_path11.join)(dir, entry.name);
13525
13744
  if (entry.isDirectory()) {
13526
13745
  cleanStaleSnapshots(fullPath, keepSet);
13527
13746
  try {
@@ -13542,7 +13761,7 @@ function cleanStaleSnapshots(dir, keepSet) {
13542
13761
 
13543
13762
  // src/lib/baseline.ts
13544
13763
  var import_node_fs13 = require("node:fs");
13545
- var import_node_path11 = require("node:path");
13764
+ var import_node_path12 = require("node:path");
13546
13765
  var import_node_crypto5 = require("node:crypto");
13547
13766
  var BASELINE_VERSION = 1;
13548
13767
  var BASELINE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -13553,13 +13772,13 @@ function sessionKey(sessionId) {
13553
13772
  return (0, import_node_crypto5.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
13554
13773
  }
13555
13774
  function sessionDir(key) {
13556
- return (0, import_node_path11.join)(projectPath(BASELINE_DIR), key);
13775
+ return (0, import_node_path12.join)(projectPath(BASELINE_DIR), key);
13557
13776
  }
13558
13777
  function manifestPath(dir) {
13559
- return (0, import_node_path11.join)(dir, "manifest.json");
13778
+ return (0, import_node_path12.join)(dir, "manifest.json");
13560
13779
  }
13561
13780
  function mirrorPath(dir, repoRelPath) {
13562
- return (0, import_node_path11.join)(dir, "files", repoRelPath);
13781
+ return (0, import_node_path12.join)(dir, "files", repoRelPath);
13563
13782
  }
13564
13783
  function captureBaseline(opts = {}) {
13565
13784
  const key = sessionKey(opts.sessionId);
@@ -13575,7 +13794,7 @@ function captureBaseline(opts = {}) {
13575
13794
  (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13576
13795
  } catch {
13577
13796
  }
13578
- const filesDir = (0, import_node_path11.join)(dir, "files");
13797
+ const filesDir = (0, import_node_path12.join)(dir, "files");
13579
13798
  const mirrored = [];
13580
13799
  try {
13581
13800
  (0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
@@ -13585,7 +13804,7 @@ function captureBaseline(opts = {}) {
13585
13804
  if (content === null) continue;
13586
13805
  const dest = mirrorPath(dir, p);
13587
13806
  try {
13588
- (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(dest), { recursive: true });
13807
+ (0, import_node_fs13.mkdirSync)((0, import_node_path12.dirname)(dest), { recursive: true });
13589
13808
  (0, import_node_fs13.writeFileSync)(dest, content);
13590
13809
  mirrored.push(p);
13591
13810
  } catch {
@@ -13713,7 +13932,7 @@ function pruneOldBaselines() {
13713
13932
  }
13714
13933
  const now = Date.now();
13715
13934
  for (const name of entries) {
13716
- const dir = (0, import_node_path11.join)(root, name);
13935
+ const dir = (0, import_node_path12.join)(root, name);
13717
13936
  const manifest = readManifest(dir);
13718
13937
  if (!manifest) {
13719
13938
  try {
@@ -13812,7 +14031,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
13812
14031
 
13813
14032
  // src/lib/cache-cleanup.ts
13814
14033
  var import_node_fs16 = require("node:fs");
13815
- var import_node_path12 = require("node:path");
14034
+ var import_node_path13 = require("node:path");
13816
14035
  var CACHE_TTL_DAYS = 7;
13817
14036
  function pruneStaleCache() {
13818
14037
  try {
@@ -13820,7 +14039,7 @@ function pruneStaleCache() {
13820
14039
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
13821
14040
  for (const entry of (0, import_node_fs16.readdirSync)(dir)) {
13822
14041
  if (!entry.startsWith("pending-")) continue;
13823
- const path = (0, import_node_path12.join)(dir, entry);
14042
+ const path = (0, import_node_path13.join)(dir, entry);
13824
14043
  try {
13825
14044
  const stat3 = (0, import_node_fs16.statSync)(path);
13826
14045
  if (stat3.mtimeMs < cutoff) {
@@ -14211,9 +14430,9 @@ function capArray(set, max) {
14211
14430
  }
14212
14431
 
14213
14432
  // src/lib/seed-runner.ts
14214
- var import_promises11 = require("node:fs/promises");
14433
+ var import_promises12 = require("node:fs/promises");
14215
14434
  var import_node_fs18 = require("node:fs");
14216
- var import_node_path13 = require("node:path");
14435
+ var import_node_path14 = require("node:path");
14217
14436
  var import_yaml2 = __toESM(require_dist());
14218
14437
 
14219
14438
  // src/lib/seed.ts
@@ -14457,7 +14676,7 @@ async function runSeed(opts) {
14457
14676
  }
14458
14677
  let standardDoc;
14459
14678
  try {
14460
- const raw = await (0, import_promises11.readFile)(STANDARD_FILE, "utf-8");
14679
+ const raw = await (0, import_promises12.readFile)(STANDARD_FILE, "utf-8");
14461
14680
  standardDoc = (0, import_yaml2.parse)(raw);
14462
14681
  } catch {
14463
14682
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
@@ -14466,7 +14685,7 @@ async function runSeed(opts) {
14466
14685
  let readmeContent;
14467
14686
  if ((0, import_node_fs18.existsSync)("README.md")) {
14468
14687
  try {
14469
- readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
14688
+ readmeContent = await (0, import_promises12.readFile)("README.md", "utf-8");
14470
14689
  } catch {
14471
14690
  }
14472
14691
  }
@@ -14474,7 +14693,7 @@ async function runSeed(opts) {
14474
14693
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
14475
14694
  if ((0, import_node_fs18.existsSync)(p)) {
14476
14695
  try {
14477
- claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
14696
+ claudeMdContent = await (0, import_promises12.readFile)(p, "utf-8");
14478
14697
  break;
14479
14698
  } catch {
14480
14699
  }
@@ -14495,7 +14714,7 @@ async function runSeed(opts) {
14495
14714
  if (candidates.length === 0) {
14496
14715
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
14497
14716
  }
14498
- const overviewPath = (0, import_node_path13.join)(MEMORY_DIR, "domain", "project-overview.md");
14717
+ const overviewPath = (0, import_node_path14.join)(MEMORY_DIR, "domain", "project-overview.md");
14499
14718
  if ((0, import_node_fs18.existsSync)(overviewPath) && !opts.force) {
14500
14719
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
14501
14720
  }
@@ -14531,10 +14750,10 @@ async function runSeed(opts) {
14531
14750
  }
14532
14751
  const nodeId = res.data.node_id;
14533
14752
  const filePathRel = res.data.file_path;
14534
- const targetPath = (0, import_node_path13.join)(MEMORY_DIR, filePathRel);
14753
+ const targetPath = (0, import_node_path14.join)(MEMORY_DIR, filePathRel);
14535
14754
  try {
14536
- await (0, import_promises11.mkdir)((0, import_node_path13.dirname)(targetPath), { recursive: true });
14537
- await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
14755
+ await (0, import_promises12.mkdir)((0, import_node_path14.dirname)(targetPath), { recursive: true });
14756
+ await (0, import_promises12.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
14538
14757
  created++;
14539
14758
  opts.onCreated?.(nodeId, filePathRel, c);
14540
14759
  } catch (err) {
@@ -14586,6 +14805,27 @@ function passAndExit(reason) {
14586
14805
  printJsonCompact({ gate_decision: "PASS", systemMessage: `Verity: ${reason}` });
14587
14806
  process.exit(0);
14588
14807
  }
14808
+ var EMPTY_STATIC = {
14809
+ tool: "@codacy/analysis-cli",
14810
+ findings: [],
14811
+ summary: { total_findings: 0, by_severity: {}, tools_run: [] }
14812
+ };
14813
+ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
14814
+ if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
14815
+ let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14816
+ if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
14817
+ if (scannable.length === 0) return EMPTY_STATIC;
14818
+ return runCodacyAnalysis(scannable);
14819
+ }
14820
+ function localOnlyAndExit(staticResults) {
14821
+ printJsonCompact({
14822
+ gate_decision: "PASS",
14823
+ 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.",
14824
+ unauthenticated: true,
14825
+ static_results: staticResults
14826
+ });
14827
+ process.exit(0);
14828
+ }
14589
14829
  function registerAnalyzeCommand(program2) {
14590
14830
  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) => {
14591
14831
  const globals = program2.opts();
@@ -14636,12 +14876,9 @@ async function runAnalyze(opts, globals) {
14636
14876
  passAndExit("Reflection-prompt turn \u2014 skipping analysis");
14637
14877
  }
14638
14878
  const tokenResult = await resolveToken(globals.token);
14639
- if (!tokenResult.ok) {
14640
- passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14641
- }
14642
14879
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
14643
- if (!urlResult.ok) {
14644
- passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14880
+ if (!tokenResult.ok || !urlResult.ok) {
14881
+ localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
14645
14882
  }
14646
14883
  const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
14647
14884
  let contextFilePaths = [];
@@ -14797,7 +15034,7 @@ async function runAnalyze(opts, globals) {
14797
15034
  let autoSeedNotice = null;
14798
15035
  try {
14799
15036
  await ensureMemoryDir();
14800
- const seedMarker = (0, import_node_path14.join)(VERITY_DIR, ".seeded");
15037
+ const seedMarker = (0, import_node_path15.join)(VERITY_DIR, ".seeded");
14801
15038
  const hasStandard = (0, import_node_fs19.existsSync)(STANDARD_FILE);
14802
15039
  const alreadyTried = (0, import_node_fs19.existsSync)(seedMarker);
14803
15040
  if (hasStandard && !alreadyTried) {
@@ -15215,13 +15452,14 @@ async function runReview(opts, globals) {
15215
15452
  }
15216
15453
  const codeDelta = collectCodeDelta(allFiles);
15217
15454
  const tokenResult = await resolveToken(globals.token);
15218
- if (!tokenResult.ok) {
15219
- printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
15220
- process.exit(0);
15221
- }
15222
15455
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
15223
- if (!urlResult.ok) {
15224
- printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
15456
+ if (!tokenResult.ok || !urlResult.ok) {
15457
+ printJsonCompact({
15458
+ gate_decision: "PASS",
15459
+ 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.",
15460
+ unauthenticated: true,
15461
+ static_results: staticResults
15462
+ });
15225
15463
  process.exit(0);
15226
15464
  }
15227
15465
  let specs;
@@ -15290,9 +15528,9 @@ async function runReview(opts, globals) {
15290
15528
 
15291
15529
  // src/commands/guard.ts
15292
15530
  var import_node_fs22 = require("node:fs");
15293
- var import_node_path15 = require("node:path");
15531
+ var import_node_path16 = require("node:path");
15294
15532
  var GUARD_BLOCK_CAP = 2;
15295
- var GUARD_ITER_FILE = (0, import_node_path15.join)(VERITY_DIR, ".guard-iteration");
15533
+ var GUARD_ITER_FILE = (0, import_node_path16.join)(VERITY_DIR, ".guard-iteration");
15296
15534
  function readPreToolUseStdin() {
15297
15535
  const empty = { command: "", cwd: null, sessionId: null };
15298
15536
  return new Promise((resolve) => {
@@ -15607,13 +15845,14 @@ function writeBlockMessage(moment, response) {
15607
15845
 
15608
15846
  // src/commands/init.ts
15609
15847
  var import_node_fs24 = require("node:fs");
15610
- var import_promises12 = require("node:fs/promises");
15611
- var import_node_path17 = require("node:path");
15848
+ var import_promises13 = require("node:fs/promises");
15849
+ var import_node_path18 = require("node:path");
15612
15850
  var import_node_child_process9 = require("node:child_process");
15851
+ var readline = __toESM(require("node:readline/promises"));
15613
15852
 
15614
15853
  // src/commands/migrate.ts
15615
15854
  var import_node_fs23 = require("node:fs");
15616
- var import_node_path16 = require("node:path");
15855
+ var import_node_path17 = require("node:path");
15617
15856
  var import_node_child_process8 = require("node:child_process");
15618
15857
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
15619
15858
  function defaultNpmRemover(pkg) {
@@ -15649,8 +15888,8 @@ async function runMigration(opts = {}) {
15649
15888
  return { actions, migrated: actions.length > 0 };
15650
15889
  }
15651
15890
  function migrateProjectDir(root, actions) {
15652
- const gateDir = (0, import_node_path16.join)(root, ".gate");
15653
- const verityDir = (0, import_node_path16.join)(root, ".verity");
15891
+ const gateDir = (0, import_node_path17.join)(root, ".gate");
15892
+ const verityDir = (0, import_node_path17.join)(root, ".verity");
15654
15893
  if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) {
15655
15894
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
15656
15895
  }
@@ -15704,11 +15943,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
15704
15943
  }
15705
15944
  function migrateGlobalCredentials(home, actions) {
15706
15945
  if (!home) return;
15707
- const gateCreds = (0, import_node_path16.join)(home, ".gate", "credentials");
15708
- const verityCreds = (0, import_node_path16.join)(home, ".verity", "credentials");
15946
+ const gateCreds = (0, import_node_path17.join)(home, ".gate", "credentials");
15947
+ const verityCreds = (0, import_node_path17.join)(home, ".verity", "credentials");
15709
15948
  if (!(0, import_node_fs23.existsSync)(gateCreds)) return;
15710
15949
  if (!(0, import_node_fs23.existsSync)(verityCreds)) {
15711
- (0, import_node_fs23.mkdirSync)((0, import_node_path16.join)(home, ".verity"), { recursive: true });
15950
+ (0, import_node_fs23.mkdirSync)((0, import_node_path17.join)(home, ".verity"), { recursive: true });
15712
15951
  moveFile(gateCreds, verityCreds);
15713
15952
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
15714
15953
  return;
@@ -15730,7 +15969,7 @@ async function migrateLegacyHooks(root, actions) {
15730
15969
  }
15731
15970
  }
15732
15971
  async function migrateClaudeMd(root, actions) {
15733
- const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
15972
+ const claudeMd = (0, import_node_path17.join)(root, "CLAUDE.md");
15734
15973
  const hadLegacyBlock = (0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15735
15974
  if (!hadLegacyBlock) return;
15736
15975
  try {
@@ -15741,8 +15980,8 @@ async function migrateClaudeMd(root, actions) {
15741
15980
  }
15742
15981
  }
15743
15982
  function migrateStandardFile(root, actions) {
15744
- const gateMd = (0, import_node_path16.join)(root, "GATE.md");
15745
- const verityMd = (0, import_node_path16.join)(root, "VERITY.md");
15983
+ const gateMd = (0, import_node_path17.join)(root, "GATE.md");
15984
+ const verityMd = (0, import_node_path17.join)(root, "VERITY.md");
15746
15985
  if (!(0, import_node_fs23.existsSync)(gateMd) || (0, import_node_fs23.existsSync)(verityMd)) return;
15747
15986
  let moved = false;
15748
15987
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
@@ -15830,15 +16069,15 @@ function moveFile(from, to) {
15830
16069
  function carryLegacyContents(gateDir, verityDir) {
15831
16070
  let copied = 0;
15832
16071
  const walk = (relDir) => {
15833
- const srcDir = (0, import_node_path16.join)(gateDir, relDir);
16072
+ const srcDir = (0, import_node_path17.join)(gateDir, relDir);
15834
16073
  for (const entry of (0, import_node_fs23.readdirSync)(srcDir)) {
15835
- const rel = relDir ? (0, import_node_path16.join)(relDir, entry) : entry;
15836
- const src = (0, import_node_path16.join)(gateDir, rel);
15837
- const dest = (0, import_node_path16.join)(verityDir, rel);
16074
+ const rel = relDir ? (0, import_node_path17.join)(relDir, entry) : entry;
16075
+ const src = (0, import_node_path17.join)(gateDir, rel);
16076
+ const dest = (0, import_node_path17.join)(verityDir, rel);
15838
16077
  if ((0, import_node_fs23.statSync)(src).isDirectory()) {
15839
16078
  walk(rel);
15840
16079
  } else if (!(0, import_node_fs23.existsSync)(dest)) {
15841
- (0, import_node_fs23.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
16080
+ (0, import_node_fs23.mkdirSync)((0, import_node_path17.dirname)(dest), { recursive: true });
15842
16081
  (0, import_node_fs23.cpSync)(src, dest);
15843
16082
  copied++;
15844
16083
  }
@@ -15848,22 +16087,22 @@ function carryLegacyContents(gateDir, verityDir) {
15848
16087
  return copied;
15849
16088
  }
15850
16089
  async function needsMigration(root = repoRoot()) {
15851
- const gateDir = (0, import_node_path16.join)(root, ".gate");
15852
- const verityDir = (0, import_node_path16.join)(root, ".verity");
16090
+ const gateDir = (0, import_node_path17.join)(root, ".gate");
16091
+ const verityDir = (0, import_node_path17.join)(root, ".verity");
15853
16092
  if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) return true;
15854
16093
  if ((0, import_node_fs23.existsSync)(gateDir) && (0, import_node_fs23.existsSync)(verityDir)) {
15855
- if ((0, import_node_fs23.existsSync)((0, import_node_path16.join)(gateDir, "credentials")) && !(0, import_node_fs23.existsSync)((0, import_node_path16.join)(verityDir, "credentials"))) {
16094
+ if ((0, import_node_fs23.existsSync)((0, import_node_path17.join)(gateDir, "credentials")) && !(0, import_node_fs23.existsSync)((0, import_node_path17.join)(verityDir, "credentials"))) {
15856
16095
  return true;
15857
16096
  }
15858
- if ((0, import_node_fs23.existsSync)((0, import_node_path16.join)(gateDir, "memory")) && !(0, import_node_fs23.existsSync)((0, import_node_path16.join)(verityDir, "memory"))) {
16097
+ if ((0, import_node_fs23.existsSync)((0, import_node_path17.join)(gateDir, "memory")) && !(0, import_node_fs23.existsSync)((0, import_node_path17.join)(verityDir, "memory"))) {
15859
16098
  return true;
15860
16099
  }
15861
16100
  }
15862
- const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
16101
+ const claudeMd = (0, import_node_path17.join)(root, "CLAUDE.md");
15863
16102
  if ((0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15864
16103
  return true;
15865
16104
  }
15866
- if ((0, import_node_fs23.existsSync)((0, import_node_path16.join)(root, "GATE.md")) && !(0, import_node_fs23.existsSync)((0, import_node_path16.join)(root, "VERITY.md"))) {
16105
+ if ((0, import_node_fs23.existsSync)((0, import_node_path17.join)(root, "GATE.md")) && !(0, import_node_fs23.existsSync)((0, import_node_path17.join)(root, "VERITY.md"))) {
15867
16106
  return true;
15868
16107
  }
15869
16108
  if (await hasLegacyHooksAt(root)) return true;
@@ -15889,17 +16128,77 @@ function registerMigrateCommand(program2) {
15889
16128
  }
15890
16129
 
15891
16130
  // src/commands/init.ts
16131
+ async function promptYes(question) {
16132
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
16133
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
16134
+ try {
16135
+ const answer = (await rl.question(question)).trim().toLowerCase();
16136
+ return answer === "" || answer === "y" || answer === "yes";
16137
+ } finally {
16138
+ rl.close();
16139
+ }
16140
+ }
16141
+ async function runOptionalAuth() {
16142
+ const existing = await resolveToken();
16143
+ if (existing.ok) {
16144
+ printInfo("Already authenticated \u2014 results will upload to the Verity service. \u2713");
16145
+ return;
16146
+ }
16147
+ let remote = "";
16148
+ try {
16149
+ remote = (0, import_node_child_process9.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
16150
+ } catch {
16151
+ }
16152
+ const localOnlyNote = () => {
16153
+ printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
16154
+ printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
16155
+ };
16156
+ if (process.stdin.isTTY && process.stdout.isTTY) {
16157
+ console.log("");
16158
+ console.log(" Signing in is optional. What it does:");
16159
+ console.log(" - Confirms you have write access to this repository. The GitHub token");
16160
+ console.log(" is used once to verify that, then discarded \u2014 Verity never stores it.");
16161
+ console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
16162
+ console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
16163
+ console.log(" - It is required to store and access run history for this repo");
16164
+ console.log(" (past results, trends, and shareable reports).");
16165
+ console.log(" - Skip and Verity still works fully locally: the gate runs and shows");
16166
+ console.log(" findings, but nothing is uploaded.");
16167
+ console.log("");
16168
+ }
16169
+ const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
16170
+ if (!wantsAuth) {
16171
+ printInfo("Skipped authentication.");
16172
+ localOnlyNote();
16173
+ return;
16174
+ }
16175
+ if (!remote) {
16176
+ printWarn("No git remote found \u2014 cannot authenticate yet.");
16177
+ localOnlyNote();
16178
+ return;
16179
+ }
16180
+ const projectName = parseRemote(remote)?.repo ?? (0, import_node_path18.basename)(process.cwd());
16181
+ printInfo("Authenticating with GitHub\u2026");
16182
+ const result = await registerProject({ projectName, remote, serviceUrl: DEFAULT_SERVICE_URL });
16183
+ if (result.ok) {
16184
+ printInfo(`Project registered: ${result.data.projectId} \u2713`);
16185
+ if (result.data.email) printInfo(` Authenticated as: ${result.data.email}`);
16186
+ } else {
16187
+ printWarn(`Authentication did not complete: ${result.error}`);
16188
+ localOnlyNote();
16189
+ }
16190
+ }
15892
16191
  function resolveDataDir() {
15893
16192
  const candidates = [
15894
- (0, import_node_path17.join)(__dirname, "..", "data"),
16193
+ (0, import_node_path18.join)(__dirname, "..", "data"),
15895
16194
  // installed: node_modules/@codacy/verity-cli/data
15896
- (0, import_node_path17.join)(__dirname, "..", "..", "data"),
16195
+ (0, import_node_path18.join)(__dirname, "..", "..", "data"),
15897
16196
  // edge case: nested resolution
15898
- (0, import_node_path17.join)(process.cwd(), "cli", "data")
16197
+ (0, import_node_path18.join)(process.cwd(), "cli", "data")
15899
16198
  // local dev: running from repo root
15900
16199
  ];
15901
16200
  for (const candidate of candidates) {
15902
- if ((0, import_node_fs24.existsSync)((0, import_node_path17.join)(candidate, "skills"))) {
16201
+ if ((0, import_node_fs24.existsSync)((0, import_node_path18.join)(candidate, "skills"))) {
15903
16202
  return candidate;
15904
16203
  }
15905
16204
  }
@@ -15908,8 +16207,8 @@ function resolveDataDir() {
15908
16207
  );
15909
16208
  }
15910
16209
  async function copyDir(src, dest) {
15911
- await (0, import_promises12.mkdir)(dest, { recursive: true });
15912
- await (0, import_promises12.cp)(src, dest, { recursive: true, force: true });
16210
+ await (0, import_promises13.mkdir)(dest, { recursive: true });
16211
+ await (0, import_promises13.cp)(src, dest, { recursive: true, force: true });
15913
16212
  }
15914
16213
  function registerInitCommand(program2) {
15915
16214
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
@@ -15978,24 +16277,24 @@ function registerInitCommand(program2) {
15978
16277
  console.log("");
15979
16278
  printInfo("Installing skills...");
15980
16279
  const dataDir = resolveDataDir();
15981
- const skillsSource = (0, import_node_path17.join)(dataDir, "skills");
16280
+ const skillsSource = (0, import_node_path18.join)(dataDir, "skills");
15982
16281
  const skillsDest = ".claude/skills";
15983
16282
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
15984
16283
  let skillsInstalled = 0;
15985
16284
  for (const skill of skills) {
15986
- const src = (0, import_node_path17.join)(skillsSource, skill);
15987
- const dest = (0, import_node_path17.join)(skillsDest, skill);
16285
+ const src = (0, import_node_path18.join)(skillsSource, skill);
16286
+ const dest = (0, import_node_path18.join)(skillsDest, skill);
15988
16287
  if (!(0, import_node_fs24.existsSync)(src)) {
15989
16288
  printWarn(` Skill data not found: ${skill}`);
15990
16289
  continue;
15991
16290
  }
15992
16291
  if ((0, import_node_fs24.existsSync)(dest) && !force) {
15993
- const srcSkill = (0, import_node_path17.join)(src, "SKILL.md");
15994
- const destSkill = (0, import_node_path17.join)(dest, "SKILL.md");
16292
+ const srcSkill = (0, import_node_path18.join)(src, "SKILL.md");
16293
+ const destSkill = (0, import_node_path18.join)(dest, "SKILL.md");
15995
16294
  if ((0, import_node_fs24.existsSync)(destSkill)) {
15996
16295
  try {
15997
- const srcContent = await (0, import_promises12.readFile)(srcSkill, "utf-8");
15998
- const destContent = await (0, import_promises12.readFile)(destSkill, "utf-8");
16296
+ const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
16297
+ const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
15999
16298
  if (srcContent === destContent) {
16000
16299
  skillsInstalled++;
16001
16300
  continue;
@@ -16021,7 +16320,7 @@ function registerInitCommand(program2) {
16021
16320
  printWarn(` ${hookResult.error}`);
16022
16321
  printInfo(' Run "verity hooks install --force" to overwrite.');
16023
16322
  }
16024
- await (0, import_promises12.mkdir)(VERITY_DIR, { recursive: true });
16323
+ await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
16025
16324
  await ensureMemoryDir();
16026
16325
  try {
16027
16326
  await ensureClaudeMdPointer();
@@ -16029,8 +16328,14 @@ function registerInitCommand(program2) {
16029
16328
  } catch (err) {
16030
16329
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
16031
16330
  }
16032
- const globalVerityDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".verity");
16033
- await (0, import_promises12.mkdir)(globalVerityDir, { recursive: true });
16331
+ const globalVerityDir = (0, import_node_path18.join)(process.env.HOME ?? "", ".verity");
16332
+ await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
16333
+ console.log("");
16334
+ try {
16335
+ await runOptionalAuth();
16336
+ } catch (err) {
16337
+ printWarn(`Authentication step skipped: ${err.message}`);
16338
+ }
16034
16339
  console.log("");
16035
16340
  printInfo("Verity initialized!");
16036
16341
  console.log("");
@@ -16047,13 +16352,14 @@ function registerInitCommand(program2) {
16047
16352
  console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
16048
16353
  console.log("");
16049
16354
  console.log(" Next step: open this project in Claude Code and run /verity-setup");
16355
+ console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
16050
16356
  console.log("");
16051
16357
  });
16052
16358
  }
16053
16359
 
16054
16360
  // src/commands/uninstall.ts
16055
16361
  var import_node_fs25 = require("node:fs");
16056
- var import_node_path18 = require("node:path");
16362
+ var import_node_path19 = require("node:path");
16057
16363
  var SKILL_NAMES = [
16058
16364
  "verity-setup",
16059
16365
  "verity-analyze",
@@ -16072,7 +16378,7 @@ function registerUninstallCommand(program2) {
16072
16378
  const actions = [];
16073
16379
  const skillsRoot = projectPath(".claude/skills");
16074
16380
  for (const name of SKILL_NAMES) {
16075
- const dir = (0, import_node_path18.join)(skillsRoot, name);
16381
+ const dir = (0, import_node_path19.join)(skillsRoot, name);
16076
16382
  if ((0, import_node_fs25.existsSync)(dir)) {
16077
16383
  actions.push({
16078
16384
  label: `Remove .claude/skills/${name}/`,
@@ -16118,7 +16424,7 @@ function registerUninstallCommand(program2) {
16118
16424
  }
16119
16425
  });
16120
16426
  const home = process.env.HOME ?? "";
16121
- const globalVerityDir = (0, import_node_path18.join)(home, ".verity");
16427
+ const globalVerityDir = (0, import_node_path19.join)(home, ".verity");
16122
16428
  if (purgeGlobal && (0, import_node_fs25.existsSync)(globalVerityDir)) {
16123
16429
  actions.push({
16124
16430
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
@@ -16317,7 +16623,7 @@ function registerTaskCommands(program2) {
16317
16623
 
16318
16624
  // src/commands/reset.ts
16319
16625
  var import_node_fs26 = require("node:fs");
16320
- var import_node_path19 = require("node:path");
16626
+ var import_node_path20 = require("node:path");
16321
16627
  function registerResetCommand(program2) {
16322
16628
  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) => {
16323
16629
  const globals = program2.opts();
@@ -16358,7 +16664,7 @@ function registerResetCommand(program2) {
16358
16664
  for (const entry of (0, import_node_fs26.readdirSync)(cacheDir)) {
16359
16665
  if (entry.startsWith("pending-")) {
16360
16666
  try {
16361
- (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(cacheDir, entry));
16667
+ (0, import_node_fs26.unlinkSync)((0, import_node_path20.join)(cacheDir, entry));
16362
16668
  purged++;
16363
16669
  } catch {
16364
16670
  }
@@ -16385,7 +16691,7 @@ function registerResetCommand(program2) {
16385
16691
  if ((0, import_node_fs26.existsSync)(logsDir)) {
16386
16692
  for (const entry of (0, import_node_fs26.readdirSync)(logsDir)) {
16387
16693
  try {
16388
- (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(logsDir, entry));
16694
+ (0, import_node_fs26.unlinkSync)((0, import_node_path20.join)(logsDir, entry));
16389
16695
  } catch {
16390
16696
  }
16391
16697
  }
@@ -16643,8 +16949,7 @@ function registerRunCommand(program2) {
16643
16949
  }
16644
16950
 
16645
16951
  // src/lib/telemetry.ts
16646
- var import_promises13 = require("node:fs/promises");
16647
- var import_node_path20 = require("node:path");
16952
+ var import_promises14 = require("node:fs/promises");
16648
16953
  var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
16649
16954
  var GITIGNORE_FILE = ".gitignore";
16650
16955
  var GITIGNORE_ENTRY = ".claude/settings.local.json";
@@ -16675,21 +16980,19 @@ function buildTelemetryEnv(serviceUrl, token) {
16675
16980
  var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv("", ""));
16676
16981
  async function readSettingsLocal() {
16677
16982
  try {
16678
- return JSON.parse(await (0, import_promises13.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
16983
+ return JSON.parse(await (0, import_promises14.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
16679
16984
  } catch {
16680
16985
  return {};
16681
16986
  }
16682
16987
  }
16683
16988
  async function writeSettingsLocal(settings) {
16684
- const file = projectPath(SETTINGS_LOCAL_FILE2);
16685
- await (0, import_promises13.mkdir)((0, import_node_path20.dirname)(file), { recursive: true });
16686
- await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
16989
+ await writeJsonFilePreservingStyle(projectPath(SETTINGS_LOCAL_FILE2), settings);
16687
16990
  }
16688
16991
  async function ensureGitignore() {
16689
16992
  const file = projectPath(GITIGNORE_FILE);
16690
16993
  let content = "";
16691
16994
  try {
16692
- content = await (0, import_promises13.readFile)(file, "utf-8");
16995
+ content = await (0, import_promises14.readFile)(file, "utf-8");
16693
16996
  } catch {
16694
16997
  }
16695
16998
  const lines = content.split("\n").map((l) => l.trim());
@@ -16698,7 +17001,7 @@ async function ensureGitignore() {
16698
17001
  }
16699
17002
  const block = "# Verity telemetry \u2014 holds your project token\n" + GITIGNORE_ENTRY + "\n";
16700
17003
  const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
16701
- await (0, import_promises13.writeFile)(file, next);
17004
+ await (0, import_promises14.writeFile)(file, next);
16702
17005
  }
16703
17006
  async function installTelemetry(serviceUrl, token) {
16704
17007
  const env = buildTelemetryEnv(serviceUrl, token);
@@ -16778,7 +17081,7 @@ function registerTelemetryCommands(program2) {
16778
17081
  }
16779
17082
 
16780
17083
  // src/cli.ts
16781
- program.name("verity").description("CLI for Verity quality gate service").version("0.25.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
17084
+ program.name("verity").description("CLI for Verity quality gate service").version("0.26.0-experimental.a2dc844").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16782
17085
  registerAuthCommands(program);
16783
17086
  registerHooksCommands(program);
16784
17087
  registerIntentCommands(program);