@codacy/verity-cli 0.24.0 → 0.25.0-experimental.56ef7d8

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");
@@ -10379,6 +10377,7 @@ var MAX_PLAN_FILES = 3;
10379
10377
  var MAX_PLAN_FILE_BYTES = 10240;
10380
10378
  var MAX_INTENT_CHARS = 2e3;
10381
10379
  var SNAPSHOT_DIR = `${VERITY_DIR}/.snapshot`;
10380
+ var BASELINE_DIR = `${VERITY_DIR}/.baseline`;
10382
10381
  var CONVERSATION_BUFFER_FILE = `${VERITY_DIR}/.conversation-buffer`;
10383
10382
  var CONVERSATION_MAX_ENTRIES = 10;
10384
10383
  var CONVERSATION_WINDOW_MINUTES = 15;
@@ -10473,7 +10472,12 @@ var SECURITY_PATTERNS = [
10473
10472
  /Cargo\.lock$/,
10474
10473
  /Dockerfile/
10475
10474
  ];
10476
- var DEFAULT_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10475
+ var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10476
+ var DEFAULT_SERVICE_URL = "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1".length > 0 ? "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1" : PROD_SERVICE_URL;
10477
+ var GITHUB_CLIENT_ID = "Ov23liBpj42KMTtUtN10";
10478
+ var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
10479
+ var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
10480
+ var GITHUB_OAUTH_SCOPES = "repo user:email";
10477
10481
 
10478
10482
  // src/lib/auth.ts
10479
10483
  async function resolveToken(flagToken) {
@@ -10639,7 +10643,8 @@ async function apiRequest(options) {
10639
10643
  timeout = 9e4,
10640
10644
  cmd = "unknown",
10641
10645
  retry = false,
10642
- encodeBody = false
10646
+ encodeBody = false,
10647
+ extraHeaders
10643
10648
  } = options;
10644
10649
  const url = `${serviceUrl}${path}`;
10645
10650
  const headers = {
@@ -10648,6 +10653,9 @@ async function apiRequest(options) {
10648
10653
  if (token) {
10649
10654
  headers["Authorization"] = `Bearer ${token}`;
10650
10655
  }
10656
+ if (extraHeaders) {
10657
+ Object.assign(headers, extraHeaders);
10658
+ }
10651
10659
  const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
10652
10660
  if (testMockScenario) {
10653
10661
  headers["X-Verity-Mock-Scenario"] = testMockScenario;
@@ -10738,6 +10746,388 @@ function analyzeRequest(options) {
10738
10746
  });
10739
10747
  }
10740
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
+
10741
11131
  // src/commands/auth.ts
10742
11132
  function registerAuthCommands(program2) {
10743
11133
  const auth = program2.command("auth").description("Manage project authentication");
@@ -10747,36 +11137,26 @@ function registerAuthCommands(program2) {
10747
11137
  let remote = opts.remote;
10748
11138
  if (!remote) {
10749
11139
  try {
10750
- 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();
10751
11141
  } catch {
10752
11142
  printError("No git remote found. Use --remote to specify one.");
10753
11143
  process.exit(1);
10754
11144
  }
10755
11145
  }
10756
- const result = await apiRequest({
10757
- method: "POST",
10758
- path: "/auth/register",
11146
+ const result = await registerProject({
11147
+ projectName: opts.project,
11148
+ remote,
10759
11149
  serviceUrl,
10760
- body: { project_name: opts.project, git_remote_url: remote },
10761
11150
  verbose: globals.verbose
10762
11151
  });
10763
11152
  if (!result.ok) {
10764
11153
  printError(result.error);
10765
11154
  process.exit(1);
10766
11155
  }
10767
- const { project_id, token, service_url } = result.data;
10768
- await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
10769
- await (0, import_promises3.writeFile)(CREDENTIALS_FILE, `token: ${token}
10770
- service_url: ${service_url}
10771
- `);
10772
- try {
10773
- await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
10774
- await (0, import_promises3.appendFile)(GLOBAL_CREDENTIALS_FILE, `${remote} token: ${token}
10775
- `);
10776
- } catch {
10777
- }
10778
- printInfo(`Project registered: ${project_id}`);
10779
- 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 });
10780
11160
  });
10781
11161
  auth.command("verify").description("Verify the current token is valid").action(async () => {
10782
11162
  const globals = program2.opts();
@@ -10809,7 +11189,7 @@ service_url: ${service_url}
10809
11189
  let remote = opts.remote;
10810
11190
  if (!remote) {
10811
11191
  try {
10812
- 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();
10813
11193
  } catch {
10814
11194
  printError("No git remote found. Use --remote to specify one.");
10815
11195
  process.exit(1);
@@ -10837,7 +11217,7 @@ service_url: ${service_url}
10837
11217
 
10838
11218
  // src/lib/hooks.ts
10839
11219
  var import_promises4 = require("node:fs/promises");
10840
- var import_node_path3 = require("node:path");
11220
+ var import_node_path4 = require("node:path");
10841
11221
  var VERITY_STOP_HOOK = {
10842
11222
  type: "command",
10843
11223
  command: "verity analyze",
@@ -10848,6 +11228,10 @@ var VERITY_INTENT_HOOK = {
10848
11228
  type: "command",
10849
11229
  command: "verity intent capture"
10850
11230
  };
11231
+ var VERITY_BASELINE_HOOK = {
11232
+ type: "command",
11233
+ command: "verity baseline capture"
11234
+ };
10851
11235
  var GUARD_TIMEOUT = 300;
10852
11236
  function buildGuardHook(on) {
10853
11237
  return {
@@ -10860,9 +11244,13 @@ function buildGuardHook(on) {
10860
11244
  var VERITY_STOP_RE = /(?:^|[\/\s"'])verity\s+analyze\b/;
10861
11245
  var VERITY_INTENT_RE = /(?:^|[\/\s"'])verity\s+intent\s+capture\b/;
10862
11246
  var VERITY_GUARD_RE = /(?:^|[\/\s"'])verity\s+guard\b/;
11247
+ var VERITY_BASELINE_RE = /(?:^|[\/\s"'])verity\s+baseline\s+capture\b/;
10863
11248
  function isVerityGuardHook(entry) {
10864
11249
  return VERITY_GUARD_RE.test(entry.command ?? "");
10865
11250
  }
11251
+ function isVerityBaselineHook(entry) {
11252
+ return VERITY_BASELINE_RE.test(entry.command ?? "");
11253
+ }
10866
11254
  var LEGACY_STOP_RE = /(?:^|[\/\s"'])gate\s+analyze\b/;
10867
11255
  var LEGACY_INTENT_RE = /(?:^|[\/\s"'])gate\s+intent\s+capture\b/;
10868
11256
  function isVerityStopHook(entry) {
@@ -10874,7 +11262,7 @@ function isVerityIntentHook(entry) {
10874
11262
  return VERITY_INTENT_RE.test(c) || LEGACY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh") || c.includes(".gate/hooks/capture-intent.sh");
10875
11263
  }
10876
11264
  function isVerityHook(entry) {
10877
- return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry);
11265
+ return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry);
10878
11266
  }
10879
11267
  function isCurrentVerityStopHook(entry) {
10880
11268
  const c = entry.command ?? "";
@@ -10885,7 +11273,7 @@ function isCurrentVerityIntentHook(entry) {
10885
11273
  return VERITY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh");
10886
11274
  }
10887
11275
  function isCurrentVerityHook(entry) {
10888
- return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry);
11276
+ return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry);
10889
11277
  }
10890
11278
  function settingsHasLegacyHook(settings) {
10891
11279
  for (const groups of Object.values(settings.hooks ?? {})) {
@@ -10927,16 +11315,18 @@ async function readAllSettings() {
10927
11315
  async function checkAllVerityHooks() {
10928
11316
  let stop = false;
10929
11317
  let intent = false;
11318
+ let baseline = false;
10930
11319
  let guard = false;
10931
11320
  let guardOn = [];
10932
11321
  for (const settings of await readAllSettings()) {
10933
11322
  const r = checkVerityHooks(settings);
10934
11323
  stop = stop || r.stop;
10935
11324
  intent = intent || r.intent;
11325
+ baseline = baseline || r.baseline;
10936
11326
  guard = guard || r.guard;
10937
11327
  if (r.guardOn.length > guardOn.length) guardOn = r.guardOn;
10938
11328
  }
10939
- return { stop, intent, guard, guardOn };
11329
+ return { stop, intent, baseline, guard, guardOn };
10940
11330
  }
10941
11331
  async function checkExternalVerityHooks() {
10942
11332
  let stop = false;
@@ -10959,12 +11349,14 @@ async function checkExternalVerityHooks() {
10959
11349
  async function checkAllVerityHooksDetailed() {
10960
11350
  let stop = false;
10961
11351
  let intent = false;
11352
+ let baseline = false;
10962
11353
  let hasCurrent = false;
10963
11354
  let hasLegacy = false;
10964
11355
  for (const settings of await readAllSettings()) {
10965
11356
  const r = checkVerityHooks(settings);
10966
11357
  stop = stop || r.stop;
10967
11358
  intent = intent || r.intent;
11359
+ baseline = baseline || r.baseline;
10968
11360
  for (const groups of Object.values(settings.hooks ?? {})) {
10969
11361
  for (const g of groups ?? []) {
10970
11362
  for (const h of g.hooks ?? []) {
@@ -10974,22 +11366,22 @@ async function checkAllVerityHooksDetailed() {
10974
11366
  }
10975
11367
  }
10976
11368
  }
10977
- return { stop, intent, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
11369
+ return { stop, intent, baseline, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
10978
11370
  }
10979
11371
  async function writeSettings(settings) {
10980
- await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
11372
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
10981
11373
  await (0, import_promises4.writeFile)(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
10982
11374
  }
10983
11375
  async function readSettingsAt(root) {
10984
11376
  try {
10985
- return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11377
+ return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
10986
11378
  } catch {
10987
11379
  return {};
10988
11380
  }
10989
11381
  }
10990
11382
  async function writeSettingsAt(root, settings) {
10991
- const file = (0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE);
10992
- await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(file), { recursive: true });
11383
+ const file = (0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE);
11384
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
10993
11385
  await (0, import_promises4.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
10994
11386
  }
10995
11387
  async function hasLegacyHooksAt(root) {
@@ -11017,6 +11409,10 @@ function checkVerityHooks(settings) {
11017
11409
  const hasIntent = intentGroups.some(
11018
11410
  (g) => g.hooks?.some((h) => isCurrentVerityIntentHook(h))
11019
11411
  );
11412
+ const sessionStartGroups = hooks["SessionStart"] ?? [];
11413
+ const hasBaseline = sessionStartGroups.some(
11414
+ (g) => g.hooks?.some((h) => isVerityBaselineHook(h))
11415
+ );
11020
11416
  let guard = false;
11021
11417
  let guardOn = [];
11022
11418
  for (const g of hooks["PreToolUse"] ?? []) {
@@ -11029,16 +11425,17 @@ function checkVerityHooks(settings) {
11029
11425
  }
11030
11426
  }
11031
11427
  }
11032
- return { stop: hasStop, intent: hasIntent, guard, guardOn };
11428
+ return { stop: hasStop, intent: hasIntent, baseline: hasBaseline, guard, guardOn };
11033
11429
  }
11034
- function installVerityHooks(settings, force, externalPresent = { stop: false, intent: false }) {
11430
+ function installVerityHooks(settings, force, externalPresent = { stop: false, intent: false, baseline: false }) {
11035
11431
  const local = checkVerityHooks(settings);
11036
11432
  const existing = {
11037
11433
  stop: local.stop || externalPresent.stop,
11038
- intent: local.intent || externalPresent.intent
11434
+ intent: local.intent || externalPresent.intent,
11435
+ baseline: local.baseline || (externalPresent.baseline ?? false)
11039
11436
  };
11040
11437
  const hasLocalLegacy = settingsHasLegacyHook(settings);
11041
- if (existing.stop && existing.intent && !force && !hasLocalLegacy) {
11438
+ if (existing.stop && existing.intent && existing.baseline && !force && !hasLocalLegacy) {
11042
11439
  return { ok: false, error: "Verity hooks already installed. Use --force to overwrite." };
11043
11440
  }
11044
11441
  const newSettings = { ...settings };
@@ -11046,7 +11443,7 @@ function installVerityHooks(settings, force, externalPresent = { stop: false, in
11046
11443
  newSettings.hooks = {};
11047
11444
  }
11048
11445
  const shouldStrip = force ? isVerityHook : isLegacyHook;
11049
- for (const key of ["Stop", "UserPromptSubmit"]) {
11446
+ for (const key of ["Stop", "UserPromptSubmit", "SessionStart"]) {
11050
11447
  const groups = newSettings.hooks[key];
11051
11448
  if (groups) {
11052
11449
  newSettings.hooks[key] = groups.map((g) => ({
@@ -11073,6 +11470,15 @@ function installVerityHooks(settings, force, externalPresent = { stop: false, in
11073
11470
  if (!newSettings.hooks["UserPromptSubmit"]) newSettings.hooks["UserPromptSubmit"] = [];
11074
11471
  newSettings.hooks["UserPromptSubmit"].push({ hooks: [VERITY_INTENT_HOOK] });
11075
11472
  }
11473
+ if (!existing.baseline) {
11474
+ if (!newSettings.hooks["SessionStart"]) {
11475
+ newSettings.hooks["SessionStart"] = [];
11476
+ }
11477
+ newSettings.hooks["SessionStart"].push({ hooks: [VERITY_BASELINE_HOOK] });
11478
+ } else if (force && local.baseline) {
11479
+ if (!newSettings.hooks["SessionStart"]) newSettings.hooks["SessionStart"] = [];
11480
+ newSettings.hooks["SessionStart"].push({ hooks: [VERITY_BASELINE_HOOK] });
11481
+ }
11076
11482
  return { ok: true, data: newSettings };
11077
11483
  }
11078
11484
  function removeVerityHooks(settings) {
@@ -11106,6 +11512,7 @@ function reconcileMomentHooks(settings, moments, externalPresent = {
11106
11512
  (s.hooks[event] ??= []).push(group);
11107
11513
  };
11108
11514
  if (!externalPresent.intent) push("UserPromptSubmit", { hooks: [VERITY_INTENT_HOOK] });
11515
+ push("SessionStart", { hooks: [VERITY_BASELINE_HOOK] });
11109
11516
  if (moments.includes("stop") && !externalPresent.stop) {
11110
11517
  push("Stop", { hooks: [VERITY_STOP_HOOK] });
11111
11518
  }
@@ -11154,7 +11561,7 @@ function registerHooksCommands(program2) {
11154
11561
  return;
11155
11562
  }
11156
11563
  const detail = await checkAllVerityHooksDetailed();
11157
- const present = { stop: detail.stop, intent: detail.intent };
11564
+ const present = { stop: detail.stop, intent: detail.intent, baseline: detail.baseline };
11158
11565
  if (detail.legacyOnly) {
11159
11566
  printInfo("Found a legacy GATE.md hook \u2014 upgrading it to Verity...");
11160
11567
  const settings2 = removeVerityHooks(await readSettings());
@@ -11167,11 +11574,12 @@ function registerHooksCommands(program2) {
11167
11574
  printInfo("Verity hooks installed in .claude/settings.json (legacy hook removed)");
11168
11575
  printInfo(" Stop hook: verity analyze");
11169
11576
  printInfo(" UserPromptSubmit hook: verity intent capture");
11577
+ printInfo(" SessionStart hook: verity baseline capture");
11170
11578
  return;
11171
11579
  }
11172
11580
  const settings = await readSettings();
11173
11581
  const hasLocalLegacy = settingsHasLegacyHook(settings);
11174
- if (!force && present.stop && present.intent && !hasLocalLegacy) {
11582
+ if (!force && present.stop && present.intent && present.baseline && !hasLocalLegacy) {
11175
11583
  printInfo("Verity hooks already installed (found in .claude/settings.json, settings.local.json, or your global settings).");
11176
11584
  printInfo("Use --force to rewrite the project settings.json copy.");
11177
11585
  return;
@@ -11185,11 +11593,13 @@ function registerHooksCommands(program2) {
11185
11593
  printInfo("Verity hooks installed in .claude/settings.json");
11186
11594
  printInfo(" Stop hook: verity analyze");
11187
11595
  printInfo(" UserPromptSubmit hook: verity intent capture");
11596
+ printInfo(" SessionStart hook: verity baseline capture");
11188
11597
  });
11189
11598
  hooks.command("check").description("Check if Verity hooks are installed").action(async () => {
11190
11599
  const status = await checkAllVerityHooks();
11191
11600
  printInfo(`Stop hook (verity analyze): ${status.stop ? "installed" : "not installed"}`);
11192
11601
  printInfo(`Intent hook (verity intent capture): ${status.intent ? "installed" : "not installed"}`);
11602
+ printInfo(`Baseline hook (verity baseline capture): ${status.baseline ? "installed" : "not installed"}`);
11193
11603
  const gates = status.guard ? status.guardOn.join(", ") : "none";
11194
11604
  printInfo(`Git-moment gate (verity guard): ${status.guard ? `installed [${gates}]` : "not installed"}`);
11195
11605
  if (!status.stop && !status.guard) {
@@ -11208,15 +11618,19 @@ function registerHooksCommands(program2) {
11208
11618
  }
11209
11619
 
11210
11620
  // src/commands/intent.ts
11211
- var import_node_crypto2 = require("node:crypto");
11621
+ var import_node_crypto3 = require("node:crypto");
11212
11622
 
11213
11623
  // src/lib/conversation-buffer.ts
11214
11624
  var import_promises5 = require("node:fs/promises");
11215
- var import_node_fs2 = require("node:fs");
11216
- var import_node_child_process4 = require("node:child_process");
11625
+ var import_node_fs3 = require("node:fs");
11626
+ var import_node_child_process5 = require("node:child_process");
11627
+ var import_node_crypto = require("node:crypto");
11217
11628
  function stripImageReferences(text) {
11218
11629
  return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
11219
11630
  }
11631
+ function bufferTmpPath() {
11632
+ return `${CONVERSATION_BUFFER_FILE}.${process.pid}.${(0, import_node_crypto.randomBytes)(4).toString("hex")}.tmp`;
11633
+ }
11220
11634
  async function appendToConversationBuffer(prompt, sessionId) {
11221
11635
  try {
11222
11636
  await (0, import_promises5.mkdir)(VERITY_DIR, { recursive: true });
@@ -11236,26 +11650,39 @@ async function appendToConversationBuffer(prompt, sessionId) {
11236
11650
  recent.push(entry);
11237
11651
  const capped = recent.slice(-CONVERSATION_MAX_ENTRIES);
11238
11652
  const content = capped.map((e) => JSON.stringify(e)).join("\n") + "\n";
11239
- const tmpFile = `${CONVERSATION_BUFFER_FILE}.tmp`;
11653
+ const tmpFile = bufferTmpPath();
11240
11654
  await (0, import_promises5.writeFile)(tmpFile, content);
11241
11655
  await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11242
11656
  } catch {
11243
11657
  }
11244
11658
  }
11245
- async function readAndClearConversationBuffer() {
11659
+ async function readAndClearConversationBuffer(currentSessionId) {
11246
11660
  try {
11247
- if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
11661
+ if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
11248
11662
  const entries = await readBufferEntries();
11249
- await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11250
- });
11251
- if (entries.length > 0) {
11663
+ let mine = entries;
11664
+ let others = [];
11665
+ if (currentSessionId) {
11666
+ mine = entries.filter((e) => e.session_id === currentSessionId || !e.session_id);
11667
+ others = entries.filter((e) => e.session_id && e.session_id !== currentSessionId);
11668
+ }
11669
+ if (others.length > 0) {
11670
+ const remaining = others.map((e) => JSON.stringify(e)).join("\n") + "\n";
11671
+ const tmpFile = bufferTmpPath();
11672
+ await (0, import_promises5.writeFile)(tmpFile, remaining);
11673
+ await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11674
+ } else {
11675
+ await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11676
+ });
11677
+ }
11678
+ if (mine.length > 0) {
11252
11679
  return {
11253
- prompts: entries,
11680
+ prompts: mine,
11254
11681
  recent_commits: getRecentCommitMessages()
11255
11682
  };
11256
11683
  }
11257
11684
  }
11258
- if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
11685
+ if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
11259
11686
  try {
11260
11687
  const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
11261
11688
  await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
@@ -11299,7 +11726,7 @@ async function readBufferEntries() {
11299
11726
  }
11300
11727
  function getRecentCommitMessages() {
11301
11728
  try {
11302
- const output = (0, import_node_child_process4.execSync)(
11729
+ const output = (0, import_node_child_process5.execSync)(
11303
11730
  'git log --since="30 minutes ago" --format="%s" -5',
11304
11731
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
11305
11732
  ).trim();
@@ -11312,8 +11739,8 @@ function getRecentCommitMessages() {
11312
11739
 
11313
11740
  // src/lib/task-context-buffer.ts
11314
11741
  var import_promises6 = require("node:fs/promises");
11315
- var import_node_fs3 = require("node:fs");
11316
- var import_node_path4 = require("node:path");
11742
+ var import_node_fs4 = require("node:fs");
11743
+ var import_node_path5 = require("node:path");
11317
11744
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
11318
11745
  var MAX_BUFFER_BYTES = 500 * 1024;
11319
11746
  var MAX_PROMPT_CHARS = 2e3;
@@ -11352,7 +11779,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
11352
11779
  }
11353
11780
  async function readTaskContextBuffer(taskId) {
11354
11781
  const filePath = bufferPath(taskId);
11355
- if (!(0, import_node_fs3.existsSync)(filePath)) return null;
11782
+ if (!(0, import_node_fs4.existsSync)(filePath)) return null;
11356
11783
  try {
11357
11784
  const content = await (0, import_promises6.readFile)(filePath, "utf-8");
11358
11785
  if (!content.trim()) return null;
@@ -11386,12 +11813,12 @@ async function readTaskContextBuffer(taskId) {
11386
11813
  }
11387
11814
  async function cleanupTaskContextBuffers() {
11388
11815
  try {
11389
- if (!(0, import_node_fs3.existsSync)(TASK_CONTEXT_DIR)) return;
11816
+ if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
11390
11817
  const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
11391
11818
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
11392
11819
  for (const file of files) {
11393
11820
  if (!file.endsWith(".jsonl")) continue;
11394
- const filePath = (0, import_node_path4.join)(TASK_CONTEXT_DIR, file);
11821
+ const filePath = (0, import_node_path5.join)(TASK_CONTEXT_DIR, file);
11395
11822
  try {
11396
11823
  const stats = await (0, import_promises6.stat)(filePath);
11397
11824
  if (stats.mtimeMs < cutoffMs) {
@@ -11405,13 +11832,13 @@ async function cleanupTaskContextBuffers() {
11405
11832
  }
11406
11833
  function bufferPath(taskId) {
11407
11834
  const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
11408
- return (0, import_node_path4.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11835
+ return (0, import_node_path5.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11409
11836
  }
11410
11837
  async function appendEntry(taskId, entry) {
11411
11838
  try {
11412
11839
  await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
11413
11840
  const filePath = bufferPath(taskId);
11414
- if ((0, import_node_fs3.existsSync)(filePath)) {
11841
+ if ((0, import_node_fs4.existsSync)(filePath)) {
11415
11842
  const stats = await (0, import_promises6.stat)(filePath);
11416
11843
  if (stats.size >= MAX_BUFFER_BYTES) {
11417
11844
  const content = await (0, import_promises6.readFile)(filePath, "utf-8");
@@ -11422,7 +11849,7 @@ async function appendEntry(taskId, entry) {
11422
11849
  }
11423
11850
  }
11424
11851
  const line = JSON.stringify(entry) + "\n";
11425
- const existing = (0, import_node_fs3.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
11852
+ const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
11426
11853
  await (0, import_promises6.writeFile)(filePath, existing + line);
11427
11854
  } catch {
11428
11855
  }
@@ -11430,8 +11857,8 @@ async function appendEntry(taskId, entry) {
11430
11857
 
11431
11858
  // src/lib/memory-retrieval.ts
11432
11859
  var import_promises7 = require("node:fs/promises");
11433
- var import_node_fs4 = require("node:fs");
11434
- var import_node_path5 = require("node:path");
11860
+ var import_node_fs5 = require("node:fs");
11861
+ var import_node_path6 = require("node:path");
11435
11862
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
11436
11863
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
11437
11864
  var DEFAULT_BUDGET_TOKENS = 2e3;
@@ -11524,19 +11951,19 @@ function parseFrontmatter(content) {
11524
11951
  return { fm, body: match[2].trim() };
11525
11952
  }
11526
11953
  async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
11527
- if (!(0, import_node_fs4.existsSync)(memoryDir())) return null;
11954
+ if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
11528
11955
  const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
11529
11956
  const promptTokens = tokenize(promptText);
11530
11957
  const nodes = [];
11531
11958
  for (const domain of DOMAINS) {
11532
- const domainDir = (0, import_node_path5.join)(memoryDir(), domain);
11533
- if (!(0, import_node_fs4.existsSync)(domainDir)) continue;
11959
+ const domainDir = (0, import_node_path6.join)(memoryDir(), domain);
11960
+ if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
11534
11961
  try {
11535
11962
  const files = await (0, import_promises7.readdir)(domainDir);
11536
11963
  for (const file of files) {
11537
11964
  if (!file.endsWith(".md")) continue;
11538
11965
  try {
11539
- const content = await (0, import_promises7.readFile)((0, import_node_path5.join)(domainDir, file), "utf-8");
11966
+ const content = await (0, import_promises7.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
11540
11967
  const { fm, body } = parseFrontmatter(content);
11541
11968
  if (fm.status && fm.status !== "active") continue;
11542
11969
  nodes.push({
@@ -11595,9 +12022,9 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
11595
12022
 
11596
12023
  // src/lib/memory-sync.ts
11597
12024
  var import_promises8 = require("node:fs/promises");
11598
- var import_node_fs5 = require("node:fs");
11599
- var import_node_path6 = require("node:path");
11600
- var import_node_crypto = require("node:crypto");
12025
+ var import_node_fs6 = require("node:fs");
12026
+ var import_node_path7 = require("node:path");
12027
+ var import_node_crypto2 = require("node:crypto");
11601
12028
 
11602
12029
  // src/lib/glob-match.ts
11603
12030
  function globToRegex(glob) {
@@ -11667,35 +12094,35 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
11667
12094
  async function ensureMemoryDir() {
11668
12095
  await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
11669
12096
  for (const domain of DOMAINS2) {
11670
- await (0, import_promises8.mkdir)((0, import_node_path6.join)(memoryDir2(), domain), { recursive: true });
12097
+ await (0, import_promises8.mkdir)((0, import_node_path7.join)(memoryDir2(), domain), { recursive: true });
11671
12098
  }
11672
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"))) {
11673
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12099
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"))) {
12100
+ await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
11674
12101
  }
11675
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "index.md"))) {
11676
- 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");
12102
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "index.md"))) {
12103
+ await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
11677
12104
  }
11678
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "log.md"))) {
11679
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12105
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md"))) {
12106
+ await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
11680
12107
  }
11681
12108
  }
11682
12109
  async function buildManifest() {
11683
- if (!(0, import_node_fs5.existsSync)(memoryDir2())) {
12110
+ if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
11684
12111
  return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
11685
12112
  }
11686
12113
  const nodes = [];
11687
12114
  for (const domain of DOMAINS2) {
11688
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11689
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12115
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
12116
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11690
12117
  try {
11691
12118
  const files = await (0, import_promises8.readdir)(domainDir);
11692
12119
  for (const file of files) {
11693
12120
  if (!file.endsWith(".md")) continue;
11694
12121
  const filePath = `${domain}/${file}`;
11695
- const fullPath = (0, import_node_path6.join)(memoryDir2(), filePath);
12122
+ const fullPath = (0, import_node_path7.join)(memoryDir2(), filePath);
11696
12123
  try {
11697
12124
  const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
11698
- const hash = (0, import_node_crypto.createHash)("sha256").update(content).digest("hex").slice(0, 16);
12125
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
11699
12126
  nodes.push({ path: filePath, content_hash: `sha256:${hash}` });
11700
12127
  } catch {
11701
12128
  }
@@ -11705,32 +12132,32 @@ async function buildManifest() {
11705
12132
  }
11706
12133
  let indexHash = null;
11707
12134
  try {
11708
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "index.md"), "utf-8");
11709
- indexHash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
12135
+ const indexContent = await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "utf-8");
12136
+ indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
11710
12137
  } catch {
11711
12138
  }
11712
12139
  let logLength = 0;
11713
12140
  try {
11714
- const logContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "utf-8");
12141
+ const logContent = await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8");
11715
12142
  logLength = logContent.split("\n").length;
11716
12143
  } catch {
11717
12144
  }
11718
12145
  return { schema_version: 1, nodes, index_hash: indexHash, log_length: logLength };
11719
12146
  }
11720
12147
  function hashContent(content) {
11721
- return `sha256:${(0, import_node_crypto.createHash)("sha256").update(content).digest("hex").slice(0, 16)}`;
12148
+ return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16)}`;
11722
12149
  }
11723
12150
  async function readOnDiskNodes() {
11724
12151
  const out = /* @__PURE__ */ new Map();
11725
- if (!(0, import_node_fs5.existsSync)(memoryDir2())) return out;
12152
+ if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
11726
12153
  for (const domain of DOMAINS2) {
11727
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11728
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12154
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
12155
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11729
12156
  try {
11730
12157
  for (const file of await (0, import_promises8.readdir)(domainDir)) {
11731
12158
  if (!file.endsWith(".md")) continue;
11732
12159
  try {
11733
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8")));
12160
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8")));
11734
12161
  } catch {
11735
12162
  }
11736
12163
  }
@@ -11776,8 +12203,8 @@ async function computeEditedNodeUploads() {
11776
12203
  const uploads = [];
11777
12204
  for (const [path, prevHash] of prev) {
11778
12205
  if (prevHash == null) continue;
11779
- const full = (0, import_node_path6.join)(memoryDir2(), path);
11780
- if (!(0, import_node_fs5.existsSync)(full)) continue;
12206
+ const full = (0, import_node_path7.join)(memoryDir2(), path);
12207
+ if (!(0, import_node_fs6.existsSync)(full)) continue;
11781
12208
  let content;
11782
12209
  try {
11783
12210
  content = await (0, import_promises8.readFile)(full, "utf-8");
@@ -11813,15 +12240,15 @@ async function applyMemoryWrites(writes, opts = {}) {
11813
12240
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
11814
12241
  for (const n of notes) logLines.push(` - ${n}`);
11815
12242
  try {
11816
- 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";
11817
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
12243
+ const existing = (0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
12244
+ await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
11818
12245
  } catch {
11819
12246
  }
11820
12247
  await recordSyncedNodePaths();
11821
12248
  return count;
11822
12249
  }
11823
12250
  async function applyOneWrite(write, treePaths) {
11824
- const fullPath = (0, import_node_path6.join)(memoryDir2(), write.path);
12251
+ const fullPath = (0, import_node_path7.join)(memoryDir2(), write.path);
11825
12252
  const notes = [];
11826
12253
  let content = write.content;
11827
12254
  if (treePaths && treePaths.length > 0) {
@@ -11831,7 +12258,7 @@ async function applyOneWrite(write, treePaths) {
11831
12258
  notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
11832
12259
  }
11833
12260
  }
11834
- if ((0, import_node_fs5.existsSync)(fullPath)) {
12261
+ if ((0, import_node_fs6.existsSync)(fullPath)) {
11835
12262
  let existing = "";
11836
12263
  try {
11837
12264
  existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
@@ -11843,7 +12270,7 @@ async function applyOneWrite(write, treePaths) {
11843
12270
  return { written: false, notes };
11844
12271
  }
11845
12272
  }
11846
- await (0, import_promises8.mkdir)((0, import_node_path6.dirname)(fullPath), { recursive: true });
12273
+ await (0, import_promises8.mkdir)((0, import_node_path7.dirname)(fullPath), { recursive: true });
11847
12274
  await (0, import_promises8.writeFile)(fullPath, content);
11848
12275
  return { written: true, notes };
11849
12276
  }
@@ -11884,8 +12311,8 @@ async function regenerateIndex() {
11884
12311
  ];
11885
12312
  let totalNodes = 0;
11886
12313
  for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
11887
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11888
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12314
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
12315
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11889
12316
  try {
11890
12317
  const files = await (0, import_promises8.readdir)(domainDir);
11891
12318
  const mdFiles = files.filter((f) => f.endsWith(".md"));
@@ -11895,7 +12322,7 @@ async function regenerateIndex() {
11895
12322
  for (const file of mdFiles.sort()) {
11896
12323
  const slug = file.replace(/\.md$/, "");
11897
12324
  try {
11898
- const content = await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
12325
+ const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
11899
12326
  const title = pickFrontmatter(content, "title") ?? slug;
11900
12327
  const kind = pickFrontmatter(content, "kind") ?? "-";
11901
12328
  const confidence = pickFrontmatter(content, "confidence");
@@ -11919,7 +12346,7 @@ async function regenerateIndex() {
11919
12346
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
11920
12347
  }
11921
12348
  const next = lines.join("\n") + "\n";
11922
- const indexPath = (0, import_node_path6.join)(memoryDir2(), "index.md");
12349
+ const indexPath = (0, import_node_path7.join)(memoryDir2(), "index.md");
11923
12350
  let existing = null;
11924
12351
  try {
11925
12352
  existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
@@ -12001,9 +12428,9 @@ function hasLegacyMemoryBlock(text) {
12001
12428
  return findMarker(text, LEGACY_MD_START) !== -1;
12002
12429
  }
12003
12430
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
12004
- const claudeMdPath = (0, import_node_path6.join)(cwd, "CLAUDE.md");
12431
+ const claudeMdPath = (0, import_node_path7.join)(cwd, "CLAUDE.md");
12005
12432
  let existing = "";
12006
- if ((0, import_node_fs5.existsSync)(claudeMdPath)) {
12433
+ if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
12007
12434
  existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
12008
12435
  }
12009
12436
  let startTag = CLAUDE_MD_START;
@@ -12133,7 +12560,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
12133
12560
  `;
12134
12561
 
12135
12562
  // src/commands/intent.ts
12136
- var import_node_fs6 = require("node:fs");
12563
+ var import_node_fs7 = require("node:fs");
12137
12564
  function registerIntentCommands(program2) {
12138
12565
  const intent = program2.command("intent").description("Manage intent capture");
12139
12566
  intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
@@ -12142,7 +12569,7 @@ function registerIntentCommands(program2) {
12142
12569
  process.chdir(repoRoot());
12143
12570
  } catch {
12144
12571
  }
12145
- if (!(0, import_node_fs6.existsSync)(VERITY_DIR)) {
12572
+ if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
12146
12573
  process.exit(0);
12147
12574
  }
12148
12575
  const chunks = [];
@@ -12205,7 +12632,7 @@ async function fireClassify(prompt, sessionId) {
12205
12632
  logEvent("classify_skipped", { reason: "no_service_url", detail: urlResult.error });
12206
12633
  return;
12207
12634
  }
12208
- const promptHash = (0, import_node_crypto2.createHash)("sha256").update(prompt).digest("hex");
12635
+ const promptHash = (0, import_node_crypto3.createHash)("sha256").update(prompt).digest("hex");
12209
12636
  const result = await apiRequest({
12210
12637
  method: "POST",
12211
12638
  path: "/classify-task",
@@ -12673,243 +13100,54 @@ function registerFeedbackCommand(program2) {
12673
13100
  if (!result.ok) {
12674
13101
  printError(`Couldn't submit finding feedback: ${result.error}`);
12675
13102
  process.exit(1);
12676
- }
12677
- const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
12678
- printInfo(status);
12679
- });
12680
- feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
12681
- if (!message) return;
12682
- const globals = program2.opts();
12683
- await sendGeneralFeedback(message, opts, globals);
12684
- });
12685
- }
12686
- async function sendGeneralFeedback(message, opts, globals) {
12687
- const tokenResult = await resolveToken(globals.token);
12688
- if (!tokenResult.ok) {
12689
- printError(tokenResult.error);
12690
- printInfo(`Your message: ${message}`);
12691
- process.exit(1);
12692
- }
12693
- const urlResult = await resolveServiceUrl(globals.serviceUrl);
12694
- if (!urlResult.ok) {
12695
- printError(urlResult.error);
12696
- printInfo(`Your message: ${message}`);
12697
- process.exit(1);
12698
- }
12699
- const body = { message };
12700
- const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
12701
- const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
12702
- if (sessionId) body.session_id = sessionId;
12703
- if (model) body.agent_model = model;
12704
- const result = await apiRequest({
12705
- method: "POST",
12706
- path: "/feedback",
12707
- serviceUrl: urlResult.data,
12708
- token: tokenResult.data.token,
12709
- body,
12710
- verbose: globals.verbose
12711
- });
12712
- if (!result.ok) {
12713
- printError(`Couldn't send feedback: ${result.error}`);
12714
- printInfo(`Your message: ${message}`);
12715
- process.exit(1);
12716
- }
12717
- printInfo("Thanks, feedback sent!");
12718
- }
12719
-
12720
- // src/commands/analyze.ts
12721
- var import_node_fs18 = require("node:fs");
12722
- var import_node_path13 = require("node:path");
12723
-
12724
- // src/lib/git.ts
12725
- var import_node_child_process5 = require("node:child_process");
12726
- var import_node_fs7 = require("node:fs");
12727
- var import_node_path7 = require("node:path");
12728
- function resolveFile(relpath) {
12729
- if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
12730
- if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
12731
- try {
12732
- const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
12733
- for (const entry of entries) {
12734
- if (!entry.isDirectory()) continue;
12735
- const candidate = (0, import_node_path7.join)(".claude/worktrees", entry.name, relpath);
12736
- if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
12737
- }
12738
- } catch {
12739
- }
12740
- }
12741
- return null;
12742
- }
12743
- function execGit(cmd) {
12744
- try {
12745
- return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
12746
- } catch {
12747
- return "";
12748
- }
12749
- }
12750
- function splitLines(s) {
12751
- return s.split("\n").filter((l) => l.length > 0);
12752
- }
12753
- var SHA_RE = /^[0-9a-f]{40}$/;
12754
- function readBaselineSha() {
12755
- if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
12756
- let sha;
12757
- try {
12758
- sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
12759
- } catch {
12760
- return null;
12761
- }
12762
- if (!SHA_RE.test(sha)) return null;
12763
- const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
12764
- if (!reachable) {
12765
- try {
12766
- (0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
12767
- } catch {
12768
- }
12769
- return null;
12770
- }
12771
- return sha;
12772
- }
12773
- function writeBaselineSha(sha) {
12774
- if (!SHA_RE.test(sha)) return;
12775
- try {
12776
- (0, import_node_fs7.mkdirSync)((0, import_node_path7.dirname)(BASELINE_SHA_FILE), { recursive: true });
12777
- (0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
12778
- } catch {
12779
- }
12780
- }
12781
- function getChangedFiles() {
12782
- const sets = /* @__PURE__ */ new Set();
12783
- let hasRecentCommitFiles = false;
12784
- for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
12785
- for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
12786
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
12787
- const baseline = readBaselineSha();
12788
- if (baseline) {
12789
- const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
12790
- if (committed.length > 0) {
12791
- hasRecentCommitFiles = true;
12792
- for (const f of committed) sets.add(f);
12793
- }
12794
- } else {
12795
- const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
12796
- const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
12797
- const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
12798
- const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
12799
- if (commitAge < 120 && !hasUnstaged && !hasStaged) {
12800
- const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
12801
- if (recentFiles.length > 0) {
12802
- hasRecentCommitFiles = true;
12803
- for (const f of recentFiles) sets.add(f);
12804
- }
12805
- }
12806
- }
12807
- for (const f of getWorktreeFiles()) sets.add(f);
12808
- const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12809
- return { files: filtered, hasRecentCommitFiles };
12810
- }
12811
- function getStagedFiles() {
12812
- return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12813
- }
12814
- function getPushRangeFiles() {
12815
- const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12816
- const resolvers = [
12817
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
12818
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
12819
- () => {
12820
- const branch = execGit("git rev-parse --abbrev-ref HEAD");
12821
- return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
12822
- }
12823
- ];
12824
- for (const resolve of resolvers) {
12825
- const range = resolve();
12826
- if (range) return { files: diff(range), range };
12827
- }
12828
- const baseline = readBaselineSha();
12829
- if (baseline) {
12830
- const files = diff(`${baseline}..HEAD`);
12831
- if (files.length > 0) return { files, range: `${baseline}..HEAD` };
12832
- }
12833
- const last = diff("HEAD~1..HEAD");
12834
- return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
12835
- }
12836
- function getPushRangeMessages() {
12837
- const { range } = getPushRangeFiles();
12838
- if (!range) return "";
12839
- return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
12840
- }
12841
- function getWorktreeFiles() {
12842
- const result = [];
12843
- const worktreeDir = ".claude/worktrees";
12844
- if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
12845
- try {
12846
- const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
12847
- const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
12848
- for (const entry of entries) {
12849
- if (!entry.isDirectory()) continue;
12850
- const wtDir = (0, import_node_path7.join)(worktreeDir, entry.name);
12851
- scanDir(wtDir, wtDir, fiveMinAgo, result);
12852
- }
12853
- } catch {
12854
- }
12855
- return result;
12856
- }
12857
- function scanDir(baseDir, dir, minMtime, result) {
12858
- try {
12859
- const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
12860
- for (const entry of entries) {
12861
- const fullPath = (0, import_node_path7.join)(dir, entry.name);
12862
- if (entry.isDirectory()) {
12863
- if (entry.name === "node_modules" || entry.name === ".git") continue;
12864
- scanDir(baseDir, fullPath, minMtime, result);
12865
- } else if (entry.isFile()) {
12866
- const ext = (0, import_node_path7.extname)(entry.name).slice(1);
12867
- if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
12868
- try {
12869
- const stat3 = (0, import_node_fs7.statSync)(fullPath);
12870
- if (stat3.mtimeMs >= minMtime) {
12871
- const relPath = fullPath.slice(baseDir.length + 1);
12872
- result.push(relPath);
12873
- }
12874
- } catch {
12875
- }
12876
- }
12877
- }
12878
- } catch {
12879
- }
12880
- }
12881
- function filterAnalyzable(files) {
12882
- return files.filter((f) => {
12883
- const ext = (0, import_node_path7.extname)(f).slice(1);
12884
- return ANALYZABLE_EXTENSIONS.has(ext);
13103
+ }
13104
+ const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
13105
+ printInfo(status);
12885
13106
  });
12886
- }
12887
- function filterReviewable(files) {
12888
- return files.filter((f) => {
12889
- const ext = (0, import_node_path7.extname)(f).slice(1);
12890
- if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
12891
- if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
12892
- const basename2 = f.split("/").pop() ?? "";
12893
- if (REVIEWABLE_FILENAMES.has(basename2)) return true;
12894
- if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
12895
- return false;
13107
+ feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
13108
+ if (!message) return;
13109
+ const globals = program2.opts();
13110
+ await sendGeneralFeedback(message, opts, globals);
12896
13111
  });
12897
13112
  }
12898
- function filterSecurity(files) {
12899
- return files.filter(
12900
- (f) => SECURITY_PATTERNS.some((p) => p.test(f))
12901
- );
12902
- }
12903
- function getCurrentCommit() {
12904
- return execGit("git rev-parse HEAD") || "no-git";
12905
- }
12906
- function listTrackedFiles() {
12907
- const set = /* @__PURE__ */ new Set();
12908
- for (const f of splitLines(execGit("git ls-files"))) set.add(f);
12909
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
12910
- return Array.from(set);
13113
+ async function sendGeneralFeedback(message, opts, globals) {
13114
+ const tokenResult = await resolveToken(globals.token);
13115
+ if (!tokenResult.ok) {
13116
+ printError(tokenResult.error);
13117
+ printInfo(`Your message: ${message}`);
13118
+ process.exit(1);
13119
+ }
13120
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
13121
+ if (!urlResult.ok) {
13122
+ printError(urlResult.error);
13123
+ printInfo(`Your message: ${message}`);
13124
+ process.exit(1);
13125
+ }
13126
+ const body = { message };
13127
+ const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
13128
+ const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
13129
+ if (sessionId) body.session_id = sessionId;
13130
+ if (model) body.agent_model = model;
13131
+ const result = await apiRequest({
13132
+ method: "POST",
13133
+ path: "/feedback",
13134
+ serviceUrl: urlResult.data,
13135
+ token: tokenResult.data.token,
13136
+ body,
13137
+ verbose: globals.verbose
13138
+ });
13139
+ if (!result.ok) {
13140
+ printError(`Couldn't send feedback: ${result.error}`);
13141
+ printInfo(`Your message: ${message}`);
13142
+ process.exit(1);
13143
+ }
13144
+ printInfo("Thanks, feedback sent!");
12911
13145
  }
12912
13146
 
13147
+ // src/commands/analyze.ts
13148
+ var import_node_fs19 = require("node:fs");
13149
+ var import_node_path14 = require("node:path");
13150
+
12913
13151
  // src/lib/files.ts
12914
13152
  var import_node_fs8 = require("node:fs");
12915
13153
  var import_node_path8 = require("node:path");
@@ -13042,11 +13280,16 @@ function collectCodeDelta(files, opts) {
13042
13280
 
13043
13281
  // src/lib/debounce.ts
13044
13282
  var import_node_fs9 = require("node:fs");
13045
- var import_node_crypto3 = require("node:crypto");
13046
- function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
13047
- if (!(0, import_node_fs9.existsSync)(DEBOUNCE_FILE)) return null;
13283
+ var import_node_crypto4 = require("node:crypto");
13284
+ function scopedFile(base, sessionId) {
13285
+ if (!sessionId) return base;
13286
+ return `${base}.${(0, import_node_crypto4.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
13287
+ }
13288
+ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
13289
+ const file = scopedFile(DEBOUNCE_FILE, sessionId);
13290
+ if (!(0, import_node_fs9.existsSync)(file)) return null;
13048
13291
  try {
13049
- const lastTs = parseInt((0, import_node_fs9.readFileSync)(DEBOUNCE_FILE, "utf-8").trim(), 10);
13292
+ const lastTs = parseInt((0, import_node_fs9.readFileSync)(file, "utf-8").trim(), 10);
13050
13293
  const nowTs = Math.floor(Date.now() / 1e3);
13051
13294
  const elapsed = nowTs - lastTs;
13052
13295
  if (elapsed < debounceSeconds) {
@@ -13056,12 +13299,13 @@ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
13056
13299
  }
13057
13300
  return null;
13058
13301
  }
13059
- function checkMtime(files, bypassForRecentCommits) {
13302
+ function checkMtime(files, bypassForRecentCommits, sessionId) {
13060
13303
  if (bypassForRecentCommits) return null;
13061
- if (!(0, import_node_fs9.existsSync)(DEBOUNCE_FILE)) return null;
13304
+ const file = scopedFile(DEBOUNCE_FILE, sessionId);
13305
+ if (!(0, import_node_fs9.existsSync)(file)) return null;
13062
13306
  let debounceTime;
13063
13307
  try {
13064
- debounceTime = (0, import_node_fs9.statSync)(DEBOUNCE_FILE).mtimeMs;
13308
+ debounceTime = (0, import_node_fs9.statSync)(file).mtimeMs;
13065
13309
  } catch {
13066
13310
  return null;
13067
13311
  }
@@ -13080,7 +13324,7 @@ function checkMtime(files, bypassForRecentCommits) {
13080
13324
  return "No files modified since last analysis";
13081
13325
  }
13082
13326
  function computeContentHash(files) {
13083
- const hash = (0, import_node_crypto3.createHash)("sha1");
13327
+ const hash = (0, import_node_crypto4.createHash)("sha1");
13084
13328
  const sorted = [...files].sort();
13085
13329
  for (const f of sorted) {
13086
13330
  const resolved = resolveFile(f) ?? f;
@@ -13093,11 +13337,12 @@ function computeContentHash(files) {
13093
13337
  }
13094
13338
  return hash.digest("hex");
13095
13339
  }
13096
- function checkContentHash(files) {
13340
+ function checkContentHash(files, sessionId) {
13097
13341
  const hash = computeContentHash(files);
13098
- if ((0, import_node_fs9.existsSync)(HASH_FILE)) {
13342
+ const file = scopedFile(HASH_FILE, sessionId);
13343
+ if ((0, import_node_fs9.existsSync)(file)) {
13099
13344
  try {
13100
- const storedHash = (0, import_node_fs9.readFileSync)(HASH_FILE, "utf-8").trim();
13345
+ const storedHash = (0, import_node_fs9.readFileSync)(file, "utf-8").trim();
13101
13346
  if (hash === storedHash) {
13102
13347
  return { skip: "No source changes since last analysis", hash };
13103
13348
  }
@@ -13106,18 +13351,19 @@ function checkContentHash(files) {
13106
13351
  }
13107
13352
  return { skip: null, hash };
13108
13353
  }
13109
- function recordAnalysisStart() {
13354
+ function recordAnalysisStart(sessionId) {
13110
13355
  (0, import_node_fs9.mkdirSync)(VERITY_DIR, { recursive: true });
13111
- (0, import_node_fs9.writeFileSync)(DEBOUNCE_FILE, String(Math.floor(Date.now() / 1e3)));
13356
+ (0, import_node_fs9.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
13112
13357
  }
13113
- function recordPassHash(hash) {
13114
- (0, import_node_fs9.writeFileSync)(HASH_FILE, hash);
13358
+ function recordPassHash(hash, sessionId) {
13359
+ (0, import_node_fs9.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
13115
13360
  }
13116
- function narrowToRecent(files) {
13117
- if (!(0, import_node_fs9.existsSync)(DEBOUNCE_FILE)) return files;
13361
+ function narrowToRecent(files, sessionId) {
13362
+ const file = scopedFile(DEBOUNCE_FILE, sessionId);
13363
+ if (!(0, import_node_fs9.existsSync)(file)) return files;
13118
13364
  let debounceTime;
13119
13365
  try {
13120
- debounceTime = (0, import_node_fs9.statSync)(DEBOUNCE_FILE).mtimeMs;
13366
+ debounceTime = (0, import_node_fs9.statSync)(file).mtimeMs;
13121
13367
  } catch {
13122
13368
  return files;
13123
13369
  }
@@ -13463,15 +13709,207 @@ function cleanStaleSnapshots(dir, keepSet) {
13463
13709
  }
13464
13710
  }
13465
13711
 
13466
- // src/lib/offline.ts
13712
+ // src/lib/baseline.ts
13467
13713
  var import_node_fs13 = require("node:fs");
13468
- var import_node_crypto4 = require("node:crypto");
13714
+ var import_node_path11 = require("node:path");
13715
+ var import_node_crypto5 = require("node:crypto");
13716
+ var BASELINE_VERSION = 1;
13717
+ var BASELINE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
13718
+ var MIRROR_MAX_BYTES = 2 * 1024 * 1024;
13719
+ var DEFAULT_SESSION_KEY = "_default";
13720
+ function sessionKey(sessionId) {
13721
+ if (!sessionId) return DEFAULT_SESSION_KEY;
13722
+ return (0, import_node_crypto5.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
13723
+ }
13724
+ function sessionDir(key) {
13725
+ return (0, import_node_path11.join)(projectPath(BASELINE_DIR), key);
13726
+ }
13727
+ function manifestPath(dir) {
13728
+ return (0, import_node_path11.join)(dir, "manifest.json");
13729
+ }
13730
+ function mirrorPath(dir, repoRelPath) {
13731
+ return (0, import_node_path11.join)(dir, "files", repoRelPath);
13732
+ }
13733
+ function captureBaseline(opts = {}) {
13734
+ const key = sessionKey(opts.sessionId);
13735
+ const dir = sessionDir(key);
13736
+ const existing = readManifest(dir);
13737
+ const freshStart = opts.source === "startup" || opts.source === "clear";
13738
+ if (existing && !freshStart) {
13739
+ return { baseline: existing, created: false };
13740
+ }
13741
+ const head_sha = getCurrentCommit();
13742
+ const dirty = getDirtyFiles();
13743
+ try {
13744
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13745
+ } catch {
13746
+ }
13747
+ const filesDir = (0, import_node_path11.join)(dir, "files");
13748
+ const mirrored = [];
13749
+ try {
13750
+ (0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
13751
+ for (const p of dirty) {
13752
+ if (p.includes("..")) continue;
13753
+ const content = safeReadForMirror(projectPath(p));
13754
+ if (content === null) continue;
13755
+ const dest = mirrorPath(dir, p);
13756
+ try {
13757
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(dest), { recursive: true });
13758
+ (0, import_node_fs13.writeFileSync)(dest, content);
13759
+ mirrored.push(p);
13760
+ } catch {
13761
+ }
13762
+ }
13763
+ } catch {
13764
+ }
13765
+ const baseline = {
13766
+ session_id: opts.sessionId ?? "",
13767
+ head_sha,
13768
+ captured_at: Date.now(),
13769
+ dirty_paths: mirrored,
13770
+ version: BASELINE_VERSION
13771
+ };
13772
+ try {
13773
+ (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
13774
+ (0, import_node_fs13.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
13775
+ } catch {
13776
+ }
13777
+ pruneOldBaselines();
13778
+ return { baseline, created: true };
13779
+ }
13780
+ function readBaseline(sessionId) {
13781
+ return readManifest(sessionDir(sessionKey(sessionId)));
13782
+ }
13783
+ function readManifest(dir) {
13784
+ const mp = manifestPath(dir);
13785
+ if (!(0, import_node_fs13.existsSync)(mp)) return null;
13786
+ try {
13787
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(mp, "utf-8"));
13788
+ if (typeof parsed.head_sha !== "string" || typeof parsed.captured_at !== "number" || !Array.isArray(parsed.dirty_paths) || parsed.version !== BASELINE_VERSION) {
13789
+ return null;
13790
+ }
13791
+ return {
13792
+ session_id: typeof parsed.session_id === "string" ? parsed.session_id : "",
13793
+ head_sha: parsed.head_sha,
13794
+ captured_at: parsed.captured_at,
13795
+ dirty_paths: parsed.dirty_paths.filter((p) => typeof p === "string"),
13796
+ version: parsed.version
13797
+ };
13798
+ } catch {
13799
+ return null;
13800
+ }
13801
+ }
13802
+ var preImageCache = /* @__PURE__ */ new WeakMap();
13803
+ function preImage(repoRelPath, baseline) {
13804
+ let perBaseline = preImageCache.get(baseline);
13805
+ if (!perBaseline) {
13806
+ perBaseline = /* @__PURE__ */ new Map();
13807
+ preImageCache.set(baseline, perBaseline);
13808
+ }
13809
+ const cached = perBaseline.get(repoRelPath);
13810
+ if (cached) return cached;
13811
+ const resolved = resolvePreImage(repoRelPath, baseline);
13812
+ perBaseline.set(repoRelPath, resolved);
13813
+ return resolved;
13814
+ }
13815
+ function resolvePreImage(repoRelPath, baseline) {
13816
+ if (baseline.dirty_paths.includes(repoRelPath)) {
13817
+ const mp = mirrorPath(sessionDir(sessionKey(baseline.session_id)), repoRelPath);
13818
+ if ((0, import_node_fs13.existsSync)(mp)) {
13819
+ try {
13820
+ return { content: (0, import_node_fs13.readFileSync)(mp, "utf-8"), existed: true };
13821
+ } catch {
13822
+ }
13823
+ }
13824
+ }
13825
+ const atHead = showContentAtRef(baseline.head_sha, repoRelPath);
13826
+ if (atHead !== null) return { content: atHead, existed: true };
13827
+ return { content: "", existed: false };
13828
+ }
13829
+ function generateBaselineDiffs(files, baseline) {
13830
+ if (!baseline) return { diffs: [], has_baseline: false };
13831
+ const diffs = [];
13832
+ for (const file of files) {
13833
+ const language = file.language ?? detectLanguage(file.path);
13834
+ const pre = preImage(file.path, baseline);
13835
+ if (pre.existed) {
13836
+ if (pre.content === file.content) continue;
13837
+ const diff = computeDiff(pre.content, file.content, file.path);
13838
+ if (diff) diffs.push({ path: file.path, language, diff, status: "modified" });
13839
+ } else {
13840
+ const addedLines = file.content.split("\n").map((l) => `+${l}`).join("\n");
13841
+ diffs.push({
13842
+ path: file.path,
13843
+ language,
13844
+ diff: `--- /dev/null
13845
+ +++ b/${file.path}
13846
+ @@ -0,0 +1,${file.content.split("\n").length} @@
13847
+ ${addedLines}`,
13848
+ status: "added"
13849
+ });
13850
+ }
13851
+ }
13852
+ return { diffs, has_baseline: true };
13853
+ }
13854
+ function changedSinceBaseline(repoRelPath, baseline) {
13855
+ const pre = preImage(repoRelPath, baseline);
13856
+ let current;
13857
+ try {
13858
+ current = (0, import_node_fs13.readFileSync)(projectPath(repoRelPath), "utf-8");
13859
+ } catch {
13860
+ return pre.existed;
13861
+ }
13862
+ if (!pre.existed) return true;
13863
+ return current !== pre.content;
13864
+ }
13865
+ function safeReadForMirror(absPath) {
13866
+ try {
13867
+ if ((0, import_node_fs13.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
13868
+ const buf = (0, import_node_fs13.readFileSync)(absPath);
13869
+ if (buf.includes(0)) return null;
13870
+ return buf.toString("utf-8");
13871
+ } catch {
13872
+ return null;
13873
+ }
13874
+ }
13875
+ function pruneOldBaselines() {
13876
+ const root = projectPath(BASELINE_DIR);
13877
+ let entries;
13878
+ try {
13879
+ entries = (0, import_node_fs13.readdirSync)(root);
13880
+ } catch {
13881
+ return;
13882
+ }
13883
+ const now = Date.now();
13884
+ for (const name of entries) {
13885
+ const dir = (0, import_node_path11.join)(root, name);
13886
+ const manifest = readManifest(dir);
13887
+ if (!manifest) {
13888
+ try {
13889
+ if (now - (0, import_node_fs13.statSync)(dir).mtimeMs > BASELINE_TTL_MS) {
13890
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13891
+ }
13892
+ } catch {
13893
+ }
13894
+ continue;
13895
+ }
13896
+ if (now - manifest.captured_at <= BASELINE_TTL_MS) continue;
13897
+ try {
13898
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13899
+ } catch {
13900
+ }
13901
+ }
13902
+ }
13903
+
13904
+ // src/lib/offline.ts
13905
+ var import_node_fs14 = require("node:fs");
13906
+ var import_node_crypto6 = require("node:crypto");
13469
13907
  function cacheRequest(body) {
13470
13908
  try {
13471
- (0, import_node_fs13.mkdirSync)(CACHE_DIR, { recursive: true });
13472
- const suffix = (0, import_node_crypto4.randomBytes)(4).toString("hex");
13909
+ (0, import_node_fs14.mkdirSync)(CACHE_DIR, { recursive: true });
13910
+ const suffix = (0, import_node_crypto6.randomBytes)(4).toString("hex");
13473
13911
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
13474
- (0, import_node_fs13.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
13912
+ (0, import_node_fs14.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
13475
13913
  } catch {
13476
13914
  }
13477
13915
  }
@@ -13485,7 +13923,7 @@ function buildOfflineFallback(reason, staticResults) {
13485
13923
  }
13486
13924
 
13487
13925
  // src/lib/context-files.ts
13488
- var import_node_fs14 = require("node:fs");
13926
+ var import_node_fs15 = require("node:fs");
13489
13927
  var MAX_CONTEXT_FILES = 10;
13490
13928
  var MAX_CONTEXT_FILE_BYTES = 10240;
13491
13929
  var MAX_CONTEXT_TOTAL_BYTES = 51200;
@@ -13497,7 +13935,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
13497
13935
  if (result.length >= MAX_CONTEXT_FILES) break;
13498
13936
  if (deltaPaths.has(filePath)) continue;
13499
13937
  try {
13500
- const content = (0, import_node_fs14.readFileSync)(filePath, "utf8");
13938
+ const content = (0, import_node_fs15.readFileSync)(filePath, "utf8");
13501
13939
  const bytes = Buffer.byteLength(content);
13502
13940
  if (bytes > MAX_CONTEXT_FILE_BYTES) {
13503
13941
  logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
@@ -13542,20 +13980,20 @@ function gatherContextFiles(contextPaths, deltaFiles) {
13542
13980
  }
13543
13981
 
13544
13982
  // src/lib/cache-cleanup.ts
13545
- var import_node_fs15 = require("node:fs");
13546
- var import_node_path11 = require("node:path");
13983
+ var import_node_fs16 = require("node:fs");
13984
+ var import_node_path12 = require("node:path");
13547
13985
  var CACHE_TTL_DAYS = 7;
13548
13986
  function pruneStaleCache() {
13549
13987
  try {
13550
13988
  const dir = projectPath(CACHE_DIR);
13551
13989
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
13552
- for (const entry of (0, import_node_fs15.readdirSync)(dir)) {
13990
+ for (const entry of (0, import_node_fs16.readdirSync)(dir)) {
13553
13991
  if (!entry.startsWith("pending-")) continue;
13554
- const path = (0, import_node_path11.join)(dir, entry);
13992
+ const path = (0, import_node_path12.join)(dir, entry);
13555
13993
  try {
13556
- const stat3 = (0, import_node_fs15.statSync)(path);
13994
+ const stat3 = (0, import_node_fs16.statSync)(path);
13557
13995
  if (stat3.mtimeMs < cutoff) {
13558
- (0, import_node_fs15.unlinkSync)(path);
13996
+ (0, import_node_fs16.unlinkSync)(path);
13559
13997
  logEvent("cache_entry_pruned", {
13560
13998
  path: entry,
13561
13999
  age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
@@ -13627,10 +14065,11 @@ function reconcileAnalysisMode(predictedMode, signals) {
13627
14065
  signals.noFilesChanged,
13628
14066
  signals.assistantResponse,
13629
14067
  signals.conversationPrompts,
13630
- signals.actionSummary
14068
+ signals.actionSummary,
14069
+ signals.sessionAuthoredCode
13631
14070
  );
13632
14071
  }
13633
- const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0));
14072
+ const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
13634
14073
  const agentInvestigated = didAgentInvestigate(signals.actionSummary);
13635
14074
  switch (predictedMode) {
13636
14075
  case "skip":
@@ -13655,8 +14094,8 @@ function didAgentInvestigate(summary) {
13655
14094
  function isValidMode(mode) {
13656
14095
  return mode === "standard" || mode === "plan" || mode === "debug" || mode === "skip";
13657
14096
  }
13658
- function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary) {
13659
- const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
14097
+ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
14098
+ const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
13660
14099
  if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
13661
14100
  if (!agentAuthoredCode) return "skip";
13662
14101
  }
@@ -13736,7 +14175,7 @@ function isMetaTaskLabel(label2) {
13736
14175
  }
13737
14176
 
13738
14177
  // src/lib/transcript.ts
13739
- var import_node_fs16 = require("node:fs");
14178
+ var import_node_fs17 = require("node:fs");
13740
14179
  var MAX_READ_BYTES = 256 * 1024;
13741
14180
  var SMALL_FILE_BYTES = 64 * 1024;
13742
14181
  var MAX_FILES_LIST = 20;
@@ -13758,14 +14197,14 @@ async function extractActionSummary(transcriptPath) {
13758
14197
  function readTurnLines(transcriptPath) {
13759
14198
  let size;
13760
14199
  try {
13761
- size = (0, import_node_fs16.statSync)(transcriptPath).size;
14200
+ size = (0, import_node_fs17.statSync)(transcriptPath).size;
13762
14201
  } catch {
13763
14202
  return null;
13764
14203
  }
13765
14204
  if (size === 0) return null;
13766
14205
  let raw;
13767
14206
  if (size <= SMALL_FILE_BYTES) {
13768
- raw = (0, import_node_fs16.readFileSync)(transcriptPath, "utf-8");
14207
+ raw = (0, import_node_fs17.readFileSync)(transcriptPath, "utf-8");
13769
14208
  } else {
13770
14209
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
13771
14210
  const fd = require("node:fs").openSync(transcriptPath, "r");
@@ -13853,6 +14292,12 @@ function buildSummary(lines) {
13853
14292
  case "Edit":
13854
14293
  addPath(filesEdited, input.file_path);
13855
14294
  break;
14295
+ case "MultiEdit":
14296
+ addPath(filesEdited, input.file_path);
14297
+ break;
14298
+ case "NotebookEdit":
14299
+ addPath(filesEdited, input.notebook_path ?? input.file_path);
14300
+ break;
13856
14301
  case "Write":
13857
14302
  addPath(filesCreated, input.file_path);
13858
14303
  addPath(filesEdited, input.file_path);
@@ -13936,8 +14381,8 @@ function capArray(set, max) {
13936
14381
 
13937
14382
  // src/lib/seed-runner.ts
13938
14383
  var import_promises11 = require("node:fs/promises");
13939
- var import_node_fs17 = require("node:fs");
13940
- var import_node_path12 = require("node:path");
14384
+ var import_node_fs18 = require("node:fs");
14385
+ var import_node_path13 = require("node:path");
13941
14386
  var import_yaml2 = __toESM(require_dist());
13942
14387
 
13943
14388
  // src/lib/seed.ts
@@ -14176,7 +14621,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
14176
14621
  return fm;
14177
14622
  }
14178
14623
  async function runSeed(opts) {
14179
- if (!(0, import_node_fs17.existsSync)(STANDARD_FILE)) {
14624
+ if (!(0, import_node_fs18.existsSync)(STANDARD_FILE)) {
14180
14625
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
14181
14626
  }
14182
14627
  let standardDoc;
@@ -14188,7 +14633,7 @@ async function runSeed(opts) {
14188
14633
  }
14189
14634
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
14190
14635
  let readmeContent;
14191
- if ((0, import_node_fs17.existsSync)("README.md")) {
14636
+ if ((0, import_node_fs18.existsSync)("README.md")) {
14192
14637
  try {
14193
14638
  readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
14194
14639
  } catch {
@@ -14196,7 +14641,7 @@ async function runSeed(opts) {
14196
14641
  }
14197
14642
  let claudeMdContent;
14198
14643
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
14199
- if ((0, import_node_fs17.existsSync)(p)) {
14644
+ if ((0, import_node_fs18.existsSync)(p)) {
14200
14645
  try {
14201
14646
  claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
14202
14647
  break;
@@ -14219,8 +14664,8 @@ async function runSeed(opts) {
14219
14664
  if (candidates.length === 0) {
14220
14665
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
14221
14666
  }
14222
- const overviewPath = (0, import_node_path12.join)(MEMORY_DIR, "domain", "project-overview.md");
14223
- if ((0, import_node_fs17.existsSync)(overviewPath) && !opts.force) {
14667
+ const overviewPath = (0, import_node_path13.join)(MEMORY_DIR, "domain", "project-overview.md");
14668
+ if ((0, import_node_fs18.existsSync)(overviewPath) && !opts.force) {
14224
14669
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
14225
14670
  }
14226
14671
  if (opts.dryRun) {
@@ -14255,9 +14700,9 @@ async function runSeed(opts) {
14255
14700
  }
14256
14701
  const nodeId = res.data.node_id;
14257
14702
  const filePathRel = res.data.file_path;
14258
- const targetPath = (0, import_node_path12.join)(MEMORY_DIR, filePathRel);
14703
+ const targetPath = (0, import_node_path13.join)(MEMORY_DIR, filePathRel);
14259
14704
  try {
14260
- await (0, import_promises11.mkdir)((0, import_node_path12.dirname)(targetPath), { recursive: true });
14705
+ await (0, import_promises11.mkdir)((0, import_node_path13.dirname)(targetPath), { recursive: true });
14261
14706
  await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
14262
14707
  created++;
14263
14708
  opts.onCreated?.(nodeId, filePathRel, c);
@@ -14310,6 +14755,27 @@ function passAndExit(reason) {
14310
14755
  printJsonCompact({ gate_decision: "PASS", systemMessage: `Verity: ${reason}` });
14311
14756
  process.exit(0);
14312
14757
  }
14758
+ var EMPTY_STATIC = {
14759
+ tool: "@codacy/analysis-cli",
14760
+ findings: [],
14761
+ summary: { total_findings: 0, by_severity: {}, tools_run: [] }
14762
+ };
14763
+ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
14764
+ if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
14765
+ let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14766
+ if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
14767
+ if (scannable.length === 0) return EMPTY_STATIC;
14768
+ return runCodacyAnalysis(scannable);
14769
+ }
14770
+ function localOnlyAndExit(staticResults) {
14771
+ printJsonCompact({
14772
+ gate_decision: "PASS",
14773
+ 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.",
14774
+ unauthenticated: true,
14775
+ static_results: staticResults
14776
+ });
14777
+ process.exit(0);
14778
+ }
14313
14779
  function registerAnalyzeCommand(program2) {
14314
14780
  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) => {
14315
14781
  const globals = program2.opts();
@@ -14327,6 +14793,15 @@ async function runAnalyze(opts, globals) {
14327
14793
  }
14328
14794
  const { assistantMessage: assistantResponse, stopReason, transcriptPath, sessionId } = await readStopHookStdin();
14329
14795
  const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
14796
+ const baselineSessionId = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
14797
+ const baseline = readBaseline(baselineSessionId);
14798
+ if (baseline) {
14799
+ logEvent("baseline_loaded", {
14800
+ head: baseline.head_sha.slice(0, 12),
14801
+ dirty_count: baseline.dirty_paths.length,
14802
+ age_ms: Date.now() - baseline.captured_at
14803
+ });
14804
+ }
14330
14805
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
14331
14806
  const analyzable = filterAnalyzable(allChanged);
14332
14807
  const reviewable = filterReviewable(allChanged);
@@ -14336,7 +14811,7 @@ async function runAnalyze(opts, globals) {
14336
14811
  passAndExit("No analyzable files changed");
14337
14812
  }
14338
14813
  const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
14339
- const conversation = await readAndClearConversationBuffer();
14814
+ const conversation = await readAndClearConversationBuffer(sessionId ?? void 0);
14340
14815
  const specs = discoverSpecs();
14341
14816
  const plans = discoverPlans();
14342
14817
  const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
@@ -14351,12 +14826,9 @@ async function runAnalyze(opts, globals) {
14351
14826
  passAndExit("Reflection-prompt turn \u2014 skipping analysis");
14352
14827
  }
14353
14828
  const tokenResult = await resolveToken(globals.token);
14354
- if (!tokenResult.ok) {
14355
- passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14356
- }
14357
14829
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
14358
- if (!urlResult.ok) {
14359
- passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14830
+ if (!tokenResult.ok || !urlResult.ok) {
14831
+ localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
14360
14832
  }
14361
14833
  const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
14362
14834
  let contextFilePaths = [];
@@ -14386,13 +14858,14 @@ async function runAnalyze(opts, globals) {
14386
14858
  }
14387
14859
  const conversationPrompts = (conversation?.prompts ?? []).map((p) => p.prompt);
14388
14860
  let analysisMode;
14861
+ const sessionAuthoredCode = !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
14389
14862
  const modeOverride = opts.mode;
14390
14863
  if (modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride)) {
14391
14864
  analysisMode = modeOverride;
14392
14865
  } else {
14393
14866
  analysisMode = reconcileAnalysisMode(
14394
14867
  predictedMode,
14395
- { noFilesChanged, assistantResponse, actionSummary, conversationPrompts }
14868
+ { noFilesChanged, assistantResponse, actionSummary, conversationPrompts, sessionAuthoredCode }
14396
14869
  );
14397
14870
  }
14398
14871
  if (analysisMode === "skip") {
@@ -14414,7 +14887,7 @@ async function runAnalyze(opts, globals) {
14414
14887
  let currentCommit = "";
14415
14888
  if (analysisMode !== "plan") {
14416
14889
  const debounceSeconds = parseInt(opts.debounce, 10);
14417
- const debounceSkip = checkDebounce(debounceSeconds);
14890
+ const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
14418
14891
  if (debounceSkip) {
14419
14892
  if (assistantResponse) {
14420
14893
  analysisMode = "plan";
@@ -14424,7 +14897,7 @@ async function runAnalyze(opts, globals) {
14424
14897
  }
14425
14898
  if (analysisMode !== "plan") {
14426
14899
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
14427
- const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles);
14900
+ const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
14428
14901
  if (mtimeSkip) {
14429
14902
  if (assistantResponse) {
14430
14903
  analysisMode = "plan";
@@ -14434,9 +14907,9 @@ async function runAnalyze(opts, globals) {
14434
14907
  }
14435
14908
  }
14436
14909
  if (analysisMode !== "plan") {
14437
- recordAnalysisStart();
14910
+ recordAnalysisStart(baselineSessionId);
14438
14911
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
14439
- const hashResult = checkContentHash(allCheckable);
14912
+ const hashResult = checkContentHash(allCheckable, baselineSessionId);
14440
14913
  if (hashResult.skip) {
14441
14914
  if (assistantResponse) {
14442
14915
  analysisMode = "plan";
@@ -14448,12 +14921,18 @@ async function runAnalyze(opts, globals) {
14448
14921
  if (analysisMode !== "plan") {
14449
14922
  const agentNarrowed = narrowToAgentAuthored(allForReview, actionSummary);
14450
14923
  const baseForReview = agentNarrowed.length > 0 ? agentNarrowed : allForReview;
14451
- const recentForReview = narrowToRecent(baseForReview);
14924
+ const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
14452
14925
  if (!opts.skipStatic && isCodacyAvailable()) {
14453
- const allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14454
- staticResults = runCodacyAnalysis(allScannable);
14926
+ let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14927
+ if (baseline) {
14928
+ allScannable = allScannable.filter((f) => changedSinceBaseline(f, baseline));
14929
+ }
14930
+ if (allScannable.length > 0) {
14931
+ staticResults = runCodacyAnalysis(allScannable);
14932
+ }
14455
14933
  }
14456
- codeDelta = collectCodeDelta(recentForReview, {
14934
+ const deltaSet = baseline ? recentForReview.filter((f) => changedSinceBaseline(f, baseline)) : recentForReview;
14935
+ codeDelta = collectCodeDelta(deltaSet, {
14457
14936
  maxFiles: parseInt(opts.maxFiles, 10),
14458
14937
  maxFileBytes: parseInt(opts.maxFileSize, 10),
14459
14938
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
@@ -14468,7 +14947,12 @@ async function runAnalyze(opts, globals) {
14468
14947
  }
14469
14948
  }
14470
14949
  if (analysisMode !== "plan") {
14471
- snapshotResult = generateSnapshotDiffs(codeDelta.files);
14950
+ if (baseline) {
14951
+ const baselineDiffs = generateBaselineDiffs(codeDelta.files, baseline);
14952
+ snapshotResult = { has_snapshots: baselineDiffs.diffs.length > 0, diffs: baselineDiffs.diffs };
14953
+ } else {
14954
+ snapshotResult = generateSnapshotDiffs(codeDelta.files);
14955
+ }
14472
14956
  currentCommit = getCurrentCommit();
14473
14957
  const maxIterations = parseInt(opts.maxIterations, 10);
14474
14958
  const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
@@ -14500,9 +14984,9 @@ async function runAnalyze(opts, globals) {
14500
14984
  let autoSeedNotice = null;
14501
14985
  try {
14502
14986
  await ensureMemoryDir();
14503
- const seedMarker = (0, import_node_path13.join)(VERITY_DIR, ".seeded");
14504
- const hasStandard = (0, import_node_fs18.existsSync)(STANDARD_FILE);
14505
- const alreadyTried = (0, import_node_fs18.existsSync)(seedMarker);
14987
+ const seedMarker = (0, import_node_path14.join)(VERITY_DIR, ".seeded");
14988
+ const hasStandard = (0, import_node_fs19.existsSync)(STANDARD_FILE);
14989
+ const alreadyTried = (0, import_node_fs19.existsSync)(seedMarker);
14506
14990
  if (hasStandard && !alreadyTried) {
14507
14991
  const preManifest = await buildManifest();
14508
14992
  if (preManifest.nodes.length === 0) {
@@ -14515,7 +14999,7 @@ async function runAnalyze(opts, globals) {
14515
14999
  dryRun: false
14516
15000
  });
14517
15001
  if (seedResult.created > 0) {
14518
- (0, import_node_fs18.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
15002
+ (0, import_node_fs19.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
14519
15003
  `);
14520
15004
  autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
14521
15005
  logEvent("auto_seed_ran", {
@@ -14523,7 +15007,7 @@ async function runAnalyze(opts, globals) {
14523
15007
  failed: seedResult.failed
14524
15008
  });
14525
15009
  } else if (seedResult.skipped === "already_seeded") {
14526
- (0, import_node_fs18.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
15010
+ (0, import_node_fs19.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
14527
15011
  `);
14528
15012
  } else {
14529
15013
  logEvent("auto_seed_noop", {
@@ -14812,7 +15296,7 @@ async function runAnalyze(opts, globals) {
14812
15296
  }
14813
15297
  case "PASS": {
14814
15298
  writeIteration(1, currentCommit, contentHash ?? void 0);
14815
- if (contentHash) recordPassHash(contentHash);
15299
+ if (contentHash) recordPassHash(contentHash, baselineSessionId);
14816
15300
  if (currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
14817
15301
  let userSummary = response.user_summary ?? "Verity: PASS";
14818
15302
  const viewUrl = response.view_url ?? "";
@@ -14823,7 +15307,7 @@ async function runAnalyze(opts, globals) {
14823
15307
  break;
14824
15308
  }
14825
15309
  case "WARN": {
14826
- if (contentHash) recordPassHash(contentHash);
15310
+ if (contentHash) recordPassHash(contentHash, baselineSessionId);
14827
15311
  if (currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
14828
15312
  let userSummary = response.user_summary ?? "Verity: WARN";
14829
15313
  const viewUrl = response.view_url ?? "";
@@ -14841,8 +15325,54 @@ async function runAnalyze(opts, globals) {
14841
15325
  }
14842
15326
  }
14843
15327
 
15328
+ // src/commands/baseline.ts
15329
+ var import_node_fs20 = require("node:fs");
15330
+ function registerBaselineCommands(program2) {
15331
+ const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
15332
+ baseline.command("capture").description("Snapshot the working tree at task start (used by SessionStart hook)").option("--session-id <id>", "Session id (overrides any value from stdin)").option("--source <source>", "Lifecycle hint: startup|resume|clear|compact").action(async (opts) => {
15333
+ try {
15334
+ try {
15335
+ process.chdir(repoRoot());
15336
+ } catch {
15337
+ }
15338
+ if (!(0, import_node_fs20.existsSync)(VERITY_DIR)) {
15339
+ process.exit(0);
15340
+ }
15341
+ let sessionId = opts.sessionId;
15342
+ let source = opts.source;
15343
+ if (!process.stdin.isTTY) {
15344
+ const input = (await readStdin()).trim();
15345
+ if (input) {
15346
+ try {
15347
+ const event = JSON.parse(input);
15348
+ sessionId = sessionId ?? event.session_id;
15349
+ source = source ?? event.source;
15350
+ } catch {
15351
+ }
15352
+ }
15353
+ }
15354
+ const result = captureBaseline({ sessionId, source });
15355
+ logEvent("baseline_capture", {
15356
+ created: result.created,
15357
+ source: source ?? null,
15358
+ dirty_count: result.baseline.dirty_paths.length,
15359
+ head: result.baseline.head_sha.slice(0, 12)
15360
+ });
15361
+ } catch {
15362
+ }
15363
+ process.exit(0);
15364
+ });
15365
+ }
15366
+ async function readStdin() {
15367
+ const chunks = [];
15368
+ for await (const chunk of process.stdin) {
15369
+ chunks.push(chunk);
15370
+ }
15371
+ return Buffer.concat(chunks).toString("utf-8");
15372
+ }
15373
+
14844
15374
  // src/commands/review.ts
14845
- var import_node_fs19 = require("node:fs");
15375
+ var import_node_fs21 = require("node:fs");
14846
15376
  function registerReviewCommand(program2) {
14847
15377
  program2.command("review").description("Run on-demand Verity analysis (advisory, never blocks)").requiredOption("--files <paths>", "Comma-separated file list").option("--changed <paths>", "Subset of --files that were modified").option("--intent <text>", "User intent description (max 2000 chars)").option("--specs <paths>", "Comma-separated spec file paths").option("--json", "Output raw JSON response").action(async (opts) => {
14848
15378
  const globals = program2.opts();
@@ -14861,7 +15391,7 @@ async function runReview(opts, globals) {
14861
15391
  const securityFiles = filterSecurity(allFiles);
14862
15392
  let staticResults;
14863
15393
  if (isCodacyAvailable()) {
14864
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs19.existsSync)(f) || resolveFile(f) !== null);
15394
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs21.existsSync)(f) || resolveFile(f) !== null);
14865
15395
  staticResults = runCodacyAnalysis(scannable);
14866
15396
  } else {
14867
15397
  staticResults = {
@@ -14872,13 +15402,14 @@ async function runReview(opts, globals) {
14872
15402
  }
14873
15403
  const codeDelta = collectCodeDelta(allFiles);
14874
15404
  const tokenResult = await resolveToken(globals.token);
14875
- if (!tokenResult.ok) {
14876
- printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14877
- process.exit(0);
14878
- }
14879
15405
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
14880
- if (!urlResult.ok) {
14881
- printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
15406
+ if (!tokenResult.ok || !urlResult.ok) {
15407
+ printJsonCompact({
15408
+ gate_decision: "PASS",
15409
+ 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.",
15410
+ unauthenticated: true,
15411
+ static_results: staticResults
15412
+ });
14882
15413
  process.exit(0);
14883
15414
  }
14884
15415
  let specs;
@@ -14886,10 +15417,10 @@ async function runReview(opts, globals) {
14886
15417
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
14887
15418
  specs = [];
14888
15419
  for (const p of specPaths) {
14889
- if (!(0, import_node_fs19.existsSync)(p)) continue;
15420
+ if (!(0, import_node_fs21.existsSync)(p)) continue;
14890
15421
  try {
14891
- const { readFileSync: readFileSync11 } = await import("node:fs");
14892
- const content = readFileSync11(p, "utf-8");
15422
+ const { readFileSync: readFileSync12 } = await import("node:fs");
15423
+ const content = readFileSync12(p, "utf-8");
14893
15424
  specs.push({ path: p, content: content.slice(0, 10240) });
14894
15425
  } catch {
14895
15426
  }
@@ -14946,10 +15477,10 @@ async function runReview(opts, globals) {
14946
15477
  }
14947
15478
 
14948
15479
  // src/commands/guard.ts
14949
- var import_node_fs20 = require("node:fs");
14950
- var import_node_path14 = require("node:path");
15480
+ var import_node_fs22 = require("node:fs");
15481
+ var import_node_path15 = require("node:path");
14951
15482
  var GUARD_BLOCK_CAP = 2;
14952
- var GUARD_ITER_FILE = (0, import_node_path14.join)(VERITY_DIR, ".guard-iteration");
15483
+ var GUARD_ITER_FILE = (0, import_node_path15.join)(VERITY_DIR, ".guard-iteration");
14953
15484
  function readPreToolUseStdin() {
14954
15485
  const empty = { command: "", cwd: null, sessionId: null };
14955
15486
  return new Promise((resolve) => {
@@ -15013,7 +15544,7 @@ function classifyCommand(command, on) {
15013
15544
  }
15014
15545
  function readIterMap() {
15015
15546
  try {
15016
- const raw = JSON.parse((0, import_node_fs20.readFileSync)(GUARD_ITER_FILE, "utf-8"));
15547
+ const raw = JSON.parse((0, import_node_fs22.readFileSync)(GUARD_ITER_FILE, "utf-8"));
15017
15548
  if (raw && typeof raw === "object") {
15018
15549
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
15019
15550
  return { [raw.moment]: raw.count };
@@ -15033,10 +15564,10 @@ function readIter(moment) {
15033
15564
  }
15034
15565
  function writeIter(moment, count) {
15035
15566
  try {
15036
- (0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
15567
+ (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
15037
15568
  const map = readIterMap();
15038
15569
  map[moment] = count;
15039
- (0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15570
+ (0, import_node_fs22.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15040
15571
  } catch {
15041
15572
  }
15042
15573
  }
@@ -15046,10 +15577,10 @@ function resetIter(moment) {
15046
15577
  if (!(moment in map)) return;
15047
15578
  delete map[moment];
15048
15579
  if (Object.keys(map).length === 0) {
15049
- if ((0, import_node_fs20.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs20.unlinkSync)(GUARD_ITER_FILE);
15580
+ if ((0, import_node_fs22.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs22.unlinkSync)(GUARD_ITER_FILE);
15050
15581
  } else {
15051
- (0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
15052
- (0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15582
+ (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
15583
+ (0, import_node_fs22.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15053
15584
  }
15054
15585
  } catch {
15055
15586
  }
@@ -15113,7 +15644,7 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
15113
15644
  const securityFiles = filterSecurity(files);
15114
15645
  let staticResults;
15115
15646
  if (isCodacyAvailable()) {
15116
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs20.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
15647
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs22.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
15117
15648
  staticResults = runCodacyAnalysis(scannable);
15118
15649
  } else {
15119
15650
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -15158,7 +15689,7 @@ function emitAllowNotice(userMsg, agentMsg) {
15158
15689
  async function runGuard(opts, globals) {
15159
15690
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
15160
15691
  const { command, cwd, sessionId } = await readPreToolUseStdin();
15161
- if (cwd && (0, import_node_fs20.existsSync)(cwd)) {
15692
+ if (cwd && (0, import_node_fs22.existsSync)(cwd)) {
15162
15693
  try {
15163
15694
  process.chdir(cwd);
15164
15695
  } catch {
@@ -15263,14 +15794,15 @@ function writeBlockMessage(moment, response) {
15263
15794
  }
15264
15795
 
15265
15796
  // src/commands/init.ts
15266
- var import_node_fs22 = require("node:fs");
15797
+ var import_node_fs24 = require("node:fs");
15267
15798
  var import_promises12 = require("node:fs/promises");
15268
- var import_node_path16 = require("node:path");
15799
+ var import_node_path17 = require("node:path");
15269
15800
  var import_node_child_process9 = require("node:child_process");
15801
+ var readline = __toESM(require("node:readline/promises"));
15270
15802
 
15271
15803
  // src/commands/migrate.ts
15272
- var import_node_fs21 = require("node:fs");
15273
- var import_node_path15 = require("node:path");
15804
+ var import_node_fs23 = require("node:fs");
15805
+ var import_node_path16 = require("node:path");
15274
15806
  var import_node_child_process8 = require("node:child_process");
15275
15807
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
15276
15808
  function defaultNpmRemover(pkg) {
@@ -15306,12 +15838,12 @@ async function runMigration(opts = {}) {
15306
15838
  return { actions, migrated: actions.length > 0 };
15307
15839
  }
15308
15840
  function migrateProjectDir(root, actions) {
15309
- const gateDir = (0, import_node_path15.join)(root, ".gate");
15310
- const verityDir = (0, import_node_path15.join)(root, ".verity");
15311
- if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) {
15841
+ const gateDir = (0, import_node_path16.join)(root, ".gate");
15842
+ const verityDir = (0, import_node_path16.join)(root, ".verity");
15843
+ if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) {
15312
15844
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
15313
15845
  }
15314
- if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
15846
+ if ((0, import_node_fs23.existsSync)(gateDir) && (0, import_node_fs23.existsSync)(verityDir)) {
15315
15847
  return migrateProjectDirCarry(gateDir, verityDir, actions);
15316
15848
  }
15317
15849
  return false;
@@ -15332,13 +15864,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
15332
15864
  }
15333
15865
  }
15334
15866
  if (moved) {
15335
- if ((0, import_node_fs21.existsSync)(gateDir)) {
15867
+ if ((0, import_node_fs23.existsSync)(gateDir)) {
15336
15868
  const carried = carryLegacyContents(gateDir, verityDir);
15337
15869
  if (carried > 0) {
15338
15870
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
15339
15871
  }
15340
15872
  try {
15341
- (0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
15873
+ (0, import_node_fs23.rmSync)(gateDir, { recursive: true, force: true });
15342
15874
  } catch {
15343
15875
  }
15344
15876
  }
@@ -15354,18 +15886,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
15354
15886
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
15355
15887
  }
15356
15888
  try {
15357
- (0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
15889
+ (0, import_node_fs23.rmSync)(gateDir, { recursive: true, force: true });
15358
15890
  } catch {
15359
15891
  }
15360
15892
  return carried > 0;
15361
15893
  }
15362
15894
  function migrateGlobalCredentials(home, actions) {
15363
15895
  if (!home) return;
15364
- const gateCreds = (0, import_node_path15.join)(home, ".gate", "credentials");
15365
- const verityCreds = (0, import_node_path15.join)(home, ".verity", "credentials");
15366
- if (!(0, import_node_fs21.existsSync)(gateCreds)) return;
15367
- if (!(0, import_node_fs21.existsSync)(verityCreds)) {
15368
- (0, import_node_fs21.mkdirSync)((0, import_node_path15.join)(home, ".verity"), { recursive: true });
15896
+ const gateCreds = (0, import_node_path16.join)(home, ".gate", "credentials");
15897
+ const verityCreds = (0, import_node_path16.join)(home, ".verity", "credentials");
15898
+ if (!(0, import_node_fs23.existsSync)(gateCreds)) return;
15899
+ if (!(0, import_node_fs23.existsSync)(verityCreds)) {
15900
+ (0, import_node_fs23.mkdirSync)((0, import_node_path16.join)(home, ".verity"), { recursive: true });
15369
15901
  moveFile(gateCreds, verityCreds);
15370
15902
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
15371
15903
  return;
@@ -15387,8 +15919,8 @@ async function migrateLegacyHooks(root, actions) {
15387
15919
  }
15388
15920
  }
15389
15921
  async function migrateClaudeMd(root, actions) {
15390
- const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
15391
- const hadLegacyBlock = (0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15922
+ const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
15923
+ const hadLegacyBlock = (0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15392
15924
  if (!hadLegacyBlock) return;
15393
15925
  try {
15394
15926
  await ensureClaudeMdPointer(root);
@@ -15398,9 +15930,9 @@ async function migrateClaudeMd(root, actions) {
15398
15930
  }
15399
15931
  }
15400
15932
  function migrateStandardFile(root, actions) {
15401
- const gateMd = (0, import_node_path15.join)(root, "GATE.md");
15402
- const verityMd = (0, import_node_path15.join)(root, "VERITY.md");
15403
- if (!(0, import_node_fs21.existsSync)(gateMd) || (0, import_node_fs21.existsSync)(verityMd)) return;
15933
+ const gateMd = (0, import_node_path16.join)(root, "GATE.md");
15934
+ const verityMd = (0, import_node_path16.join)(root, "VERITY.md");
15935
+ if (!(0, import_node_fs23.existsSync)(gateMd) || (0, import_node_fs23.existsSync)(verityMd)) return;
15404
15936
  let moved = false;
15405
15937
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
15406
15938
  try {
@@ -15412,7 +15944,7 @@ function migrateStandardFile(root, actions) {
15412
15944
  if (!moved) moveFile(gateMd, verityMd);
15413
15945
  const content = readFileSyncSafe(verityMd);
15414
15946
  const refreshed = content.split("GATE.md").join("VERITY.md");
15415
- if (refreshed !== content) (0, import_node_fs21.writeFileSync)(verityMd, refreshed);
15947
+ if (refreshed !== content) (0, import_node_fs23.writeFileSync)(verityMd, refreshed);
15416
15948
  actions.push("Renamed GATE.md \u2192 VERITY.md");
15417
15949
  }
15418
15950
  function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
@@ -15446,14 +15978,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
15446
15978
  }
15447
15979
  if (toAppend.length > 0) {
15448
15980
  const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
15449
- (0, import_node_fs21.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
15981
+ (0, import_node_fs23.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
15450
15982
  }
15451
- (0, import_node_fs21.rmSync)(gateCreds, { force: true });
15983
+ (0, import_node_fs23.rmSync)(gateCreds, { force: true });
15452
15984
  return toAppend.length;
15453
15985
  }
15454
15986
  function readFileSyncSafe(path) {
15455
15987
  try {
15456
- return (0, import_node_fs21.readFileSync)(path, "utf-8");
15988
+ return (0, import_node_fs23.readFileSync)(path, "utf-8");
15457
15989
  } catch {
15458
15990
  return "";
15459
15991
  }
@@ -15468,35 +16000,35 @@ function hasStagedChanges(root) {
15468
16000
  }
15469
16001
  function moveDir(from, to) {
15470
16002
  try {
15471
- (0, import_node_fs21.renameSync)(from, to);
16003
+ (0, import_node_fs23.renameSync)(from, to);
15472
16004
  } catch (err) {
15473
16005
  if (err.code !== "EXDEV") throw err;
15474
- (0, import_node_fs21.cpSync)(from, to, { recursive: true });
15475
- (0, import_node_fs21.rmSync)(from, { recursive: true, force: true });
16006
+ (0, import_node_fs23.cpSync)(from, to, { recursive: true });
16007
+ (0, import_node_fs23.rmSync)(from, { recursive: true, force: true });
15476
16008
  }
15477
16009
  }
15478
16010
  function moveFile(from, to) {
15479
16011
  try {
15480
- (0, import_node_fs21.renameSync)(from, to);
16012
+ (0, import_node_fs23.renameSync)(from, to);
15481
16013
  } catch (err) {
15482
16014
  if (err.code !== "EXDEV") throw err;
15483
- (0, import_node_fs21.cpSync)(from, to);
15484
- (0, import_node_fs21.rmSync)(from, { force: true });
16015
+ (0, import_node_fs23.cpSync)(from, to);
16016
+ (0, import_node_fs23.rmSync)(from, { force: true });
15485
16017
  }
15486
16018
  }
15487
16019
  function carryLegacyContents(gateDir, verityDir) {
15488
16020
  let copied = 0;
15489
16021
  const walk = (relDir) => {
15490
- const srcDir = (0, import_node_path15.join)(gateDir, relDir);
15491
- for (const entry of (0, import_node_fs21.readdirSync)(srcDir)) {
15492
- const rel = relDir ? (0, import_node_path15.join)(relDir, entry) : entry;
15493
- const src = (0, import_node_path15.join)(gateDir, rel);
15494
- const dest = (0, import_node_path15.join)(verityDir, rel);
15495
- if ((0, import_node_fs21.statSync)(src).isDirectory()) {
16022
+ const srcDir = (0, import_node_path16.join)(gateDir, relDir);
16023
+ for (const entry of (0, import_node_fs23.readdirSync)(srcDir)) {
16024
+ const rel = relDir ? (0, import_node_path16.join)(relDir, entry) : entry;
16025
+ const src = (0, import_node_path16.join)(gateDir, rel);
16026
+ const dest = (0, import_node_path16.join)(verityDir, rel);
16027
+ if ((0, import_node_fs23.statSync)(src).isDirectory()) {
15496
16028
  walk(rel);
15497
- } else if (!(0, import_node_fs21.existsSync)(dest)) {
15498
- (0, import_node_fs21.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15499
- (0, import_node_fs21.cpSync)(src, dest);
16029
+ } else if (!(0, import_node_fs23.existsSync)(dest)) {
16030
+ (0, import_node_fs23.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
16031
+ (0, import_node_fs23.cpSync)(src, dest);
15500
16032
  copied++;
15501
16033
  }
15502
16034
  }
@@ -15505,22 +16037,22 @@ function carryLegacyContents(gateDir, verityDir) {
15505
16037
  return copied;
15506
16038
  }
15507
16039
  async function needsMigration(root = repoRoot()) {
15508
- const gateDir = (0, import_node_path15.join)(root, ".gate");
15509
- const verityDir = (0, import_node_path15.join)(root, ".verity");
15510
- if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) return true;
15511
- if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
15512
- if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(gateDir, "credentials")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(verityDir, "credentials"))) {
16040
+ const gateDir = (0, import_node_path16.join)(root, ".gate");
16041
+ const verityDir = (0, import_node_path16.join)(root, ".verity");
16042
+ if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) return true;
16043
+ if ((0, import_node_fs23.existsSync)(gateDir) && (0, import_node_fs23.existsSync)(verityDir)) {
16044
+ 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"))) {
15513
16045
  return true;
15514
16046
  }
15515
- if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(gateDir, "memory")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(verityDir, "memory"))) {
16047
+ 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"))) {
15516
16048
  return true;
15517
16049
  }
15518
16050
  }
15519
- const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
15520
- if ((0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
16051
+ const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
16052
+ if ((0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15521
16053
  return true;
15522
16054
  }
15523
- if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(root, "GATE.md")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(root, "VERITY.md"))) {
16055
+ 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"))) {
15524
16056
  return true;
15525
16057
  }
15526
16058
  if (await hasLegacyHooksAt(root)) return true;
@@ -15546,17 +16078,77 @@ function registerMigrateCommand(program2) {
15546
16078
  }
15547
16079
 
15548
16080
  // src/commands/init.ts
16081
+ async function promptYes(question) {
16082
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
16083
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
16084
+ try {
16085
+ const answer = (await rl.question(question)).trim().toLowerCase();
16086
+ return answer === "" || answer === "y" || answer === "yes";
16087
+ } finally {
16088
+ rl.close();
16089
+ }
16090
+ }
16091
+ async function runOptionalAuth() {
16092
+ const existing = await resolveToken();
16093
+ if (existing.ok) {
16094
+ printInfo("Already authenticated \u2014 results will upload to the Verity service. \u2713");
16095
+ return;
16096
+ }
16097
+ let remote = "";
16098
+ try {
16099
+ remote = (0, import_node_child_process9.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
16100
+ } catch {
16101
+ }
16102
+ const localOnlyNote = () => {
16103
+ printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
16104
+ printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
16105
+ };
16106
+ if (process.stdin.isTTY && process.stdout.isTTY) {
16107
+ console.log("");
16108
+ console.log(" Signing in is optional. What it does:");
16109
+ console.log(" - Confirms you have write access to this repository. The GitHub token");
16110
+ console.log(" is used once to verify that, then discarded \u2014 Verity never stores it.");
16111
+ console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
16112
+ console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
16113
+ console.log(" - It is required to store and access run history for this repo");
16114
+ console.log(" (past results, trends, and shareable reports).");
16115
+ console.log(" - Skip and Verity still works fully locally: the gate runs and shows");
16116
+ console.log(" findings, but nothing is uploaded.");
16117
+ console.log("");
16118
+ }
16119
+ const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
16120
+ if (!wantsAuth) {
16121
+ printInfo("Skipped authentication.");
16122
+ localOnlyNote();
16123
+ return;
16124
+ }
16125
+ if (!remote) {
16126
+ printWarn("No git remote found \u2014 cannot authenticate yet.");
16127
+ localOnlyNote();
16128
+ return;
16129
+ }
16130
+ const projectName = parseRemote(remote)?.repo ?? (0, import_node_path17.basename)(process.cwd());
16131
+ printInfo("Authenticating with GitHub\u2026");
16132
+ const result = await registerProject({ projectName, remote, serviceUrl: DEFAULT_SERVICE_URL });
16133
+ if (result.ok) {
16134
+ printInfo(`Project registered: ${result.data.projectId} \u2713`);
16135
+ if (result.data.email) printInfo(` Authenticated as: ${result.data.email}`);
16136
+ } else {
16137
+ printWarn(`Authentication did not complete: ${result.error}`);
16138
+ localOnlyNote();
16139
+ }
16140
+ }
15549
16141
  function resolveDataDir() {
15550
16142
  const candidates = [
15551
- (0, import_node_path16.join)(__dirname, "..", "data"),
16143
+ (0, import_node_path17.join)(__dirname, "..", "data"),
15552
16144
  // installed: node_modules/@codacy/verity-cli/data
15553
- (0, import_node_path16.join)(__dirname, "..", "..", "data"),
16145
+ (0, import_node_path17.join)(__dirname, "..", "..", "data"),
15554
16146
  // edge case: nested resolution
15555
- (0, import_node_path16.join)(process.cwd(), "cli", "data")
16147
+ (0, import_node_path17.join)(process.cwd(), "cli", "data")
15556
16148
  // local dev: running from repo root
15557
16149
  ];
15558
16150
  for (const candidate of candidates) {
15559
- if ((0, import_node_fs22.existsSync)((0, import_node_path16.join)(candidate, "skills"))) {
16151
+ if ((0, import_node_fs24.existsSync)((0, import_node_path17.join)(candidate, "skills"))) {
15560
16152
  return candidate;
15561
16153
  }
15562
16154
  }
@@ -15572,7 +16164,7 @@ function registerInitCommand(program2) {
15572
16164
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
15573
16165
  const force = opts.force ?? false;
15574
16166
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
15575
- const isProject = projectMarkers.some((m) => (0, import_node_fs22.existsSync)(m));
16167
+ const isProject = projectMarkers.some((m) => (0, import_node_fs24.existsSync)(m));
15576
16168
  if (!isProject) {
15577
16169
  printError("No project detected in the current directory.");
15578
16170
  printInfo('Run "verity init" from your project root.');
@@ -15635,21 +16227,21 @@ function registerInitCommand(program2) {
15635
16227
  console.log("");
15636
16228
  printInfo("Installing skills...");
15637
16229
  const dataDir = resolveDataDir();
15638
- const skillsSource = (0, import_node_path16.join)(dataDir, "skills");
16230
+ const skillsSource = (0, import_node_path17.join)(dataDir, "skills");
15639
16231
  const skillsDest = ".claude/skills";
15640
16232
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
15641
16233
  let skillsInstalled = 0;
15642
16234
  for (const skill of skills) {
15643
- const src = (0, import_node_path16.join)(skillsSource, skill);
15644
- const dest = (0, import_node_path16.join)(skillsDest, skill);
15645
- if (!(0, import_node_fs22.existsSync)(src)) {
16235
+ const src = (0, import_node_path17.join)(skillsSource, skill);
16236
+ const dest = (0, import_node_path17.join)(skillsDest, skill);
16237
+ if (!(0, import_node_fs24.existsSync)(src)) {
15646
16238
  printWarn(` Skill data not found: ${skill}`);
15647
16239
  continue;
15648
16240
  }
15649
- if ((0, import_node_fs22.existsSync)(dest) && !force) {
15650
- const srcSkill = (0, import_node_path16.join)(src, "SKILL.md");
15651
- const destSkill = (0, import_node_path16.join)(dest, "SKILL.md");
15652
- if ((0, import_node_fs22.existsSync)(destSkill)) {
16241
+ if ((0, import_node_fs24.existsSync)(dest) && !force) {
16242
+ const srcSkill = (0, import_node_path17.join)(src, "SKILL.md");
16243
+ const destSkill = (0, import_node_path17.join)(dest, "SKILL.md");
16244
+ if ((0, import_node_fs24.existsSync)(destSkill)) {
15653
16245
  try {
15654
16246
  const srcContent = await (0, import_promises12.readFile)(srcSkill, "utf-8");
15655
16247
  const destContent = await (0, import_promises12.readFile)(destSkill, "utf-8");
@@ -15673,6 +16265,7 @@ function registerInitCommand(program2) {
15673
16265
  await writeSettings(hookResult.data);
15674
16266
  printInfo(" Stop hook: verity analyze \u2713");
15675
16267
  printInfo(" Intent hook: verity intent capture \u2713");
16268
+ printInfo(" Baseline hook: verity baseline capture \u2713");
15676
16269
  } else {
15677
16270
  printWarn(` ${hookResult.error}`);
15678
16271
  printInfo(' Run "verity hooks install --force" to overwrite.');
@@ -15685,9 +16278,15 @@ function registerInitCommand(program2) {
15685
16278
  } catch (err) {
15686
16279
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
15687
16280
  }
15688
- const globalVerityDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".verity");
16281
+ const globalVerityDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".verity");
15689
16282
  await (0, import_promises12.mkdir)(globalVerityDir, { recursive: true });
15690
16283
  console.log("");
16284
+ try {
16285
+ await runOptionalAuth();
16286
+ } catch (err) {
16287
+ printWarn(`Authentication step skipped: ${err.message}`);
16288
+ }
16289
+ console.log("");
15691
16290
  printInfo("Verity initialized!");
15692
16291
  console.log("");
15693
16292
  console.log(" Installed:");
@@ -15703,13 +16302,14 @@ function registerInitCommand(program2) {
15703
16302
  console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
15704
16303
  console.log("");
15705
16304
  console.log(" Next step: open this project in Claude Code and run /verity-setup");
16305
+ console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
15706
16306
  console.log("");
15707
16307
  });
15708
16308
  }
15709
16309
 
15710
16310
  // src/commands/uninstall.ts
15711
- var import_node_fs23 = require("node:fs");
15712
- var import_node_path17 = require("node:path");
16311
+ var import_node_fs25 = require("node:fs");
16312
+ var import_node_path18 = require("node:path");
15713
16313
  var SKILL_NAMES = [
15714
16314
  "verity-setup",
15715
16315
  "verity-analyze",
@@ -15728,11 +16328,11 @@ function registerUninstallCommand(program2) {
15728
16328
  const actions = [];
15729
16329
  const skillsRoot = projectPath(".claude/skills");
15730
16330
  for (const name of SKILL_NAMES) {
15731
- const dir = (0, import_node_path17.join)(skillsRoot, name);
15732
- if ((0, import_node_fs23.existsSync)(dir)) {
16331
+ const dir = (0, import_node_path18.join)(skillsRoot, name);
16332
+ if ((0, import_node_fs25.existsSync)(dir)) {
15733
16333
  actions.push({
15734
16334
  label: `Remove .claude/skills/${name}/`,
15735
- apply: () => (0, import_node_fs23.rmSync)(dir, { recursive: true, force: true })
16335
+ apply: () => (0, import_node_fs25.rmSync)(dir, { recursive: true, force: true })
15736
16336
  });
15737
16337
  }
15738
16338
  }
@@ -15746,24 +16346,24 @@ function registerUninstallCommand(program2) {
15746
16346
  });
15747
16347
  }
15748
16348
  const verityDir = projectPath(VERITY_DIR);
15749
- if ((0, import_node_fs23.existsSync)(verityDir)) {
16349
+ if ((0, import_node_fs25.existsSync)(verityDir)) {
15750
16350
  actions.push({
15751
16351
  label: `Remove ${VERITY_DIR}/`,
15752
- apply: () => (0, import_node_fs23.rmSync)(verityDir, { recursive: true, force: true })
16352
+ apply: () => (0, import_node_fs25.rmSync)(verityDir, { recursive: true, force: true })
15753
16353
  });
15754
16354
  }
15755
16355
  if (!keepVerityMd) {
15756
16356
  const verityMd = projectPath(VERITY_MD_FILE);
15757
- if ((0, import_node_fs23.existsSync)(verityMd)) {
16357
+ if ((0, import_node_fs25.existsSync)(verityMd)) {
15758
16358
  actions.push({
15759
16359
  label: `Remove ${VERITY_MD_FILE}`,
15760
- apply: () => (0, import_node_fs23.rmSync)(verityMd, { force: true })
16360
+ apply: () => (0, import_node_fs25.rmSync)(verityMd, { force: true })
15761
16361
  });
15762
16362
  }
15763
16363
  }
15764
16364
  const cleanupEmptyDir = (path) => {
15765
- if ((0, import_node_fs23.existsSync)(path) && (0, import_node_fs23.statSync)(path).isDirectory() && (0, import_node_fs23.readdirSync)(path).length === 0) {
15766
- (0, import_node_fs23.rmdirSync)(path);
16365
+ if ((0, import_node_fs25.existsSync)(path) && (0, import_node_fs25.statSync)(path).isDirectory() && (0, import_node_fs25.readdirSync)(path).length === 0) {
16366
+ (0, import_node_fs25.rmdirSync)(path);
15767
16367
  }
15768
16368
  };
15769
16369
  actions.push({
@@ -15774,11 +16374,11 @@ function registerUninstallCommand(program2) {
15774
16374
  }
15775
16375
  });
15776
16376
  const home = process.env.HOME ?? "";
15777
- const globalVerityDir = (0, import_node_path17.join)(home, ".verity");
15778
- if (purgeGlobal && (0, import_node_fs23.existsSync)(globalVerityDir)) {
16377
+ const globalVerityDir = (0, import_node_path18.join)(home, ".verity");
16378
+ if (purgeGlobal && (0, import_node_fs25.existsSync)(globalVerityDir)) {
15779
16379
  actions.push({
15780
16380
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
15781
- apply: () => (0, import_node_fs23.rmSync)(globalVerityDir, { recursive: true, force: true })
16381
+ apply: () => (0, import_node_fs25.rmSync)(globalVerityDir, { recursive: true, force: true })
15782
16382
  });
15783
16383
  }
15784
16384
  if (actions.length === 0) {
@@ -15972,8 +16572,8 @@ function registerTaskCommands(program2) {
15972
16572
  }
15973
16573
 
15974
16574
  // src/commands/reset.ts
15975
- var import_node_fs24 = require("node:fs");
15976
- var import_node_path18 = require("node:path");
16575
+ var import_node_fs26 = require("node:fs");
16576
+ var import_node_path19 = require("node:path");
15977
16577
  function registerResetCommand(program2) {
15978
16578
  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) => {
15979
16579
  const globals = program2.opts();
@@ -16010,11 +16610,11 @@ function registerResetCommand(program2) {
16010
16610
  }
16011
16611
  const cacheDir = projectPath(CACHE_DIR);
16012
16612
  let purged = 0;
16013
- if ((0, import_node_fs24.existsSync)(cacheDir)) {
16014
- for (const entry of (0, import_node_fs24.readdirSync)(cacheDir)) {
16613
+ if ((0, import_node_fs26.existsSync)(cacheDir)) {
16614
+ for (const entry of (0, import_node_fs26.readdirSync)(cacheDir)) {
16015
16615
  if (entry.startsWith("pending-")) {
16016
16616
  try {
16017
- (0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(cacheDir, entry));
16617
+ (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(cacheDir, entry));
16018
16618
  purged++;
16019
16619
  } catch {
16020
16620
  }
@@ -16029,19 +16629,19 @@ function registerResetCommand(program2) {
16029
16629
  projectPath(`${VERITY_DIR}/.last-analysis`)
16030
16630
  ];
16031
16631
  for (const file of filesToClear) {
16032
- if ((0, import_node_fs24.existsSync)(file)) {
16632
+ if ((0, import_node_fs26.existsSync)(file)) {
16033
16633
  try {
16034
- (0, import_node_fs24.writeFileSync)(file, "");
16634
+ (0, import_node_fs26.writeFileSync)(file, "");
16035
16635
  } catch {
16036
16636
  }
16037
16637
  }
16038
16638
  }
16039
16639
  if (opts.all) {
16040
16640
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
16041
- if ((0, import_node_fs24.existsSync)(logsDir)) {
16042
- for (const entry of (0, import_node_fs24.readdirSync)(logsDir)) {
16641
+ if ((0, import_node_fs26.existsSync)(logsDir)) {
16642
+ for (const entry of (0, import_node_fs26.readdirSync)(logsDir)) {
16043
16643
  try {
16044
- (0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(logsDir, entry));
16644
+ (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(logsDir, entry));
16045
16645
  } catch {
16046
16646
  }
16047
16647
  }
@@ -16300,7 +16900,7 @@ function registerRunCommand(program2) {
16300
16900
 
16301
16901
  // src/lib/telemetry.ts
16302
16902
  var import_promises13 = require("node:fs/promises");
16303
- var import_node_path19 = require("node:path");
16903
+ var import_node_path20 = require("node:path");
16304
16904
  var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
16305
16905
  var GITIGNORE_FILE = ".gitignore";
16306
16906
  var GITIGNORE_ENTRY = ".claude/settings.local.json";
@@ -16338,7 +16938,7 @@ async function readSettingsLocal() {
16338
16938
  }
16339
16939
  async function writeSettingsLocal(settings) {
16340
16940
  const file = projectPath(SETTINGS_LOCAL_FILE2);
16341
- await (0, import_promises13.mkdir)((0, import_node_path19.dirname)(file), { recursive: true });
16941
+ await (0, import_promises13.mkdir)((0, import_node_path20.dirname)(file), { recursive: true });
16342
16942
  await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
16343
16943
  }
16344
16944
  async function ensureGitignore() {
@@ -16434,7 +17034,7 @@ function registerTelemetryCommands(program2) {
16434
17034
  }
16435
17035
 
16436
17036
  // src/cli.ts
16437
- program.name("verity").description("CLI for Verity quality gate service").version("0.24.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
17037
+ program.name("verity").description("CLI for Verity quality gate service").version("0.25.0-experimental.56ef7d8").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16438
17038
  registerAuthCommands(program);
16439
17039
  registerHooksCommands(program);
16440
17040
  registerIntentCommands(program);
@@ -16443,6 +17043,7 @@ registerConfigCommands(program);
16443
17043
  registerStatusCommand(program);
16444
17044
  registerFeedbackCommand(program);
16445
17045
  registerAnalyzeCommand(program);
17046
+ registerBaselineCommands(program);
16446
17047
  registerReviewCommand(program);
16447
17048
  registerGuardCommand(program);
16448
17049
  registerInitCommand(program);