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

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,388 @@ 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
+ provider_token: ${providerToken}
11104
+ `,
11105
+ { mode: 384 }
11106
+ );
11107
+ await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
11108
+ });
11109
+ } catch (err) {
11110
+ return {
11111
+ ok: false,
11112
+ error: `Registered with the Verity service, but could not save credentials to ${CREDENTIALS_FILE}: ${err.message}. Check filesystem permissions and re-run "verity auth register".`
11113
+ };
11114
+ }
11115
+ try {
11116
+ await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
11117
+ await (0, import_promises3.appendFile)(
11118
+ GLOBAL_CREDENTIALS_FILE,
11119
+ `${opts.remote} token: ${token}
11120
+ ${opts.remote} provider_token: ${providerToken}
11121
+ `,
11122
+ { mode: 384 }
11123
+ );
11124
+ await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
11125
+ });
11126
+ } catch {
11127
+ }
11128
+ return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email: user?.email } };
11129
+ }
11130
+
10743
11131
  // src/commands/auth.ts
10744
11132
  function registerAuthCommands(program2) {
10745
11133
  const auth = program2.command("auth").description("Manage project authentication");
@@ -10749,36 +11137,26 @@ function registerAuthCommands(program2) {
10749
11137
  let remote = opts.remote;
10750
11138
  if (!remote) {
10751
11139
  try {
10752
- remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
11140
+ remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10753
11141
  } catch {
10754
11142
  printError("No git remote found. Use --remote to specify one.");
10755
11143
  process.exit(1);
10756
11144
  }
10757
11145
  }
10758
- const result = await apiRequest({
10759
- method: "POST",
10760
- path: "/auth/register",
11146
+ const result = await registerProject({
11147
+ projectName: opts.project,
11148
+ remote,
10761
11149
  serviceUrl,
10762
- body: { project_name: opts.project, git_remote_url: remote },
10763
11150
  verbose: globals.verbose
10764
11151
  });
10765
11152
  if (!result.ok) {
10766
11153
  printError(result.error);
10767
11154
  process.exit(1);
10768
11155
  }
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 });
11156
+ const { projectId, serviceUrl: resolvedUrl, email } = result.data;
11157
+ printInfo(`Project registered: ${projectId}`);
11158
+ if (email) printInfo(`Authenticated as: ${email}`);
11159
+ printJson({ project_id: projectId, service_url: resolvedUrl });
10782
11160
  });
10783
11161
  auth.command("verify").description("Verify the current token is valid").action(async () => {
10784
11162
  const globals = program2.opts();
@@ -10811,7 +11189,7 @@ service_url: ${service_url}
10811
11189
  let remote = opts.remote;
10812
11190
  if (!remote) {
10813
11191
  try {
10814
- remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
11192
+ remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10815
11193
  } catch {
10816
11194
  printError("No git remote found. Use --remote to specify one.");
10817
11195
  process.exit(1);
@@ -10838,8 +11216,63 @@ service_url: ${service_url}
10838
11216
  }
10839
11217
 
10840
11218
  // src/lib/hooks.ts
10841
- var import_promises4 = require("node:fs/promises");
10842
- var import_node_path3 = require("node:path");
11219
+ var import_promises5 = require("node:fs/promises");
11220
+ var import_node_path5 = require("node:path");
11221
+
11222
+ // src/lib/json-file.ts
11223
+ var import_promises4 = require("node:fs/promises");
11224
+ var import_node_path4 = require("node:path");
11225
+ function jsonSemanticEqual(a, b) {
11226
+ if (a === b) return true;
11227
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
11228
+ return a === b;
11229
+ }
11230
+ const aIsArr = Array.isArray(a);
11231
+ const bIsArr = Array.isArray(b);
11232
+ if (aIsArr || bIsArr) {
11233
+ if (!aIsArr || !bIsArr || a.length !== b.length) return false;
11234
+ for (let i = 0; i < a.length; i++) {
11235
+ if (!jsonSemanticEqual(a[i], b[i])) return false;
11236
+ }
11237
+ return true;
11238
+ }
11239
+ const ao = a;
11240
+ const bo = b;
11241
+ const aKeys = Object.keys(ao).filter((k) => ao[k] !== void 0);
11242
+ const bKeys = Object.keys(bo).filter((k) => bo[k] !== void 0);
11243
+ if (aKeys.length !== bKeys.length) return false;
11244
+ for (const k of aKeys) {
11245
+ if (bo[k] === void 0) return false;
11246
+ if (!jsonSemanticEqual(ao[k], bo[k])) return false;
11247
+ }
11248
+ return true;
11249
+ }
11250
+ function detectJsonIndent(raw) {
11251
+ const m = raw.match(/\n([ \t]+)\S/);
11252
+ return m ? m[1] : 2;
11253
+ }
11254
+ async function writeJsonFilePreservingStyle(file, value) {
11255
+ let currentRaw = null;
11256
+ try {
11257
+ currentRaw = await (0, import_promises4.readFile)(file, "utf-8");
11258
+ } catch {
11259
+ currentRaw = null;
11260
+ }
11261
+ if (currentRaw !== null) {
11262
+ try {
11263
+ if (jsonSemanticEqual(JSON.parse(currentRaw), value)) return false;
11264
+ } catch {
11265
+ }
11266
+ }
11267
+ const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
11268
+ const next = JSON.stringify(value, null, indent) + "\n";
11269
+ if (next === currentRaw) return false;
11270
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
11271
+ await (0, import_promises4.writeFile)(file, next);
11272
+ return true;
11273
+ }
11274
+
11275
+ // src/lib/hooks.ts
10843
11276
  var VERITY_STOP_HOOK = {
10844
11277
  type: "command",
10845
11278
  command: "verity analyze",
@@ -10917,7 +11350,7 @@ function globalSettingsFile() {
10917
11350
  }
10918
11351
  async function readSettings() {
10919
11352
  try {
10920
- const content = await (0, import_promises4.readFile)(CLAUDE_SETTINGS_FILE, "utf-8");
11353
+ const content = await (0, import_promises5.readFile)(CLAUDE_SETTINGS_FILE, "utf-8");
10921
11354
  return JSON.parse(content);
10922
11355
  } catch {
10923
11356
  return {};
@@ -10928,7 +11361,7 @@ async function readAllSettings() {
10928
11361
  const out = [];
10929
11362
  for (const f of files) {
10930
11363
  try {
10931
- out.push(JSON.parse(await (0, import_promises4.readFile)(f, "utf-8")));
11364
+ out.push(JSON.parse(await (0, import_promises5.readFile)(f, "utf-8")));
10932
11365
  } catch {
10933
11366
  }
10934
11367
  }
@@ -10957,7 +11390,7 @@ async function checkExternalVerityHooks() {
10957
11390
  for (const f of [SETTINGS_LOCAL_FILE, globalSettingsFile()]) {
10958
11391
  let settings;
10959
11392
  try {
10960
- settings = JSON.parse(await (0, import_promises4.readFile)(f, "utf-8"));
11393
+ settings = JSON.parse(await (0, import_promises5.readFile)(f, "utf-8"));
10961
11394
  } catch {
10962
11395
  continue;
10963
11396
  }
@@ -10991,20 +11424,17 @@ async function checkAllVerityHooksDetailed() {
10991
11424
  return { stop, intent, baseline, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
10992
11425
  }
10993
11426
  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");
11427
+ await writeJsonFilePreservingStyle(CLAUDE_SETTINGS_FILE, settings);
10996
11428
  }
10997
11429
  async function readSettingsAt(root) {
10998
11430
  try {
10999
- return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11431
+ return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11000
11432
  } catch {
11001
11433
  return {};
11002
11434
  }
11003
11435
  }
11004
11436
  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");
11437
+ await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
11008
11438
  }
11009
11439
  async function hasLegacyHooksAt(root) {
11010
11440
  const settings = await readSettingsAt(root);
@@ -11243,9 +11673,9 @@ function registerHooksCommands(program2) {
11243
11673
  var import_node_crypto3 = require("node:crypto");
11244
11674
 
11245
11675
  // 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");
11676
+ var import_promises6 = require("node:fs/promises");
11677
+ var import_node_fs3 = require("node:fs");
11678
+ var import_node_child_process5 = require("node:child_process");
11249
11679
  var import_node_crypto = require("node:crypto");
11250
11680
  function stripImageReferences(text) {
11251
11681
  return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
@@ -11255,7 +11685,7 @@ function bufferTmpPath() {
11255
11685
  }
11256
11686
  async function appendToConversationBuffer(prompt, sessionId) {
11257
11687
  try {
11258
- await (0, import_promises5.mkdir)(VERITY_DIR, { recursive: true });
11688
+ await (0, import_promises6.mkdir)(VERITY_DIR, { recursive: true });
11259
11689
  let sanitized = prompt.length > MAX_INTENT_CHARS ? prompt.slice(0, MAX_INTENT_CHARS) : prompt;
11260
11690
  sanitized = stripImageReferences(sanitized);
11261
11691
  const entry = {
@@ -11273,14 +11703,14 @@ async function appendToConversationBuffer(prompt, sessionId) {
11273
11703
  const capped = recent.slice(-CONVERSATION_MAX_ENTRIES);
11274
11704
  const content = capped.map((e) => JSON.stringify(e)).join("\n") + "\n";
11275
11705
  const tmpFile = bufferTmpPath();
11276
- await (0, import_promises5.writeFile)(tmpFile, content);
11277
- await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11706
+ await (0, import_promises6.writeFile)(tmpFile, content);
11707
+ await (0, import_promises6.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11278
11708
  } catch {
11279
11709
  }
11280
11710
  }
11281
11711
  async function readAndClearConversationBuffer(currentSessionId) {
11282
11712
  try {
11283
- if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
11713
+ if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
11284
11714
  const entries = await readBufferEntries();
11285
11715
  let mine = entries;
11286
11716
  let others = [];
@@ -11291,10 +11721,10 @@ async function readAndClearConversationBuffer(currentSessionId) {
11291
11721
  if (others.length > 0) {
11292
11722
  const remaining = others.map((e) => JSON.stringify(e)).join("\n") + "\n";
11293
11723
  const tmpFile = bufferTmpPath();
11294
- await (0, import_promises5.writeFile)(tmpFile, remaining);
11295
- await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11724
+ await (0, import_promises6.writeFile)(tmpFile, remaining);
11725
+ await (0, import_promises6.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11296
11726
  } else {
11297
- await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11727
+ await (0, import_promises6.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11298
11728
  });
11299
11729
  }
11300
11730
  if (mine.length > 0) {
@@ -11304,10 +11734,10 @@ async function readAndClearConversationBuffer(currentSessionId) {
11304
11734
  };
11305
11735
  }
11306
11736
  }
11307
- if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
11737
+ if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
11308
11738
  try {
11309
- const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
11310
- await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
11739
+ const content = await (0, import_promises6.readFile)(INTENT_FILE, "utf-8");
11740
+ await (0, import_promises6.unlink)(INTENT_FILE).catch(() => {
11311
11741
  });
11312
11742
  const data = JSON.parse(content);
11313
11743
  if (data.prompt) {
@@ -11330,7 +11760,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
11330
11760
  }
11331
11761
  async function readBufferEntries() {
11332
11762
  try {
11333
- const content = await (0, import_promises5.readFile)(CONVERSATION_BUFFER_FILE, "utf-8");
11763
+ const content = await (0, import_promises6.readFile)(CONVERSATION_BUFFER_FILE, "utf-8");
11334
11764
  const entries = [];
11335
11765
  for (const line of content.split("\n")) {
11336
11766
  const trimmed = line.trim();
@@ -11348,7 +11778,7 @@ async function readBufferEntries() {
11348
11778
  }
11349
11779
  function getRecentCommitMessages() {
11350
11780
  try {
11351
- const output = (0, import_node_child_process4.execSync)(
11781
+ const output = (0, import_node_child_process5.execSync)(
11352
11782
  'git log --since="30 minutes ago" --format="%s" -5',
11353
11783
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
11354
11784
  ).trim();
@@ -11360,9 +11790,9 @@ function getRecentCommitMessages() {
11360
11790
  }
11361
11791
 
11362
11792
  // 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");
11793
+ var import_promises7 = require("node:fs/promises");
11794
+ var import_node_fs4 = require("node:fs");
11795
+ var import_node_path6 = require("node:path");
11366
11796
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
11367
11797
  var MAX_BUFFER_BYTES = 500 * 1024;
11368
11798
  var MAX_PROMPT_CHARS = 2e3;
@@ -11401,9 +11831,9 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
11401
11831
  }
11402
11832
  async function readTaskContextBuffer(taskId) {
11403
11833
  const filePath = bufferPath(taskId);
11404
- if (!(0, import_node_fs3.existsSync)(filePath)) return null;
11834
+ if (!(0, import_node_fs4.existsSync)(filePath)) return null;
11405
11835
  try {
11406
- const content = await (0, import_promises6.readFile)(filePath, "utf-8");
11836
+ const content = await (0, import_promises7.readFile)(filePath, "utf-8");
11407
11837
  if (!content.trim()) return null;
11408
11838
  const lines = content.split("\n").filter((l) => l.trim());
11409
11839
  const formatted = [];
@@ -11435,16 +11865,16 @@ async function readTaskContextBuffer(taskId) {
11435
11865
  }
11436
11866
  async function cleanupTaskContextBuffers() {
11437
11867
  try {
11438
- if (!(0, import_node_fs3.existsSync)(TASK_CONTEXT_DIR)) return;
11439
- const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
11868
+ if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
11869
+ const files = await (0, import_promises7.readdir)(TASK_CONTEXT_DIR);
11440
11870
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
11441
11871
  for (const file of files) {
11442
11872
  if (!file.endsWith(".jsonl")) continue;
11443
- const filePath = (0, import_node_path4.join)(TASK_CONTEXT_DIR, file);
11873
+ const filePath = (0, import_node_path6.join)(TASK_CONTEXT_DIR, file);
11444
11874
  try {
11445
- const stats = await (0, import_promises6.stat)(filePath);
11875
+ const stats = await (0, import_promises7.stat)(filePath);
11446
11876
  if (stats.mtimeMs < cutoffMs) {
11447
- await (0, import_promises6.unlink)(filePath);
11877
+ await (0, import_promises7.unlink)(filePath);
11448
11878
  }
11449
11879
  } catch {
11450
11880
  }
@@ -11454,33 +11884,33 @@ async function cleanupTaskContextBuffers() {
11454
11884
  }
11455
11885
  function bufferPath(taskId) {
11456
11886
  const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
11457
- return (0, import_node_path4.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11887
+ return (0, import_node_path6.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11458
11888
  }
11459
11889
  async function appendEntry(taskId, entry) {
11460
11890
  try {
11461
- await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
11891
+ await (0, import_promises7.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
11462
11892
  const filePath = bufferPath(taskId);
11463
- if ((0, import_node_fs3.existsSync)(filePath)) {
11464
- const stats = await (0, import_promises6.stat)(filePath);
11893
+ if ((0, import_node_fs4.existsSync)(filePath)) {
11894
+ const stats = await (0, import_promises7.stat)(filePath);
11465
11895
  if (stats.size >= MAX_BUFFER_BYTES) {
11466
- const content = await (0, import_promises6.readFile)(filePath, "utf-8");
11896
+ const content = await (0, import_promises7.readFile)(filePath, "utf-8");
11467
11897
  const lines = content.split("\n").filter((l) => l.trim());
11468
11898
  const keepFrom = Math.floor(lines.length * 0.25);
11469
11899
  const pruned = lines.slice(keepFrom).join("\n") + "\n";
11470
- await (0, import_promises6.writeFile)(filePath, pruned);
11900
+ await (0, import_promises7.writeFile)(filePath, pruned);
11471
11901
  }
11472
11902
  }
11473
11903
  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);
11904
+ const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
11905
+ await (0, import_promises7.writeFile)(filePath, existing + line);
11476
11906
  } catch {
11477
11907
  }
11478
11908
  }
11479
11909
 
11480
11910
  // 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");
11911
+ var import_promises8 = require("node:fs/promises");
11912
+ var import_node_fs5 = require("node:fs");
11913
+ var import_node_path7 = require("node:path");
11484
11914
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
11485
11915
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
11486
11916
  var DEFAULT_BUDGET_TOKENS = 2e3;
@@ -11573,19 +12003,19 @@ function parseFrontmatter(content) {
11573
12003
  return { fm, body: match[2].trim() };
11574
12004
  }
11575
12005
  async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
11576
- if (!(0, import_node_fs4.existsSync)(memoryDir())) return null;
12006
+ if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
11577
12007
  const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
11578
12008
  const promptTokens = tokenize(promptText);
11579
12009
  const nodes = [];
11580
12010
  for (const domain of DOMAINS) {
11581
- const domainDir = (0, import_node_path5.join)(memoryDir(), domain);
11582
- if (!(0, import_node_fs4.existsSync)(domainDir)) continue;
12011
+ const domainDir = (0, import_node_path7.join)(memoryDir(), domain);
12012
+ if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
11583
12013
  try {
11584
- const files = await (0, import_promises7.readdir)(domainDir);
12014
+ const files = await (0, import_promises8.readdir)(domainDir);
11585
12015
  for (const file of files) {
11586
12016
  if (!file.endsWith(".md")) continue;
11587
12017
  try {
11588
- const content = await (0, import_promises7.readFile)((0, import_node_path5.join)(domainDir, file), "utf-8");
12018
+ const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
11589
12019
  const { fm, body } = parseFrontmatter(content);
11590
12020
  if (fm.status && fm.status !== "active") continue;
11591
12021
  nodes.push({
@@ -11643,9 +12073,9 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
11643
12073
  }
11644
12074
 
11645
12075
  // 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");
12076
+ var import_promises9 = require("node:fs/promises");
12077
+ var import_node_fs6 = require("node:fs");
12078
+ var import_node_path8 = require("node:path");
11649
12079
  var import_node_crypto2 = require("node:crypto");
11650
12080
 
11651
12081
  // src/lib/glob-match.ts
@@ -11714,36 +12144,36 @@ var memoryDir2 = () => projectPath(`${VERITY_DIR}/memory`);
11714
12144
  var DOMAINS2 = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations", "_archive"];
11715
12145
  var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
11716
12146
  async function ensureMemoryDir() {
11717
- await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
12147
+ await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
11718
12148
  for (const domain of DOMAINS2) {
11719
- await (0, import_promises8.mkdir)((0, import_node_path6.join)(memoryDir2(), domain), { recursive: true });
12149
+ await (0, import_promises9.mkdir)((0, import_node_path8.join)(memoryDir2(), domain), { recursive: true });
11720
12150
  }
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);
12151
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"))) {
12152
+ await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
11723
12153
  }
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");
12154
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "index.md"))) {
12155
+ await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
11726
12156
  }
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");
12157
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md"))) {
12158
+ await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
11729
12159
  }
11730
12160
  }
11731
12161
  async function buildManifest() {
11732
- if (!(0, import_node_fs5.existsSync)(memoryDir2())) {
12162
+ if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
11733
12163
  return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
11734
12164
  }
11735
12165
  const nodes = [];
11736
12166
  for (const domain of DOMAINS2) {
11737
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11738
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12167
+ const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12168
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11739
12169
  try {
11740
- const files = await (0, import_promises8.readdir)(domainDir);
12170
+ const files = await (0, import_promises9.readdir)(domainDir);
11741
12171
  for (const file of files) {
11742
12172
  if (!file.endsWith(".md")) continue;
11743
12173
  const filePath = `${domain}/${file}`;
11744
- const fullPath = (0, import_node_path6.join)(memoryDir2(), filePath);
12174
+ const fullPath = (0, import_node_path8.join)(memoryDir2(), filePath);
11745
12175
  try {
11746
- const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
12176
+ const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
11747
12177
  const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
11748
12178
  nodes.push({ path: filePath, content_hash: `sha256:${hash}` });
11749
12179
  } catch {
@@ -11754,13 +12184,13 @@ async function buildManifest() {
11754
12184
  }
11755
12185
  let indexHash = null;
11756
12186
  try {
11757
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "index.md"), "utf-8");
12187
+ const indexContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "utf-8");
11758
12188
  indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
11759
12189
  } catch {
11760
12190
  }
11761
12191
  let logLength = 0;
11762
12192
  try {
11763
- const logContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "utf-8");
12193
+ const logContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8");
11764
12194
  logLength = logContent.split("\n").length;
11765
12195
  } catch {
11766
12196
  }
@@ -11771,15 +12201,15 @@ function hashContent(content) {
11771
12201
  }
11772
12202
  async function readOnDiskNodes() {
11773
12203
  const out = /* @__PURE__ */ new Map();
11774
- if (!(0, import_node_fs5.existsSync)(memoryDir2())) return out;
12204
+ if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
11775
12205
  for (const domain of DOMAINS2) {
11776
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11777
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12206
+ const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12207
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11778
12208
  try {
11779
- for (const file of await (0, import_promises8.readdir)(domainDir)) {
12209
+ for (const file of await (0, import_promises9.readdir)(domainDir)) {
11780
12210
  if (!file.endsWith(".md")) continue;
11781
12211
  try {
11782
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8")));
12212
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8")));
11783
12213
  } catch {
11784
12214
  }
11785
12215
  }
@@ -11791,7 +12221,7 @@ async function readOnDiskNodes() {
11791
12221
  async function readSyncBaseline() {
11792
12222
  const out = /* @__PURE__ */ new Map();
11793
12223
  try {
11794
- const parsed = JSON.parse(await (0, import_promises8.readFile)(syncStateFile(), "utf-8"));
12224
+ const parsed = JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
11795
12225
  if (Array.isArray(parsed?.nodes)) {
11796
12226
  for (const n of parsed.nodes) if (n?.path) out.set(n.path, n.hash ?? null);
11797
12227
  } else if (Array.isArray(parsed?.paths)) {
@@ -11807,12 +12237,12 @@ async function recordSyncedNodePaths() {
11807
12237
  const next = JSON.stringify({ schema: 2, nodes }) + "\n";
11808
12238
  let existing = "";
11809
12239
  try {
11810
- existing = await (0, import_promises8.readFile)(syncStateFile(), "utf-8");
12240
+ existing = await (0, import_promises9.readFile)(syncStateFile(), "utf-8");
11811
12241
  } catch {
11812
12242
  }
11813
12243
  if (existing === next) return;
11814
- await (0, import_promises8.mkdir)(projectPath(VERITY_DIR), { recursive: true });
11815
- await (0, import_promises8.writeFile)(syncStateFile(), next);
12244
+ await (0, import_promises9.mkdir)(projectPath(VERITY_DIR), { recursive: true });
12245
+ await (0, import_promises9.writeFile)(syncStateFile(), next);
11816
12246
  } catch {
11817
12247
  }
11818
12248
  }
@@ -11825,11 +12255,11 @@ async function computeEditedNodeUploads() {
11825
12255
  const uploads = [];
11826
12256
  for (const [path, prevHash] of prev) {
11827
12257
  if (prevHash == null) continue;
11828
- const full = (0, import_node_path6.join)(memoryDir2(), path);
11829
- if (!(0, import_node_fs5.existsSync)(full)) continue;
12258
+ const full = (0, import_node_path8.join)(memoryDir2(), path);
12259
+ if (!(0, import_node_fs6.existsSync)(full)) continue;
11830
12260
  let content;
11831
12261
  try {
11832
- content = await (0, import_promises8.readFile)(full, "utf-8");
12262
+ content = await (0, import_promises9.readFile)(full, "utf-8");
11833
12263
  } catch {
11834
12264
  continue;
11835
12265
  }
@@ -11862,15 +12292,15 @@ async function applyMemoryWrites(writes, opts = {}) {
11862
12292
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
11863
12293
  for (const n of notes) logLines.push(` - ${n}`);
11864
12294
  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");
12295
+ const existing = (0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
12296
+ await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
11867
12297
  } catch {
11868
12298
  }
11869
12299
  await recordSyncedNodePaths();
11870
12300
  return count;
11871
12301
  }
11872
12302
  async function applyOneWrite(write, treePaths) {
11873
- const fullPath = (0, import_node_path6.join)(memoryDir2(), write.path);
12303
+ const fullPath = (0, import_node_path8.join)(memoryDir2(), write.path);
11874
12304
  const notes = [];
11875
12305
  let content = write.content;
11876
12306
  if (treePaths && treePaths.length > 0) {
@@ -11880,10 +12310,10 @@ async function applyOneWrite(write, treePaths) {
11880
12310
  notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
11881
12311
  }
11882
12312
  }
11883
- if ((0, import_node_fs5.existsSync)(fullPath)) {
12313
+ if ((0, import_node_fs6.existsSync)(fullPath)) {
11884
12314
  let existing = "";
11885
12315
  try {
11886
- existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
12316
+ existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
11887
12317
  } catch {
11888
12318
  }
11889
12319
  if (existing === content) return { written: false, notes };
@@ -11892,8 +12322,8 @@ async function applyOneWrite(write, treePaths) {
11892
12322
  return { written: false, notes };
11893
12323
  }
11894
12324
  }
11895
- await (0, import_promises8.mkdir)((0, import_node_path6.dirname)(fullPath), { recursive: true });
11896
- await (0, import_promises8.writeFile)(fullPath, content);
12325
+ await (0, import_promises9.mkdir)((0, import_node_path8.dirname)(fullPath), { recursive: true });
12326
+ await (0, import_promises9.writeFile)(fullPath, content);
11897
12327
  return { written: true, notes };
11898
12328
  }
11899
12329
  function groundFileGlobs(content, treePaths) {
@@ -11933,10 +12363,10 @@ async function regenerateIndex() {
11933
12363
  ];
11934
12364
  let totalNodes = 0;
11935
12365
  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;
12366
+ const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
12367
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11938
12368
  try {
11939
- const files = await (0, import_promises8.readdir)(domainDir);
12369
+ const files = await (0, import_promises9.readdir)(domainDir);
11940
12370
  const mdFiles = files.filter((f) => f.endsWith(".md"));
11941
12371
  if (mdFiles.length === 0) continue;
11942
12372
  lines.push(`## ${domain}/ (${mdFiles.length})`);
@@ -11944,7 +12374,7 @@ async function regenerateIndex() {
11944
12374
  for (const file of mdFiles.sort()) {
11945
12375
  const slug = file.replace(/\.md$/, "");
11946
12376
  try {
11947
- const content = await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
12377
+ const content = await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
11948
12378
  const title = pickFrontmatter(content, "title") ?? slug;
11949
12379
  const kind = pickFrontmatter(content, "kind") ?? "-";
11950
12380
  const confidence = pickFrontmatter(content, "confidence");
@@ -11968,14 +12398,14 @@ async function regenerateIndex() {
11968
12398
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
11969
12399
  }
11970
12400
  const next = lines.join("\n") + "\n";
11971
- const indexPath = (0, import_node_path6.join)(memoryDir2(), "index.md");
12401
+ const indexPath = (0, import_node_path8.join)(memoryDir2(), "index.md");
11972
12402
  let existing = null;
11973
12403
  try {
11974
- existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
12404
+ existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
11975
12405
  } catch {
11976
12406
  }
11977
12407
  if (existing === next) return;
11978
- await (0, import_promises8.writeFile)(indexPath, next);
12408
+ await (0, import_promises9.writeFile)(indexPath, next);
11979
12409
  }
11980
12410
  function pickFrontmatter(content, key) {
11981
12411
  const re = new RegExp(`^${key}:\\s*"?([^"\\n]+?)"?\\s*$`, "m");
@@ -12050,10 +12480,10 @@ function hasLegacyMemoryBlock(text) {
12050
12480
  return findMarker(text, LEGACY_MD_START) !== -1;
12051
12481
  }
12052
12482
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
12053
- const claudeMdPath = (0, import_node_path6.join)(cwd, "CLAUDE.md");
12483
+ const claudeMdPath = (0, import_node_path8.join)(cwd, "CLAUDE.md");
12054
12484
  let existing = "";
12055
- if ((0, import_node_fs5.existsSync)(claudeMdPath)) {
12056
- existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
12485
+ if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
12486
+ existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
12057
12487
  }
12058
12488
  let startTag = CLAUDE_MD_START;
12059
12489
  let endTag = CLAUDE_MD_END;
@@ -12109,7 +12539,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
12109
12539
  next = existing.replace(/\n*$/, "") + "\n\n" + block + "\n";
12110
12540
  }
12111
12541
  if (next === existing) return;
12112
- await (0, import_promises8.writeFile)(claudeMdPath, next);
12542
+ await (0, import_promises9.writeFile)(claudeMdPath, next);
12113
12543
  }
12114
12544
  function extractPreserveContent(interior) {
12115
12545
  for (const [start, end] of [
@@ -12182,7 +12612,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
12182
12612
  `;
12183
12613
 
12184
12614
  // src/commands/intent.ts
12185
- var import_node_fs6 = require("node:fs");
12615
+ var import_node_fs7 = require("node:fs");
12186
12616
  function registerIntentCommands(program2) {
12187
12617
  const intent = program2.command("intent").description("Manage intent capture");
12188
12618
  intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
@@ -12191,7 +12621,7 @@ function registerIntentCommands(program2) {
12191
12621
  process.chdir(repoRoot());
12192
12622
  } catch {
12193
12623
  }
12194
- if (!(0, import_node_fs6.existsSync)(VERITY_DIR)) {
12624
+ if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
12195
12625
  process.exit(0);
12196
12626
  }
12197
12627
  const chunks = [];
@@ -12292,7 +12722,7 @@ async function fireClassify(prompt, sessionId) {
12292
12722
  }
12293
12723
 
12294
12724
  // src/commands/standard.ts
12295
- var import_promises9 = require("node:fs/promises");
12725
+ var import_promises10 = require("node:fs/promises");
12296
12726
  var import_yaml = __toESM(require_dist());
12297
12727
  function registerStandardCommands(program2) {
12298
12728
  const standard = program2.command("standard").description("Manage the project Standard");
@@ -12310,7 +12740,7 @@ function registerStandardCommands(program2) {
12310
12740
  }
12311
12741
  let yamlContent;
12312
12742
  try {
12313
- yamlContent = await (0, import_promises9.readFile)(opts.file, "utf-8");
12743
+ yamlContent = await (0, import_promises10.readFile)(opts.file, "utf-8");
12314
12744
  } catch {
12315
12745
  printError(`Cannot read ${opts.file}`);
12316
12746
  process.exit(1);
@@ -12401,7 +12831,7 @@ function registerStandardCommands(program2) {
12401
12831
  }
12402
12832
 
12403
12833
  // src/commands/config.ts
12404
- var import_promises10 = require("node:fs/promises");
12834
+ var import_promises11 = require("node:fs/promises");
12405
12835
  function registerConfigCommands(program2) {
12406
12836
  const config = program2.command("config").description("Manage analysis configuration");
12407
12837
  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 +12848,7 @@ function registerConfigCommands(program2) {
12418
12848
  }
12419
12849
  let content;
12420
12850
  try {
12421
- const raw = await (0, import_promises10.readFile)(opts.file, "utf-8");
12851
+ const raw = await (0, import_promises11.readFile)(opts.file, "utf-8");
12422
12852
  content = JSON.parse(raw);
12423
12853
  } catch {
12424
12854
  printError(`Cannot read or parse ${opts.file}`);
@@ -12618,370 +13048,161 @@ function registerStatusCommand(program2) {
12618
13048
  if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
12619
13049
  printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
12620
13050
  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);
13051
+ const r = mem.recent_runs;
13052
+ printInfo("");
13053
+ printInfo("--- Last Run ---");
13054
+ printInfo(`Decision: ${r.last_gate_decision ?? "none"}`);
13055
+ printInfo(`Quality: ${r.last_quality_score ?? "-"}/10`);
13056
+ printInfo(`Security: ${r.last_security_score ?? "-"}/10`);
13057
+ printInfo(`Trend: ${r.trend}`);
13058
+ printInfo(`Runs: ${r.count} recorded`);
12921
13059
  }
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 {
13060
+ if (mem.pending_items && mem.pending_items.length > 0) {
13061
+ printInfo("");
13062
+ printInfo("--- Pending Items ---");
13063
+ for (const item of mem.pending_items) {
13064
+ printInfo(` [${item.priority.toUpperCase()}] ${item.description}`);
13065
+ }
13066
+ }
13067
+ const recentTasks = mem.recent_tasks;
13068
+ const currentTask = mem.current_task;
13069
+ const contextFiles = mem.context_files;
13070
+ if (recentTasks && recentTasks.length > 0) {
13071
+ printInfo("");
13072
+ printInfo("--- Active Tasks ---");
13073
+ for (const task of recentTasks) {
13074
+ const isCurrent = currentTask && task.id === currentTask.id;
13075
+ const marker = isCurrent ? "\u25CF" : "\u25CB";
13076
+ const runCount = task.run_count ?? 0;
13077
+ const lastAt = task.last_run_at ? timeAgo(task.last_run_at) : "no runs";
13078
+ printInfo(` ${marker} ${task.label} ${runCount} run${runCount === 1 ? "" : "s"}, ${lastAt}`);
13079
+ }
13080
+ if (currentTask) {
13081
+ printInfo("");
13082
+ printInfo(`Current task: ${currentTask.label}`);
13083
+ if (contextFiles && contextFiles.length > 0) {
13084
+ printInfo(` Files in task: ${contextFiles.length} (${contextFiles.slice(0, 3).join(", ")}${contextFiles.length > 3 ? "..." : ""})`);
13085
+ }
13086
+ }
13087
+ }
13088
+ if (opts.history) {
13089
+ const runsResult = await apiRequest({
13090
+ method: "GET",
13091
+ path: `/runs?limit=${opts.limit}`,
13092
+ serviceUrl,
13093
+ token,
13094
+ verbose: globals.verbose
13095
+ });
13096
+ if (runsResult.ok && runsResult.data.runs.length > 0) {
13097
+ printInfo("");
13098
+ printInfo("--- Recent Runs ---");
13099
+ printInfo(`${"Run ID".padEnd(32)} ${"Decision".padEnd(10)}${"Q".padEnd(4)}${"S".padEnd(4)}${"Findings".padEnd(32)}Date`);
13100
+ for (const run of runsResult.data.runs) {
13101
+ const q = run.quality_score != null ? `${run.quality_score}` : "-";
13102
+ const s = run.security_score != null ? `${run.security_score}` : "-";
13103
+ const findings = formatFindingsSummary(run.findings_count);
13104
+ const date = run.created_at.slice(0, 19).replace("T", " ");
13105
+ printInfo(`${run.run_id.padEnd(32)} ${run.gate_decision.padEnd(10)}${q.padEnd(4)}${s.padEnd(4)}${findings.padEnd(32)}${date}`);
12944
13106
  }
12945
13107
  }
12946
13108
  }
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
13109
  });
12955
13110
  }
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;
13111
+
13112
+ // src/commands/feedback.ts
13113
+ function registerFeedbackCommand(program2) {
13114
+ const feedbackCmd = program2.command("feedback").description("Send feedback to the Verity team");
13115
+ feedbackCmd.command("message <text>").description("Send general feedback").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
13116
+ const globals = program2.opts();
13117
+ await sendGeneralFeedback(message, opts, globals);
13118
+ });
13119
+ 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) => {
13120
+ const globals = program2.opts();
13121
+ const validActions = ["false_positive", "acknowledged", "will_fix_later", "wrong_severity", "useful"];
13122
+ if (!validActions.includes(action)) {
13123
+ printError(`Invalid action "${action}". Must be one of: ${validActions.join(", ")}`);
13124
+ process.exit(1);
13125
+ }
13126
+ const tokenResult = await resolveToken(globals.token);
13127
+ if (!tokenResult.ok) {
13128
+ printError(tokenResult.error);
13129
+ process.exit(1);
13130
+ }
13131
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
13132
+ if (!urlResult.ok) {
13133
+ printError(urlResult.error);
13134
+ process.exit(1);
13135
+ }
13136
+ const body = {
13137
+ run_id: runId,
13138
+ pattern_id: patternId,
13139
+ action
13140
+ };
13141
+ if (note) body.note = note;
13142
+ if (opts.file) body.file_path = opts.file;
13143
+ if (opts.line != null) body.line = opts.line;
13144
+ const result = await apiRequest({
13145
+ method: "POST",
13146
+ path: "/feedback/findings",
13147
+ serviceUrl: urlResult.data,
13148
+ token: tokenResult.data.token,
13149
+ body,
13150
+ verbose: globals.verbose
13151
+ });
13152
+ if (!result.ok) {
13153
+ printError(`Couldn't submit finding feedback: ${result.error}`);
13154
+ process.exit(1);
13155
+ }
13156
+ const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
13157
+ printInfo(status);
13158
+ });
13159
+ feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
13160
+ if (!message) return;
13161
+ const globals = program2.opts();
13162
+ await sendGeneralFeedback(message, opts, globals);
12965
13163
  });
12966
13164
  }
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);
13165
+ async function sendGeneralFeedback(message, opts, globals) {
13166
+ const tokenResult = await resolveToken(globals.token);
13167
+ if (!tokenResult.ok) {
13168
+ printError(tokenResult.error);
13169
+ printInfo(`Your message: ${message}`);
13170
+ process.exit(1);
13171
+ }
13172
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
13173
+ if (!urlResult.ok) {
13174
+ printError(urlResult.error);
13175
+ printInfo(`Your message: ${message}`);
13176
+ process.exit(1);
13177
+ }
13178
+ const body = { message };
13179
+ const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
13180
+ const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
13181
+ if (sessionId) body.session_id = sessionId;
13182
+ if (model) body.agent_model = model;
13183
+ const result = await apiRequest({
13184
+ method: "POST",
13185
+ path: "/feedback",
13186
+ serviceUrl: urlResult.data,
13187
+ token: tokenResult.data.token,
13188
+ body,
13189
+ verbose: globals.verbose
13190
+ });
13191
+ if (!result.ok) {
13192
+ printError(`Couldn't send feedback: ${result.error}`);
13193
+ printInfo(`Your message: ${message}`);
13194
+ process.exit(1);
13195
+ }
13196
+ printInfo("Thanks, feedback sent!");
12980
13197
  }
12981
13198
 
13199
+ // src/commands/analyze.ts
13200
+ var import_node_fs19 = require("node:fs");
13201
+ var import_node_path15 = require("node:path");
13202
+
12982
13203
  // src/lib/files.ts
12983
13204
  var import_node_fs8 = require("node:fs");
12984
- var import_node_path8 = require("node:path");
13205
+ var import_node_path9 = require("node:path");
12985
13206
  var LANG_MAP = {
12986
13207
  // Analyzable (static analysis + Gemini)
12987
13208
  ts: "typescript",
@@ -13049,7 +13270,7 @@ var LANG_MAP = {
13049
13270
  mk: "make"
13050
13271
  };
13051
13272
  function detectLanguage(filepath) {
13052
- const ext = (0, import_node_path8.extname)(filepath).slice(1);
13273
+ const ext = (0, import_node_path9.extname)(filepath).slice(1);
13053
13274
  return LANG_MAP[ext] ?? ext;
13054
13275
  }
13055
13276
  function sortByMtime(files) {
@@ -13335,7 +13556,7 @@ function runCodacyAnalysis(files) {
13335
13556
 
13336
13557
  // src/lib/specs.ts
13337
13558
  var import_node_fs11 = require("node:fs");
13338
- var import_node_path9 = require("node:path");
13559
+ var import_node_path10 = require("node:path");
13339
13560
  var SPEC_CANDIDATES = [
13340
13561
  "CLAUDE.md",
13341
13562
  "AGENTS.md",
@@ -13397,7 +13618,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
13397
13618
  try {
13398
13619
  const entries = (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true });
13399
13620
  for (const entry of entries) {
13400
- const fullPath = (0, import_node_path9.join)(dir, entry.name);
13621
+ const fullPath = (0, import_node_path10.join)(dir, entry.name);
13401
13622
  if (entry.isFile() && entry.name.endsWith(".md")) {
13402
13623
  result.push(fullPath);
13403
13624
  } else if (entry.isDirectory() && depth < maxDepth - 1) {
@@ -13409,7 +13630,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
13409
13630
  return result;
13410
13631
  }
13411
13632
  function discoverPlans() {
13412
- const homePlansDir = (0, import_node_path9.join)(process.env.HOME ?? "", ".claude", "plans");
13633
+ const homePlansDir = (0, import_node_path10.join)(process.env.HOME ?? "", ".claude", "plans");
13413
13634
  const localPlansDir = ".claude/plans";
13414
13635
  const candidates = [];
13415
13636
  const seen = /* @__PURE__ */ new Set();
@@ -13419,7 +13640,7 @@ function discoverPlans() {
13419
13640
  for (const f of (0, import_node_fs11.readdirSync)(plansDir)) {
13420
13641
  if (!f.endsWith(".md") || seen.has(f)) continue;
13421
13642
  seen.add(f);
13422
- const fullPath = (0, import_node_path9.join)(plansDir, f);
13643
+ const fullPath = (0, import_node_path10.join)(plansDir, f);
13423
13644
  try {
13424
13645
  const stat3 = (0, import_node_fs11.statSync)(fullPath);
13425
13646
  candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
@@ -13444,7 +13665,7 @@ function discoverPlans() {
13444
13665
 
13445
13666
  // src/lib/snapshot.ts
13446
13667
  var import_node_fs12 = require("node:fs");
13447
- var import_node_path10 = require("node:path");
13668
+ var import_node_path11 = require("node:path");
13448
13669
  var import_node_child_process7 = require("node:child_process");
13449
13670
  function generateSnapshotDiffs(files) {
13450
13671
  if (!(0, import_node_fs12.existsSync)(SNAPSHOT_DIR)) {
@@ -13452,7 +13673,7 @@ function generateSnapshotDiffs(files) {
13452
13673
  }
13453
13674
  const diffs = [];
13454
13675
  for (const file of files) {
13455
- const snapshotPath = (0, import_node_path10.join)(SNAPSHOT_DIR, file.path);
13676
+ const snapshotPath = (0, import_node_path11.join)(SNAPSHOT_DIR, file.path);
13456
13677
  const language = file.language ?? detectLanguage(file.path);
13457
13678
  if ((0, import_node_fs12.existsSync)(snapshotPath)) {
13458
13679
  const oldContent = (0, import_node_fs12.readFileSync)(snapshotPath, "utf-8");
@@ -13479,16 +13700,16 @@ ${addedLines}`,
13479
13700
  function saveSnapshots(files) {
13480
13701
  const snapshotPaths = /* @__PURE__ */ new Set();
13481
13702
  for (const file of files) {
13482
- const snapshotPath = (0, import_node_path10.join)(SNAPSHOT_DIR, file.path);
13703
+ const snapshotPath = (0, import_node_path11.join)(SNAPSHOT_DIR, file.path);
13483
13704
  snapshotPaths.add(snapshotPath);
13484
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(snapshotPath), { recursive: true });
13705
+ (0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(snapshotPath), { recursive: true });
13485
13706
  (0, import_node_fs12.writeFileSync)(snapshotPath, file.content);
13486
13707
  }
13487
13708
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
13488
13709
  }
13489
13710
  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");
13711
+ const tmpOld = (0, import_node_path11.join)(SNAPSHOT_DIR, ".diff-old.tmp");
13712
+ const tmpNew = (0, import_node_path11.join)(SNAPSHOT_DIR, ".diff-new.tmp");
13492
13713
  try {
13493
13714
  (0, import_node_fs12.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
13494
13715
  (0, import_node_fs12.writeFileSync)(tmpOld, oldContent);
@@ -13521,7 +13742,7 @@ function cleanStaleSnapshots(dir, keepSet) {
13521
13742
  const entries = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true });
13522
13743
  for (const entry of entries) {
13523
13744
  if (entry.name.startsWith(".")) continue;
13524
- const fullPath = (0, import_node_path10.join)(dir, entry.name);
13745
+ const fullPath = (0, import_node_path11.join)(dir, entry.name);
13525
13746
  if (entry.isDirectory()) {
13526
13747
  cleanStaleSnapshots(fullPath, keepSet);
13527
13748
  try {
@@ -13542,7 +13763,7 @@ function cleanStaleSnapshots(dir, keepSet) {
13542
13763
 
13543
13764
  // src/lib/baseline.ts
13544
13765
  var import_node_fs13 = require("node:fs");
13545
- var import_node_path11 = require("node:path");
13766
+ var import_node_path12 = require("node:path");
13546
13767
  var import_node_crypto5 = require("node:crypto");
13547
13768
  var BASELINE_VERSION = 1;
13548
13769
  var BASELINE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -13553,13 +13774,13 @@ function sessionKey(sessionId) {
13553
13774
  return (0, import_node_crypto5.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
13554
13775
  }
13555
13776
  function sessionDir(key) {
13556
- return (0, import_node_path11.join)(projectPath(BASELINE_DIR), key);
13777
+ return (0, import_node_path12.join)(projectPath(BASELINE_DIR), key);
13557
13778
  }
13558
13779
  function manifestPath(dir) {
13559
- return (0, import_node_path11.join)(dir, "manifest.json");
13780
+ return (0, import_node_path12.join)(dir, "manifest.json");
13560
13781
  }
13561
13782
  function mirrorPath(dir, repoRelPath) {
13562
- return (0, import_node_path11.join)(dir, "files", repoRelPath);
13783
+ return (0, import_node_path12.join)(dir, "files", repoRelPath);
13563
13784
  }
13564
13785
  function captureBaseline(opts = {}) {
13565
13786
  const key = sessionKey(opts.sessionId);
@@ -13575,7 +13796,7 @@ function captureBaseline(opts = {}) {
13575
13796
  (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13576
13797
  } catch {
13577
13798
  }
13578
- const filesDir = (0, import_node_path11.join)(dir, "files");
13799
+ const filesDir = (0, import_node_path12.join)(dir, "files");
13579
13800
  const mirrored = [];
13580
13801
  try {
13581
13802
  (0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
@@ -13585,7 +13806,7 @@ function captureBaseline(opts = {}) {
13585
13806
  if (content === null) continue;
13586
13807
  const dest = mirrorPath(dir, p);
13587
13808
  try {
13588
- (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(dest), { recursive: true });
13809
+ (0, import_node_fs13.mkdirSync)((0, import_node_path12.dirname)(dest), { recursive: true });
13589
13810
  (0, import_node_fs13.writeFileSync)(dest, content);
13590
13811
  mirrored.push(p);
13591
13812
  } catch {
@@ -13713,7 +13934,7 @@ function pruneOldBaselines() {
13713
13934
  }
13714
13935
  const now = Date.now();
13715
13936
  for (const name of entries) {
13716
- const dir = (0, import_node_path11.join)(root, name);
13937
+ const dir = (0, import_node_path12.join)(root, name);
13717
13938
  const manifest = readManifest(dir);
13718
13939
  if (!manifest) {
13719
13940
  try {
@@ -13812,7 +14033,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
13812
14033
 
13813
14034
  // src/lib/cache-cleanup.ts
13814
14035
  var import_node_fs16 = require("node:fs");
13815
- var import_node_path12 = require("node:path");
14036
+ var import_node_path13 = require("node:path");
13816
14037
  var CACHE_TTL_DAYS = 7;
13817
14038
  function pruneStaleCache() {
13818
14039
  try {
@@ -13820,7 +14041,7 @@ function pruneStaleCache() {
13820
14041
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
13821
14042
  for (const entry of (0, import_node_fs16.readdirSync)(dir)) {
13822
14043
  if (!entry.startsWith("pending-")) continue;
13823
- const path = (0, import_node_path12.join)(dir, entry);
14044
+ const path = (0, import_node_path13.join)(dir, entry);
13824
14045
  try {
13825
14046
  const stat3 = (0, import_node_fs16.statSync)(path);
13826
14047
  if (stat3.mtimeMs < cutoff) {
@@ -14211,9 +14432,9 @@ function capArray(set, max) {
14211
14432
  }
14212
14433
 
14213
14434
  // src/lib/seed-runner.ts
14214
- var import_promises11 = require("node:fs/promises");
14435
+ var import_promises12 = require("node:fs/promises");
14215
14436
  var import_node_fs18 = require("node:fs");
14216
- var import_node_path13 = require("node:path");
14437
+ var import_node_path14 = require("node:path");
14217
14438
  var import_yaml2 = __toESM(require_dist());
14218
14439
 
14219
14440
  // src/lib/seed.ts
@@ -14457,7 +14678,7 @@ async function runSeed(opts) {
14457
14678
  }
14458
14679
  let standardDoc;
14459
14680
  try {
14460
- const raw = await (0, import_promises11.readFile)(STANDARD_FILE, "utf-8");
14681
+ const raw = await (0, import_promises12.readFile)(STANDARD_FILE, "utf-8");
14461
14682
  standardDoc = (0, import_yaml2.parse)(raw);
14462
14683
  } catch {
14463
14684
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
@@ -14466,7 +14687,7 @@ async function runSeed(opts) {
14466
14687
  let readmeContent;
14467
14688
  if ((0, import_node_fs18.existsSync)("README.md")) {
14468
14689
  try {
14469
- readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
14690
+ readmeContent = await (0, import_promises12.readFile)("README.md", "utf-8");
14470
14691
  } catch {
14471
14692
  }
14472
14693
  }
@@ -14474,7 +14695,7 @@ async function runSeed(opts) {
14474
14695
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
14475
14696
  if ((0, import_node_fs18.existsSync)(p)) {
14476
14697
  try {
14477
- claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
14698
+ claudeMdContent = await (0, import_promises12.readFile)(p, "utf-8");
14478
14699
  break;
14479
14700
  } catch {
14480
14701
  }
@@ -14495,7 +14716,7 @@ async function runSeed(opts) {
14495
14716
  if (candidates.length === 0) {
14496
14717
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
14497
14718
  }
14498
- const overviewPath = (0, import_node_path13.join)(MEMORY_DIR, "domain", "project-overview.md");
14719
+ const overviewPath = (0, import_node_path14.join)(MEMORY_DIR, "domain", "project-overview.md");
14499
14720
  if ((0, import_node_fs18.existsSync)(overviewPath) && !opts.force) {
14500
14721
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
14501
14722
  }
@@ -14531,10 +14752,10 @@ async function runSeed(opts) {
14531
14752
  }
14532
14753
  const nodeId = res.data.node_id;
14533
14754
  const filePathRel = res.data.file_path;
14534
- const targetPath = (0, import_node_path13.join)(MEMORY_DIR, filePathRel);
14755
+ const targetPath = (0, import_node_path14.join)(MEMORY_DIR, filePathRel);
14535
14756
  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));
14757
+ await (0, import_promises12.mkdir)((0, import_node_path14.dirname)(targetPath), { recursive: true });
14758
+ await (0, import_promises12.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
14538
14759
  created++;
14539
14760
  opts.onCreated?.(nodeId, filePathRel, c);
14540
14761
  } catch (err) {
@@ -14586,6 +14807,27 @@ function passAndExit(reason) {
14586
14807
  printJsonCompact({ gate_decision: "PASS", systemMessage: `Verity: ${reason}` });
14587
14808
  process.exit(0);
14588
14809
  }
14810
+ var EMPTY_STATIC = {
14811
+ tool: "@codacy/analysis-cli",
14812
+ findings: [],
14813
+ summary: { total_findings: 0, by_severity: {}, tools_run: [] }
14814
+ };
14815
+ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
14816
+ if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
14817
+ let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14818
+ if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
14819
+ if (scannable.length === 0) return EMPTY_STATIC;
14820
+ return runCodacyAnalysis(scannable);
14821
+ }
14822
+ function localOnlyAndExit(staticResults) {
14823
+ printJsonCompact({
14824
+ gate_decision: "PASS",
14825
+ systemMessage: "Verity: not authenticated \u2014 ran a local static-only check (no deep review, no upload). Run `verity init` to authenticate and enable the full quality gate.",
14826
+ unauthenticated: true,
14827
+ static_results: staticResults
14828
+ });
14829
+ process.exit(0);
14830
+ }
14589
14831
  function registerAnalyzeCommand(program2) {
14590
14832
  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
14833
  const globals = program2.opts();
@@ -14636,12 +14878,9 @@ async function runAnalyze(opts, globals) {
14636
14878
  passAndExit("Reflection-prompt turn \u2014 skipping analysis");
14637
14879
  }
14638
14880
  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
14881
  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.");
14882
+ if (!tokenResult.ok || !urlResult.ok) {
14883
+ localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
14645
14884
  }
14646
14885
  const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
14647
14886
  let contextFilePaths = [];
@@ -14797,7 +15036,7 @@ async function runAnalyze(opts, globals) {
14797
15036
  let autoSeedNotice = null;
14798
15037
  try {
14799
15038
  await ensureMemoryDir();
14800
- const seedMarker = (0, import_node_path14.join)(VERITY_DIR, ".seeded");
15039
+ const seedMarker = (0, import_node_path15.join)(VERITY_DIR, ".seeded");
14801
15040
  const hasStandard = (0, import_node_fs19.existsSync)(STANDARD_FILE);
14802
15041
  const alreadyTried = (0, import_node_fs19.existsSync)(seedMarker);
14803
15042
  if (hasStandard && !alreadyTried) {
@@ -15215,13 +15454,14 @@ async function runReview(opts, globals) {
15215
15454
  }
15216
15455
  const codeDelta = collectCodeDelta(allFiles);
15217
15456
  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
15457
  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.");
15458
+ if (!tokenResult.ok || !urlResult.ok) {
15459
+ printJsonCompact({
15460
+ gate_decision: "PASS",
15461
+ systemMessage: "Verity: not authenticated \u2014 showing local static results only (no deep review, no upload). Run `verity init` to authenticate and unlock the full review.",
15462
+ unauthenticated: true,
15463
+ static_results: staticResults
15464
+ });
15225
15465
  process.exit(0);
15226
15466
  }
15227
15467
  let specs;
@@ -15290,9 +15530,9 @@ async function runReview(opts, globals) {
15290
15530
 
15291
15531
  // src/commands/guard.ts
15292
15532
  var import_node_fs22 = require("node:fs");
15293
- var import_node_path15 = require("node:path");
15533
+ var import_node_path16 = require("node:path");
15294
15534
  var GUARD_BLOCK_CAP = 2;
15295
- var GUARD_ITER_FILE = (0, import_node_path15.join)(VERITY_DIR, ".guard-iteration");
15535
+ var GUARD_ITER_FILE = (0, import_node_path16.join)(VERITY_DIR, ".guard-iteration");
15296
15536
  function readPreToolUseStdin() {
15297
15537
  const empty = { command: "", cwd: null, sessionId: null };
15298
15538
  return new Promise((resolve) => {
@@ -15607,13 +15847,14 @@ function writeBlockMessage(moment, response) {
15607
15847
 
15608
15848
  // src/commands/init.ts
15609
15849
  var import_node_fs24 = require("node:fs");
15610
- var import_promises12 = require("node:fs/promises");
15611
- var import_node_path17 = require("node:path");
15850
+ var import_promises13 = require("node:fs/promises");
15851
+ var import_node_path18 = require("node:path");
15612
15852
  var import_node_child_process9 = require("node:child_process");
15853
+ var readline = __toESM(require("node:readline/promises"));
15613
15854
 
15614
15855
  // src/commands/migrate.ts
15615
15856
  var import_node_fs23 = require("node:fs");
15616
- var import_node_path16 = require("node:path");
15857
+ var import_node_path17 = require("node:path");
15617
15858
  var import_node_child_process8 = require("node:child_process");
15618
15859
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
15619
15860
  function defaultNpmRemover(pkg) {
@@ -15649,8 +15890,8 @@ async function runMigration(opts = {}) {
15649
15890
  return { actions, migrated: actions.length > 0 };
15650
15891
  }
15651
15892
  function migrateProjectDir(root, actions) {
15652
- const gateDir = (0, import_node_path16.join)(root, ".gate");
15653
- const verityDir = (0, import_node_path16.join)(root, ".verity");
15893
+ const gateDir = (0, import_node_path17.join)(root, ".gate");
15894
+ const verityDir = (0, import_node_path17.join)(root, ".verity");
15654
15895
  if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) {
15655
15896
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
15656
15897
  }
@@ -15704,11 +15945,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
15704
15945
  }
15705
15946
  function migrateGlobalCredentials(home, actions) {
15706
15947
  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");
15948
+ const gateCreds = (0, import_node_path17.join)(home, ".gate", "credentials");
15949
+ const verityCreds = (0, import_node_path17.join)(home, ".verity", "credentials");
15709
15950
  if (!(0, import_node_fs23.existsSync)(gateCreds)) return;
15710
15951
  if (!(0, import_node_fs23.existsSync)(verityCreds)) {
15711
- (0, import_node_fs23.mkdirSync)((0, import_node_path16.join)(home, ".verity"), { recursive: true });
15952
+ (0, import_node_fs23.mkdirSync)((0, import_node_path17.join)(home, ".verity"), { recursive: true });
15712
15953
  moveFile(gateCreds, verityCreds);
15713
15954
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
15714
15955
  return;
@@ -15730,7 +15971,7 @@ async function migrateLegacyHooks(root, actions) {
15730
15971
  }
15731
15972
  }
15732
15973
  async function migrateClaudeMd(root, actions) {
15733
- const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
15974
+ const claudeMd = (0, import_node_path17.join)(root, "CLAUDE.md");
15734
15975
  const hadLegacyBlock = (0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15735
15976
  if (!hadLegacyBlock) return;
15736
15977
  try {
@@ -15741,8 +15982,8 @@ async function migrateClaudeMd(root, actions) {
15741
15982
  }
15742
15983
  }
15743
15984
  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");
15985
+ const gateMd = (0, import_node_path17.join)(root, "GATE.md");
15986
+ const verityMd = (0, import_node_path17.join)(root, "VERITY.md");
15746
15987
  if (!(0, import_node_fs23.existsSync)(gateMd) || (0, import_node_fs23.existsSync)(verityMd)) return;
15747
15988
  let moved = false;
15748
15989
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
@@ -15830,15 +16071,15 @@ function moveFile(from, to) {
15830
16071
  function carryLegacyContents(gateDir, verityDir) {
15831
16072
  let copied = 0;
15832
16073
  const walk = (relDir) => {
15833
- const srcDir = (0, import_node_path16.join)(gateDir, relDir);
16074
+ const srcDir = (0, import_node_path17.join)(gateDir, relDir);
15834
16075
  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);
16076
+ const rel = relDir ? (0, import_node_path17.join)(relDir, entry) : entry;
16077
+ const src = (0, import_node_path17.join)(gateDir, rel);
16078
+ const dest = (0, import_node_path17.join)(verityDir, rel);
15838
16079
  if ((0, import_node_fs23.statSync)(src).isDirectory()) {
15839
16080
  walk(rel);
15840
16081
  } else if (!(0, import_node_fs23.existsSync)(dest)) {
15841
- (0, import_node_fs23.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
16082
+ (0, import_node_fs23.mkdirSync)((0, import_node_path17.dirname)(dest), { recursive: true });
15842
16083
  (0, import_node_fs23.cpSync)(src, dest);
15843
16084
  copied++;
15844
16085
  }
@@ -15848,22 +16089,22 @@ function carryLegacyContents(gateDir, verityDir) {
15848
16089
  return copied;
15849
16090
  }
15850
16091
  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");
16092
+ const gateDir = (0, import_node_path17.join)(root, ".gate");
16093
+ const verityDir = (0, import_node_path17.join)(root, ".verity");
15853
16094
  if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) return true;
15854
16095
  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"))) {
16096
+ 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
16097
  return true;
15857
16098
  }
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"))) {
16099
+ 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
16100
  return true;
15860
16101
  }
15861
16102
  }
15862
- const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
16103
+ const claudeMd = (0, import_node_path17.join)(root, "CLAUDE.md");
15863
16104
  if ((0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15864
16105
  return true;
15865
16106
  }
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"))) {
16107
+ 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
16108
  return true;
15868
16109
  }
15869
16110
  if (await hasLegacyHooksAt(root)) return true;
@@ -15889,17 +16130,77 @@ function registerMigrateCommand(program2) {
15889
16130
  }
15890
16131
 
15891
16132
  // src/commands/init.ts
16133
+ async function promptYes(question) {
16134
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
16135
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
16136
+ try {
16137
+ const answer = (await rl.question(question)).trim().toLowerCase();
16138
+ return answer === "" || answer === "y" || answer === "yes";
16139
+ } finally {
16140
+ rl.close();
16141
+ }
16142
+ }
16143
+ async function runOptionalAuth() {
16144
+ const existing = await resolveToken();
16145
+ if (existing.ok) {
16146
+ printInfo("Already authenticated \u2014 results will upload to the Verity service. \u2713");
16147
+ return;
16148
+ }
16149
+ let remote = "";
16150
+ try {
16151
+ remote = (0, import_node_child_process9.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
16152
+ } catch {
16153
+ }
16154
+ const localOnlyNote = () => {
16155
+ printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
16156
+ printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
16157
+ };
16158
+ if (process.stdin.isTTY && process.stdout.isTTY) {
16159
+ console.log("");
16160
+ console.log(" Signing in is optional. What it does:");
16161
+ console.log(" - Confirms you have write access to this repository. The GitHub token");
16162
+ console.log(" is used once to verify that, then discarded \u2014 Verity never stores it.");
16163
+ console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
16164
+ console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
16165
+ console.log(" - It is required to store and access run history for this repo");
16166
+ console.log(" (past results, trends, and shareable reports).");
16167
+ console.log(" - Skip and Verity still works fully locally: the gate runs and shows");
16168
+ console.log(" findings, but nothing is uploaded.");
16169
+ console.log("");
16170
+ }
16171
+ const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
16172
+ if (!wantsAuth) {
16173
+ printInfo("Skipped authentication.");
16174
+ localOnlyNote();
16175
+ return;
16176
+ }
16177
+ if (!remote) {
16178
+ printWarn("No git remote found \u2014 cannot authenticate yet.");
16179
+ localOnlyNote();
16180
+ return;
16181
+ }
16182
+ const projectName = parseRemote(remote)?.repo ?? (0, import_node_path18.basename)(process.cwd());
16183
+ printInfo("Authenticating with GitHub\u2026");
16184
+ const result = await registerProject({ projectName, remote, serviceUrl: DEFAULT_SERVICE_URL });
16185
+ if (result.ok) {
16186
+ printInfo(`Project registered: ${result.data.projectId} \u2713`);
16187
+ if (result.data.email) printInfo(` Authenticated as: ${result.data.email}`);
16188
+ } else {
16189
+ printWarn(`Authentication did not complete: ${result.error}`);
16190
+ localOnlyNote();
16191
+ }
16192
+ }
15892
16193
  function resolveDataDir() {
15893
16194
  const candidates = [
15894
- (0, import_node_path17.join)(__dirname, "..", "data"),
16195
+ (0, import_node_path18.join)(__dirname, "..", "data"),
15895
16196
  // installed: node_modules/@codacy/verity-cli/data
15896
- (0, import_node_path17.join)(__dirname, "..", "..", "data"),
16197
+ (0, import_node_path18.join)(__dirname, "..", "..", "data"),
15897
16198
  // edge case: nested resolution
15898
- (0, import_node_path17.join)(process.cwd(), "cli", "data")
16199
+ (0, import_node_path18.join)(process.cwd(), "cli", "data")
15899
16200
  // local dev: running from repo root
15900
16201
  ];
15901
16202
  for (const candidate of candidates) {
15902
- if ((0, import_node_fs24.existsSync)((0, import_node_path17.join)(candidate, "skills"))) {
16203
+ if ((0, import_node_fs24.existsSync)((0, import_node_path18.join)(candidate, "skills"))) {
15903
16204
  return candidate;
15904
16205
  }
15905
16206
  }
@@ -15908,8 +16209,8 @@ function resolveDataDir() {
15908
16209
  );
15909
16210
  }
15910
16211
  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 });
16212
+ await (0, import_promises13.mkdir)(dest, { recursive: true });
16213
+ await (0, import_promises13.cp)(src, dest, { recursive: true, force: true });
15913
16214
  }
15914
16215
  function registerInitCommand(program2) {
15915
16216
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
@@ -15978,24 +16279,24 @@ function registerInitCommand(program2) {
15978
16279
  console.log("");
15979
16280
  printInfo("Installing skills...");
15980
16281
  const dataDir = resolveDataDir();
15981
- const skillsSource = (0, import_node_path17.join)(dataDir, "skills");
16282
+ const skillsSource = (0, import_node_path18.join)(dataDir, "skills");
15982
16283
  const skillsDest = ".claude/skills";
15983
16284
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
15984
16285
  let skillsInstalled = 0;
15985
16286
  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);
16287
+ const src = (0, import_node_path18.join)(skillsSource, skill);
16288
+ const dest = (0, import_node_path18.join)(skillsDest, skill);
15988
16289
  if (!(0, import_node_fs24.existsSync)(src)) {
15989
16290
  printWarn(` Skill data not found: ${skill}`);
15990
16291
  continue;
15991
16292
  }
15992
16293
  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");
16294
+ const srcSkill = (0, import_node_path18.join)(src, "SKILL.md");
16295
+ const destSkill = (0, import_node_path18.join)(dest, "SKILL.md");
15995
16296
  if ((0, import_node_fs24.existsSync)(destSkill)) {
15996
16297
  try {
15997
- const srcContent = await (0, import_promises12.readFile)(srcSkill, "utf-8");
15998
- const destContent = await (0, import_promises12.readFile)(destSkill, "utf-8");
16298
+ const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
16299
+ const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
15999
16300
  if (srcContent === destContent) {
16000
16301
  skillsInstalled++;
16001
16302
  continue;
@@ -16021,7 +16322,7 @@ function registerInitCommand(program2) {
16021
16322
  printWarn(` ${hookResult.error}`);
16022
16323
  printInfo(' Run "verity hooks install --force" to overwrite.');
16023
16324
  }
16024
- await (0, import_promises12.mkdir)(VERITY_DIR, { recursive: true });
16325
+ await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
16025
16326
  await ensureMemoryDir();
16026
16327
  try {
16027
16328
  await ensureClaudeMdPointer();
@@ -16029,8 +16330,14 @@ function registerInitCommand(program2) {
16029
16330
  } catch (err) {
16030
16331
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
16031
16332
  }
16032
- const globalVerityDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".verity");
16033
- await (0, import_promises12.mkdir)(globalVerityDir, { recursive: true });
16333
+ const globalVerityDir = (0, import_node_path18.join)(process.env.HOME ?? "", ".verity");
16334
+ await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
16335
+ console.log("");
16336
+ try {
16337
+ await runOptionalAuth();
16338
+ } catch (err) {
16339
+ printWarn(`Authentication step skipped: ${err.message}`);
16340
+ }
16034
16341
  console.log("");
16035
16342
  printInfo("Verity initialized!");
16036
16343
  console.log("");
@@ -16047,13 +16354,14 @@ function registerInitCommand(program2) {
16047
16354
  console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
16048
16355
  console.log("");
16049
16356
  console.log(" Next step: open this project in Claude Code and run /verity-setup");
16357
+ console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
16050
16358
  console.log("");
16051
16359
  });
16052
16360
  }
16053
16361
 
16054
16362
  // src/commands/uninstall.ts
16055
16363
  var import_node_fs25 = require("node:fs");
16056
- var import_node_path18 = require("node:path");
16364
+ var import_node_path19 = require("node:path");
16057
16365
  var SKILL_NAMES = [
16058
16366
  "verity-setup",
16059
16367
  "verity-analyze",
@@ -16072,7 +16380,7 @@ function registerUninstallCommand(program2) {
16072
16380
  const actions = [];
16073
16381
  const skillsRoot = projectPath(".claude/skills");
16074
16382
  for (const name of SKILL_NAMES) {
16075
- const dir = (0, import_node_path18.join)(skillsRoot, name);
16383
+ const dir = (0, import_node_path19.join)(skillsRoot, name);
16076
16384
  if ((0, import_node_fs25.existsSync)(dir)) {
16077
16385
  actions.push({
16078
16386
  label: `Remove .claude/skills/${name}/`,
@@ -16118,7 +16426,7 @@ function registerUninstallCommand(program2) {
16118
16426
  }
16119
16427
  });
16120
16428
  const home = process.env.HOME ?? "";
16121
- const globalVerityDir = (0, import_node_path18.join)(home, ".verity");
16429
+ const globalVerityDir = (0, import_node_path19.join)(home, ".verity");
16122
16430
  if (purgeGlobal && (0, import_node_fs25.existsSync)(globalVerityDir)) {
16123
16431
  actions.push({
16124
16432
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
@@ -16317,7 +16625,7 @@ function registerTaskCommands(program2) {
16317
16625
 
16318
16626
  // src/commands/reset.ts
16319
16627
  var import_node_fs26 = require("node:fs");
16320
- var import_node_path19 = require("node:path");
16628
+ var import_node_path20 = require("node:path");
16321
16629
  function registerResetCommand(program2) {
16322
16630
  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
16631
  const globals = program2.opts();
@@ -16358,7 +16666,7 @@ function registerResetCommand(program2) {
16358
16666
  for (const entry of (0, import_node_fs26.readdirSync)(cacheDir)) {
16359
16667
  if (entry.startsWith("pending-")) {
16360
16668
  try {
16361
- (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(cacheDir, entry));
16669
+ (0, import_node_fs26.unlinkSync)((0, import_node_path20.join)(cacheDir, entry));
16362
16670
  purged++;
16363
16671
  } catch {
16364
16672
  }
@@ -16385,7 +16693,7 @@ function registerResetCommand(program2) {
16385
16693
  if ((0, import_node_fs26.existsSync)(logsDir)) {
16386
16694
  for (const entry of (0, import_node_fs26.readdirSync)(logsDir)) {
16387
16695
  try {
16388
- (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(logsDir, entry));
16696
+ (0, import_node_fs26.unlinkSync)((0, import_node_path20.join)(logsDir, entry));
16389
16697
  } catch {
16390
16698
  }
16391
16699
  }
@@ -16643,8 +16951,7 @@ function registerRunCommand(program2) {
16643
16951
  }
16644
16952
 
16645
16953
  // src/lib/telemetry.ts
16646
- var import_promises13 = require("node:fs/promises");
16647
- var import_node_path20 = require("node:path");
16954
+ var import_promises14 = require("node:fs/promises");
16648
16955
  var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
16649
16956
  var GITIGNORE_FILE = ".gitignore";
16650
16957
  var GITIGNORE_ENTRY = ".claude/settings.local.json";
@@ -16675,21 +16982,19 @@ function buildTelemetryEnv(serviceUrl, token) {
16675
16982
  var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv("", ""));
16676
16983
  async function readSettingsLocal() {
16677
16984
  try {
16678
- return JSON.parse(await (0, import_promises13.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
16985
+ return JSON.parse(await (0, import_promises14.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
16679
16986
  } catch {
16680
16987
  return {};
16681
16988
  }
16682
16989
  }
16683
16990
  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");
16991
+ await writeJsonFilePreservingStyle(projectPath(SETTINGS_LOCAL_FILE2), settings);
16687
16992
  }
16688
16993
  async function ensureGitignore() {
16689
16994
  const file = projectPath(GITIGNORE_FILE);
16690
16995
  let content = "";
16691
16996
  try {
16692
- content = await (0, import_promises13.readFile)(file, "utf-8");
16997
+ content = await (0, import_promises14.readFile)(file, "utf-8");
16693
16998
  } catch {
16694
16999
  }
16695
17000
  const lines = content.split("\n").map((l) => l.trim());
@@ -16698,7 +17003,7 @@ async function ensureGitignore() {
16698
17003
  }
16699
17004
  const block = "# Verity telemetry \u2014 holds your project token\n" + GITIGNORE_ENTRY + "\n";
16700
17005
  const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
16701
- await (0, import_promises13.writeFile)(file, next);
17006
+ await (0, import_promises14.writeFile)(file, next);
16702
17007
  }
16703
17008
  async function installTelemetry(serviceUrl, token) {
16704
17009
  const env = buildTelemetryEnv(serviceUrl, token);
@@ -16778,7 +17083,7 @@ function registerTelemetryCommands(program2) {
16778
17083
  }
16779
17084
 
16780
17085
  // 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");
17086
+ program.name("verity").description("CLI for Verity quality gate service").version("0.26.0-experimental.d7dfc00").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16782
17087
  registerAuthCommands(program);
16783
17088
  registerHooksCommands(program);
16784
17089
  registerIntentCommands(program);