@codacy/verity-cli 0.24.0-experimental.b2914b9 → 0.24.0-experimental.bdf0db7

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
@@ -10326,8 +10326,8 @@ var {
10326
10326
 
10327
10327
  // src/commands/auth.ts
10328
10328
  var import_promises3 = require("node:fs/promises");
10329
- var import_node_child_process3 = require("node:child_process");
10330
- var import_node_path2 = require("node:path");
10329
+ var import_node_child_process4 = require("node:child_process");
10330
+ var import_node_path3 = require("node:path");
10331
10331
 
10332
10332
  // src/lib/auth.ts
10333
10333
  var import_promises = require("node:fs/promises");
@@ -10379,6 +10379,7 @@ var MAX_PLAN_FILES = 3;
10379
10379
  var MAX_PLAN_FILE_BYTES = 10240;
10380
10380
  var MAX_INTENT_CHARS = 2e3;
10381
10381
  var SNAPSHOT_DIR = `${VERITY_DIR}/.snapshot`;
10382
+ var BASELINE_DIR = `${VERITY_DIR}/.baseline`;
10382
10383
  var CONVERSATION_BUFFER_FILE = `${VERITY_DIR}/.conversation-buffer`;
10383
10384
  var CONVERSATION_MAX_ENTRIES = 10;
10384
10385
  var CONVERSATION_WINDOW_MINUTES = 15;
@@ -10474,7 +10475,11 @@ var SECURITY_PATTERNS = [
10474
10475
  /Dockerfile/
10475
10476
  ];
10476
10477
  var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10477
- var DEFAULT_SERVICE_URL = "https://yykkfdexzljmmkklpgyz.supabase.co".length > 0 ? "https://yykkfdexzljmmkklpgyz.supabase.co" : PROD_SERVICE_URL;
10478
+ var DEFAULT_SERVICE_URL = "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1".length > 0 ? "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1" : PROD_SERVICE_URL;
10479
+ var GITHUB_CLIENT_ID = "Ov23liBpj42KMTtUtN10";
10480
+ var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
10481
+ var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
10482
+ var GITHUB_OAUTH_SCOPES = "repo user:email";
10478
10483
 
10479
10484
  // src/lib/auth.ts
10480
10485
  async function resolveToken(flagToken) {
@@ -10640,7 +10645,8 @@ async function apiRequest(options) {
10640
10645
  timeout = 9e4,
10641
10646
  cmd = "unknown",
10642
10647
  retry = false,
10643
- encodeBody = false
10648
+ encodeBody = false,
10649
+ extraHeaders
10644
10650
  } = options;
10645
10651
  const url = `${serviceUrl}${path}`;
10646
10652
  const headers = {
@@ -10649,6 +10655,9 @@ async function apiRequest(options) {
10649
10655
  if (token) {
10650
10656
  headers["Authorization"] = `Bearer ${token}`;
10651
10657
  }
10658
+ if (extraHeaders) {
10659
+ Object.assign(headers, extraHeaders);
10660
+ }
10652
10661
  const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
10653
10662
  if (testMockScenario) {
10654
10663
  headers["X-Verity-Mock-Scenario"] = testMockScenario;
@@ -10739,6 +10748,324 @@ function analyzeRequest(options) {
10739
10748
  });
10740
10749
  }
10741
10750
 
10751
+ // src/lib/provider-auth.ts
10752
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10753
+ var form = (fields) => new URLSearchParams(fields).toString();
10754
+ async function githubDeviceFlow() {
10755
+ const override = process.env.VERITY_PROVIDER_TOKEN;
10756
+ if (override) return { ok: true, data: override };
10757
+ let dc;
10758
+ try {
10759
+ const res = await fetch(GITHUB_DEVICE_CODE_URL, {
10760
+ method: "POST",
10761
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
10762
+ body: form({ client_id: GITHUB_CLIENT_ID, scope: GITHUB_OAUTH_SCOPES })
10763
+ });
10764
+ if (!res.ok) {
10765
+ return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
10766
+ }
10767
+ dc = await res.json();
10768
+ } catch (err) {
10769
+ return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
10770
+ }
10771
+ if (!dc.device_code || !dc.user_code) {
10772
+ return {
10773
+ ok: false,
10774
+ error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
10775
+ };
10776
+ }
10777
+ printInfo("");
10778
+ printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
10779
+ printInfo(`And enter the code: ${dc.user_code}`);
10780
+ printInfo("Waiting for authorization\u2026");
10781
+ const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
10782
+ let interval = dc.interval || 5;
10783
+ while (Date.now() < deadline) {
10784
+ await sleep(interval * 1e3);
10785
+ let data;
10786
+ try {
10787
+ const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
10788
+ method: "POST",
10789
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
10790
+ body: form({
10791
+ client_id: GITHUB_CLIENT_ID,
10792
+ device_code: dc.device_code,
10793
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
10794
+ })
10795
+ });
10796
+ data = await res.json().catch(() => ({}));
10797
+ } catch {
10798
+ continue;
10799
+ }
10800
+ if (data.access_token) return { ok: true, data: data.access_token };
10801
+ switch (data.error) {
10802
+ case "authorization_pending":
10803
+ break;
10804
+ case "slow_down":
10805
+ interval += 5;
10806
+ break;
10807
+ case "access_denied":
10808
+ return { ok: false, error: "Authorization was denied on GitHub." };
10809
+ case "expired_token":
10810
+ return { ok: false, error: "The authorization code expired. Re-run register." };
10811
+ default:
10812
+ if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
10813
+ }
10814
+ }
10815
+ return { ok: false, error: "Timed out waiting for GitHub authorization." };
10816
+ }
10817
+
10818
+ // src/lib/git.ts
10819
+ var import_node_child_process3 = require("node:child_process");
10820
+ var import_node_fs2 = require("node:fs");
10821
+ var import_node_path2 = require("node:path");
10822
+ function resolveFile(relpath) {
10823
+ if ((0, import_node_fs2.existsSync)(relpath)) return relpath;
10824
+ if ((0, import_node_fs2.existsSync)(".claude/worktrees")) {
10825
+ try {
10826
+ const entries = (0, import_node_fs2.readdirSync)(".claude/worktrees", { withFileTypes: true });
10827
+ for (const entry of entries) {
10828
+ if (!entry.isDirectory()) continue;
10829
+ const candidate = (0, import_node_path2.join)(".claude/worktrees", entry.name, relpath);
10830
+ if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
10831
+ }
10832
+ } catch {
10833
+ }
10834
+ }
10835
+ return null;
10836
+ }
10837
+ function execGit(cmd) {
10838
+ try {
10839
+ return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10840
+ } catch {
10841
+ return "";
10842
+ }
10843
+ }
10844
+ function splitLines(s) {
10845
+ return s.split("\n").filter((l) => l.length > 0);
10846
+ }
10847
+ var SHA_RE = /^[0-9a-f]{40}$/;
10848
+ function readBaselineSha() {
10849
+ if (!(0, import_node_fs2.existsSync)(BASELINE_SHA_FILE)) return null;
10850
+ let sha;
10851
+ try {
10852
+ sha = (0, import_node_fs2.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
10853
+ } catch {
10854
+ return null;
10855
+ }
10856
+ if (!SHA_RE.test(sha)) return null;
10857
+ const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
10858
+ if (!reachable) {
10859
+ try {
10860
+ (0, import_node_fs2.unlinkSync)(BASELINE_SHA_FILE);
10861
+ } catch {
10862
+ }
10863
+ return null;
10864
+ }
10865
+ return sha;
10866
+ }
10867
+ function writeBaselineSha(sha) {
10868
+ if (!SHA_RE.test(sha)) return;
10869
+ try {
10870
+ (0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(BASELINE_SHA_FILE), { recursive: true });
10871
+ (0, import_node_fs2.writeFileSync)(BASELINE_SHA_FILE, sha);
10872
+ } catch {
10873
+ }
10874
+ }
10875
+ function getChangedFiles() {
10876
+ const sets = /* @__PURE__ */ new Set();
10877
+ let hasRecentCommitFiles = false;
10878
+ for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
10879
+ for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
10880
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
10881
+ const baseline = readBaselineSha();
10882
+ if (baseline) {
10883
+ const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
10884
+ if (committed.length > 0) {
10885
+ hasRecentCommitFiles = true;
10886
+ for (const f of committed) sets.add(f);
10887
+ }
10888
+ } else {
10889
+ const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
10890
+ const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
10891
+ const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
10892
+ const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
10893
+ if (commitAge < 120 && !hasUnstaged && !hasStaged) {
10894
+ const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
10895
+ if (recentFiles.length > 0) {
10896
+ hasRecentCommitFiles = true;
10897
+ for (const f of recentFiles) sets.add(f);
10898
+ }
10899
+ }
10900
+ }
10901
+ for (const f of getWorktreeFiles()) sets.add(f);
10902
+ const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10903
+ return { files: filtered, hasRecentCommitFiles };
10904
+ }
10905
+ function getStagedFiles() {
10906
+ return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10907
+ }
10908
+ function getDirtyFiles() {
10909
+ const set = /* @__PURE__ */ new Set();
10910
+ for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
10911
+ for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
10912
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
10913
+ return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10914
+ }
10915
+ function showContentAtRef(ref, repoRelPath) {
10916
+ if (!ref || ref === "no-git") return null;
10917
+ const normalizedPath = repoRelPath.replace(/\\/g, "/");
10918
+ try {
10919
+ return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
10920
+ encoding: "utf-8",
10921
+ maxBuffer: 64 * 1024 * 1024,
10922
+ stdio: ["pipe", "pipe", "pipe"]
10923
+ });
10924
+ } catch {
10925
+ return null;
10926
+ }
10927
+ }
10928
+ function getPushRangeFiles() {
10929
+ const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10930
+ const resolvers = [
10931
+ () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
10932
+ () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
10933
+ () => {
10934
+ const branch = execGit("git rev-parse --abbrev-ref HEAD");
10935
+ return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
10936
+ }
10937
+ ];
10938
+ for (const resolve of resolvers) {
10939
+ const range = resolve();
10940
+ if (range) return { files: diff(range), range };
10941
+ }
10942
+ const baseline = readBaselineSha();
10943
+ if (baseline) {
10944
+ const files = diff(`${baseline}..HEAD`);
10945
+ if (files.length > 0) return { files, range: `${baseline}..HEAD` };
10946
+ }
10947
+ const last = diff("HEAD~1..HEAD");
10948
+ return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
10949
+ }
10950
+ function getPushRangeMessages() {
10951
+ const { range } = getPushRangeFiles();
10952
+ if (!range) return "";
10953
+ return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
10954
+ }
10955
+ function getWorktreeFiles() {
10956
+ const result = [];
10957
+ const worktreeDir = ".claude/worktrees";
10958
+ if (!(0, import_node_fs2.existsSync)(worktreeDir)) return result;
10959
+ try {
10960
+ const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
10961
+ const entries = (0, import_node_fs2.readdirSync)(worktreeDir, { withFileTypes: true });
10962
+ for (const entry of entries) {
10963
+ if (!entry.isDirectory()) continue;
10964
+ const wtDir = (0, import_node_path2.join)(worktreeDir, entry.name);
10965
+ scanDir(wtDir, wtDir, fiveMinAgo, result);
10966
+ }
10967
+ } catch {
10968
+ }
10969
+ return result;
10970
+ }
10971
+ function scanDir(baseDir, dir, minMtime, result) {
10972
+ try {
10973
+ const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
10974
+ for (const entry of entries) {
10975
+ const fullPath = (0, import_node_path2.join)(dir, entry.name);
10976
+ if (entry.isDirectory()) {
10977
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
10978
+ scanDir(baseDir, fullPath, minMtime, result);
10979
+ } else if (entry.isFile()) {
10980
+ const ext = (0, import_node_path2.extname)(entry.name).slice(1);
10981
+ if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
10982
+ try {
10983
+ const stat3 = (0, import_node_fs2.statSync)(fullPath);
10984
+ if (stat3.mtimeMs >= minMtime) {
10985
+ const relPath = fullPath.slice(baseDir.length + 1);
10986
+ result.push(relPath);
10987
+ }
10988
+ } catch {
10989
+ }
10990
+ }
10991
+ }
10992
+ } catch {
10993
+ }
10994
+ }
10995
+ function filterAnalyzable(files) {
10996
+ return files.filter((f) => {
10997
+ const ext = (0, import_node_path2.extname)(f).slice(1);
10998
+ return ANALYZABLE_EXTENSIONS.has(ext);
10999
+ });
11000
+ }
11001
+ function filterReviewable(files) {
11002
+ return files.filter((f) => {
11003
+ const ext = (0, import_node_path2.extname)(f).slice(1);
11004
+ if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
11005
+ if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
11006
+ const basename2 = f.split("/").pop() ?? "";
11007
+ if (REVIEWABLE_FILENAMES.has(basename2)) return true;
11008
+ if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
11009
+ return false;
11010
+ });
11011
+ }
11012
+ function filterSecurity(files) {
11013
+ return files.filter(
11014
+ (f) => SECURITY_PATTERNS.some((p) => p.test(f))
11015
+ );
11016
+ }
11017
+ function getCurrentCommit() {
11018
+ return execGit("git rev-parse HEAD") || "no-git";
11019
+ }
11020
+ function detectProvider(host) {
11021
+ const h = host.toLowerCase();
11022
+ if (h.includes("github")) return "github";
11023
+ if (h.includes("gitlab")) return "gitlab";
11024
+ if (h.includes("bitbucket")) return "bitbucket";
11025
+ return "unknown";
11026
+ }
11027
+ function parseRemote(raw) {
11028
+ if (!raw || typeof raw !== "string") return null;
11029
+ let s = raw.trim();
11030
+ if (!s) return null;
11031
+ let host = "";
11032
+ let path = "";
11033
+ const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
11034
+ if (scp) {
11035
+ host = scp[1];
11036
+ path = scp[2];
11037
+ } else {
11038
+ s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
11039
+ s = s.replace(/^[^/@]+@/, "");
11040
+ const slash = s.indexOf("/");
11041
+ if (slash === -1) return null;
11042
+ host = s.slice(0, slash);
11043
+ path = s.slice(slash + 1);
11044
+ }
11045
+ host = host.toLowerCase().trim();
11046
+ path = path.replace(/\/+$/, "").replace(/\.git$/, "");
11047
+ if (!host || !path) return null;
11048
+ const segments = path.split("/").filter(Boolean);
11049
+ if (segments.length < 2) return null;
11050
+ const owner = segments[0];
11051
+ const repo = segments[segments.length - 1];
11052
+ if (!owner || !repo) return null;
11053
+ return {
11054
+ host,
11055
+ owner,
11056
+ repo,
11057
+ provider: detectProvider(host),
11058
+ orgUrl: `https://${host}/${owner}`,
11059
+ orgName: owner
11060
+ };
11061
+ }
11062
+ function listTrackedFiles() {
11063
+ const set = /* @__PURE__ */ new Set();
11064
+ for (const f of splitLines(execGit("git ls-files"))) set.add(f);
11065
+ for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
11066
+ return Array.from(set);
11067
+ }
11068
+
10742
11069
  // src/commands/auth.ts
10743
11070
  function registerAuthCommands(program2) {
10744
11071
  const auth = program2.command("auth").description("Manage project authentication");
@@ -10748,35 +11075,65 @@ function registerAuthCommands(program2) {
10748
11075
  let remote = opts.remote;
10749
11076
  if (!remote) {
10750
11077
  try {
10751
- remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
11078
+ remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10752
11079
  } catch {
10753
11080
  printError("No git remote found. Use --remote to specify one.");
10754
11081
  process.exit(1);
10755
11082
  }
10756
11083
  }
11084
+ const parsed = parseRemote(remote);
11085
+ if (!parsed) {
11086
+ printError(`Could not parse git remote: ${remote}`);
11087
+ process.exit(1);
11088
+ }
11089
+ if (parsed.provider !== "github") {
11090
+ printError(`Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.`);
11091
+ process.exit(1);
11092
+ }
11093
+ const providerAuth = await githubDeviceFlow();
11094
+ if (!providerAuth.ok) {
11095
+ printError(providerAuth.error);
11096
+ process.exit(1);
11097
+ }
11098
+ const providerToken = providerAuth.data;
10757
11099
  const result = await apiRequest({
10758
11100
  method: "POST",
10759
11101
  path: "/auth/register",
10760
11102
  serviceUrl,
10761
11103
  body: { project_name: opts.project, git_remote_url: remote },
11104
+ extraHeaders: { "X-Provider-Token": providerToken },
10762
11105
  verbose: globals.verbose
10763
11106
  });
10764
11107
  if (!result.ok) {
10765
11108
  printError(result.error);
10766
11109
  process.exit(1);
10767
11110
  }
10768
- const { project_id, token, service_url } = result.data;
11111
+ const { project_id, token, service_url, user } = result.data;
10769
11112
  await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
10770
- await (0, import_promises3.writeFile)(CREDENTIALS_FILE, `token: ${token}
11113
+ await (0, import_promises3.writeFile)(
11114
+ CREDENTIALS_FILE,
11115
+ `token: ${token}
10771
11116
  service_url: ${service_url}
10772
- `);
11117
+ provider_token: ${providerToken}
11118
+ `,
11119
+ { mode: 384 }
11120
+ );
11121
+ await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
11122
+ });
10773
11123
  try {
10774
- await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
10775
- await (0, import_promises3.appendFile)(GLOBAL_CREDENTIALS_FILE, `${remote} token: ${token}
10776
- `);
11124
+ await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
11125
+ await (0, import_promises3.appendFile)(
11126
+ GLOBAL_CREDENTIALS_FILE,
11127
+ `${remote} token: ${token}
11128
+ ${remote} provider_token: ${providerToken}
11129
+ `
11130
+ );
11131
+ await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
11132
+ });
10777
11133
  } catch {
10778
11134
  }
10779
11135
  printInfo(`Project registered: ${project_id}`);
11136
+ if (user?.email) printInfo(`Authenticated as: ${user.email}`);
10780
11137
  printJson({ project_id, service_url });
10781
11138
  });
10782
11139
  auth.command("verify").description("Verify the current token is valid").action(async () => {
@@ -10810,7 +11167,7 @@ service_url: ${service_url}
10810
11167
  let remote = opts.remote;
10811
11168
  if (!remote) {
10812
11169
  try {
10813
- remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
11170
+ remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
10814
11171
  } catch {
10815
11172
  printError("No git remote found. Use --remote to specify one.");
10816
11173
  process.exit(1);
@@ -10838,7 +11195,7 @@ service_url: ${service_url}
10838
11195
 
10839
11196
  // src/lib/hooks.ts
10840
11197
  var import_promises4 = require("node:fs/promises");
10841
- var import_node_path3 = require("node:path");
11198
+ var import_node_path4 = require("node:path");
10842
11199
  var VERITY_STOP_HOOK = {
10843
11200
  type: "command",
10844
11201
  command: "verity analyze",
@@ -10849,6 +11206,10 @@ var VERITY_INTENT_HOOK = {
10849
11206
  type: "command",
10850
11207
  command: "verity intent capture"
10851
11208
  };
11209
+ var VERITY_BASELINE_HOOK = {
11210
+ type: "command",
11211
+ command: "verity baseline capture"
11212
+ };
10852
11213
  var GUARD_TIMEOUT = 300;
10853
11214
  function buildGuardHook(on) {
10854
11215
  return {
@@ -10861,9 +11222,13 @@ function buildGuardHook(on) {
10861
11222
  var VERITY_STOP_RE = /(?:^|[\/\s"'])verity\s+analyze\b/;
10862
11223
  var VERITY_INTENT_RE = /(?:^|[\/\s"'])verity\s+intent\s+capture\b/;
10863
11224
  var VERITY_GUARD_RE = /(?:^|[\/\s"'])verity\s+guard\b/;
11225
+ var VERITY_BASELINE_RE = /(?:^|[\/\s"'])verity\s+baseline\s+capture\b/;
10864
11226
  function isVerityGuardHook(entry) {
10865
11227
  return VERITY_GUARD_RE.test(entry.command ?? "");
10866
11228
  }
11229
+ function isVerityBaselineHook(entry) {
11230
+ return VERITY_BASELINE_RE.test(entry.command ?? "");
11231
+ }
10867
11232
  var LEGACY_STOP_RE = /(?:^|[\/\s"'])gate\s+analyze\b/;
10868
11233
  var LEGACY_INTENT_RE = /(?:^|[\/\s"'])gate\s+intent\s+capture\b/;
10869
11234
  function isVerityStopHook(entry) {
@@ -10875,7 +11240,7 @@ function isVerityIntentHook(entry) {
10875
11240
  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");
10876
11241
  }
10877
11242
  function isVerityHook(entry) {
10878
- return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry);
11243
+ return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry);
10879
11244
  }
10880
11245
  function isCurrentVerityStopHook(entry) {
10881
11246
  const c = entry.command ?? "";
@@ -10886,7 +11251,7 @@ function isCurrentVerityIntentHook(entry) {
10886
11251
  return VERITY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh");
10887
11252
  }
10888
11253
  function isCurrentVerityHook(entry) {
10889
- return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry);
11254
+ return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry);
10890
11255
  }
10891
11256
  function settingsHasLegacyHook(settings) {
10892
11257
  for (const groups of Object.values(settings.hooks ?? {})) {
@@ -10928,16 +11293,18 @@ async function readAllSettings() {
10928
11293
  async function checkAllVerityHooks() {
10929
11294
  let stop = false;
10930
11295
  let intent = false;
11296
+ let baseline = false;
10931
11297
  let guard = false;
10932
11298
  let guardOn = [];
10933
11299
  for (const settings of await readAllSettings()) {
10934
11300
  const r = checkVerityHooks(settings);
10935
11301
  stop = stop || r.stop;
10936
11302
  intent = intent || r.intent;
11303
+ baseline = baseline || r.baseline;
10937
11304
  guard = guard || r.guard;
10938
11305
  if (r.guardOn.length > guardOn.length) guardOn = r.guardOn;
10939
11306
  }
10940
- return { stop, intent, guard, guardOn };
11307
+ return { stop, intent, baseline, guard, guardOn };
10941
11308
  }
10942
11309
  async function checkExternalVerityHooks() {
10943
11310
  let stop = false;
@@ -10960,12 +11327,14 @@ async function checkExternalVerityHooks() {
10960
11327
  async function checkAllVerityHooksDetailed() {
10961
11328
  let stop = false;
10962
11329
  let intent = false;
11330
+ let baseline = false;
10963
11331
  let hasCurrent = false;
10964
11332
  let hasLegacy = false;
10965
11333
  for (const settings of await readAllSettings()) {
10966
11334
  const r = checkVerityHooks(settings);
10967
11335
  stop = stop || r.stop;
10968
11336
  intent = intent || r.intent;
11337
+ baseline = baseline || r.baseline;
10969
11338
  for (const groups of Object.values(settings.hooks ?? {})) {
10970
11339
  for (const g of groups ?? []) {
10971
11340
  for (const h of g.hooks ?? []) {
@@ -10975,22 +11344,22 @@ async function checkAllVerityHooksDetailed() {
10975
11344
  }
10976
11345
  }
10977
11346
  }
10978
- return { stop, intent, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
11347
+ return { stop, intent, baseline, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
10979
11348
  }
10980
11349
  async function writeSettings(settings) {
10981
- await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
11350
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
10982
11351
  await (0, import_promises4.writeFile)(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
10983
11352
  }
10984
11353
  async function readSettingsAt(root) {
10985
11354
  try {
10986
- return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
11355
+ return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
10987
11356
  } catch {
10988
11357
  return {};
10989
11358
  }
10990
11359
  }
10991
11360
  async function writeSettingsAt(root, settings) {
10992
- const file = (0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE);
10993
- await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(file), { recursive: true });
11361
+ const file = (0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE);
11362
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
10994
11363
  await (0, import_promises4.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
10995
11364
  }
10996
11365
  async function hasLegacyHooksAt(root) {
@@ -11018,6 +11387,10 @@ function checkVerityHooks(settings) {
11018
11387
  const hasIntent = intentGroups.some(
11019
11388
  (g) => g.hooks?.some((h) => isCurrentVerityIntentHook(h))
11020
11389
  );
11390
+ const sessionStartGroups = hooks["SessionStart"] ?? [];
11391
+ const hasBaseline = sessionStartGroups.some(
11392
+ (g) => g.hooks?.some((h) => isVerityBaselineHook(h))
11393
+ );
11021
11394
  let guard = false;
11022
11395
  let guardOn = [];
11023
11396
  for (const g of hooks["PreToolUse"] ?? []) {
@@ -11030,16 +11403,17 @@ function checkVerityHooks(settings) {
11030
11403
  }
11031
11404
  }
11032
11405
  }
11033
- return { stop: hasStop, intent: hasIntent, guard, guardOn };
11406
+ return { stop: hasStop, intent: hasIntent, baseline: hasBaseline, guard, guardOn };
11034
11407
  }
11035
- function installVerityHooks(settings, force, externalPresent = { stop: false, intent: false }) {
11408
+ function installVerityHooks(settings, force, externalPresent = { stop: false, intent: false, baseline: false }) {
11036
11409
  const local = checkVerityHooks(settings);
11037
11410
  const existing = {
11038
11411
  stop: local.stop || externalPresent.stop,
11039
- intent: local.intent || externalPresent.intent
11412
+ intent: local.intent || externalPresent.intent,
11413
+ baseline: local.baseline || (externalPresent.baseline ?? false)
11040
11414
  };
11041
11415
  const hasLocalLegacy = settingsHasLegacyHook(settings);
11042
- if (existing.stop && existing.intent && !force && !hasLocalLegacy) {
11416
+ if (existing.stop && existing.intent && existing.baseline && !force && !hasLocalLegacy) {
11043
11417
  return { ok: false, error: "Verity hooks already installed. Use --force to overwrite." };
11044
11418
  }
11045
11419
  const newSettings = { ...settings };
@@ -11047,7 +11421,7 @@ function installVerityHooks(settings, force, externalPresent = { stop: false, in
11047
11421
  newSettings.hooks = {};
11048
11422
  }
11049
11423
  const shouldStrip = force ? isVerityHook : isLegacyHook;
11050
- for (const key of ["Stop", "UserPromptSubmit"]) {
11424
+ for (const key of ["Stop", "UserPromptSubmit", "SessionStart"]) {
11051
11425
  const groups = newSettings.hooks[key];
11052
11426
  if (groups) {
11053
11427
  newSettings.hooks[key] = groups.map((g) => ({
@@ -11074,6 +11448,15 @@ function installVerityHooks(settings, force, externalPresent = { stop: false, in
11074
11448
  if (!newSettings.hooks["UserPromptSubmit"]) newSettings.hooks["UserPromptSubmit"] = [];
11075
11449
  newSettings.hooks["UserPromptSubmit"].push({ hooks: [VERITY_INTENT_HOOK] });
11076
11450
  }
11451
+ if (!existing.baseline) {
11452
+ if (!newSettings.hooks["SessionStart"]) {
11453
+ newSettings.hooks["SessionStart"] = [];
11454
+ }
11455
+ newSettings.hooks["SessionStart"].push({ hooks: [VERITY_BASELINE_HOOK] });
11456
+ } else if (force && local.baseline) {
11457
+ if (!newSettings.hooks["SessionStart"]) newSettings.hooks["SessionStart"] = [];
11458
+ newSettings.hooks["SessionStart"].push({ hooks: [VERITY_BASELINE_HOOK] });
11459
+ }
11077
11460
  return { ok: true, data: newSettings };
11078
11461
  }
11079
11462
  function removeVerityHooks(settings) {
@@ -11107,6 +11490,7 @@ function reconcileMomentHooks(settings, moments, externalPresent = {
11107
11490
  (s.hooks[event] ??= []).push(group);
11108
11491
  };
11109
11492
  if (!externalPresent.intent) push("UserPromptSubmit", { hooks: [VERITY_INTENT_HOOK] });
11493
+ push("SessionStart", { hooks: [VERITY_BASELINE_HOOK] });
11110
11494
  if (moments.includes("stop") && !externalPresent.stop) {
11111
11495
  push("Stop", { hooks: [VERITY_STOP_HOOK] });
11112
11496
  }
@@ -11155,7 +11539,7 @@ function registerHooksCommands(program2) {
11155
11539
  return;
11156
11540
  }
11157
11541
  const detail = await checkAllVerityHooksDetailed();
11158
- const present = { stop: detail.stop, intent: detail.intent };
11542
+ const present = { stop: detail.stop, intent: detail.intent, baseline: detail.baseline };
11159
11543
  if (detail.legacyOnly) {
11160
11544
  printInfo("Found a legacy GATE.md hook \u2014 upgrading it to Verity...");
11161
11545
  const settings2 = removeVerityHooks(await readSettings());
@@ -11168,11 +11552,12 @@ function registerHooksCommands(program2) {
11168
11552
  printInfo("Verity hooks installed in .claude/settings.json (legacy hook removed)");
11169
11553
  printInfo(" Stop hook: verity analyze");
11170
11554
  printInfo(" UserPromptSubmit hook: verity intent capture");
11555
+ printInfo(" SessionStart hook: verity baseline capture");
11171
11556
  return;
11172
11557
  }
11173
11558
  const settings = await readSettings();
11174
11559
  const hasLocalLegacy = settingsHasLegacyHook(settings);
11175
- if (!force && present.stop && present.intent && !hasLocalLegacy) {
11560
+ if (!force && present.stop && present.intent && present.baseline && !hasLocalLegacy) {
11176
11561
  printInfo("Verity hooks already installed (found in .claude/settings.json, settings.local.json, or your global settings).");
11177
11562
  printInfo("Use --force to rewrite the project settings.json copy.");
11178
11563
  return;
@@ -11186,11 +11571,13 @@ function registerHooksCommands(program2) {
11186
11571
  printInfo("Verity hooks installed in .claude/settings.json");
11187
11572
  printInfo(" Stop hook: verity analyze");
11188
11573
  printInfo(" UserPromptSubmit hook: verity intent capture");
11574
+ printInfo(" SessionStart hook: verity baseline capture");
11189
11575
  });
11190
11576
  hooks.command("check").description("Check if Verity hooks are installed").action(async () => {
11191
11577
  const status = await checkAllVerityHooks();
11192
11578
  printInfo(`Stop hook (verity analyze): ${status.stop ? "installed" : "not installed"}`);
11193
11579
  printInfo(`Intent hook (verity intent capture): ${status.intent ? "installed" : "not installed"}`);
11580
+ printInfo(`Baseline hook (verity baseline capture): ${status.baseline ? "installed" : "not installed"}`);
11194
11581
  const gates = status.guard ? status.guardOn.join(", ") : "none";
11195
11582
  printInfo(`Git-moment gate (verity guard): ${status.guard ? `installed [${gates}]` : "not installed"}`);
11196
11583
  if (!status.stop && !status.guard) {
@@ -11213,8 +11600,8 @@ var import_node_crypto2 = require("node:crypto");
11213
11600
 
11214
11601
  // src/lib/conversation-buffer.ts
11215
11602
  var import_promises5 = require("node:fs/promises");
11216
- var import_node_fs2 = require("node:fs");
11217
- var import_node_child_process4 = require("node:child_process");
11603
+ var import_node_fs3 = require("node:fs");
11604
+ var import_node_child_process5 = require("node:child_process");
11218
11605
  function stripImageReferences(text) {
11219
11606
  return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
11220
11607
  }
@@ -11243,20 +11630,33 @@ async function appendToConversationBuffer(prompt, sessionId) {
11243
11630
  } catch {
11244
11631
  }
11245
11632
  }
11246
- async function readAndClearConversationBuffer() {
11633
+ async function readAndClearConversationBuffer(currentSessionId) {
11247
11634
  try {
11248
- if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
11635
+ if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
11249
11636
  const entries = await readBufferEntries();
11250
- await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11251
- });
11252
- if (entries.length > 0) {
11637
+ let mine = entries;
11638
+ let others = [];
11639
+ if (currentSessionId) {
11640
+ mine = entries.filter((e) => e.session_id === currentSessionId || !e.session_id);
11641
+ others = entries.filter((e) => e.session_id && e.session_id !== currentSessionId);
11642
+ }
11643
+ if (others.length > 0) {
11644
+ const remaining = others.map((e) => JSON.stringify(e)).join("\n") + "\n";
11645
+ const tmpFile = `${CONVERSATION_BUFFER_FILE}.tmp`;
11646
+ await (0, import_promises5.writeFile)(tmpFile, remaining);
11647
+ await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
11648
+ } else {
11649
+ await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
11650
+ });
11651
+ }
11652
+ if (mine.length > 0) {
11253
11653
  return {
11254
- prompts: entries,
11654
+ prompts: mine,
11255
11655
  recent_commits: getRecentCommitMessages()
11256
11656
  };
11257
11657
  }
11258
11658
  }
11259
- if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
11659
+ if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
11260
11660
  try {
11261
11661
  const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
11262
11662
  await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
@@ -11300,7 +11700,7 @@ async function readBufferEntries() {
11300
11700
  }
11301
11701
  function getRecentCommitMessages() {
11302
11702
  try {
11303
- const output = (0, import_node_child_process4.execSync)(
11703
+ const output = (0, import_node_child_process5.execSync)(
11304
11704
  'git log --since="30 minutes ago" --format="%s" -5',
11305
11705
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
11306
11706
  ).trim();
@@ -11313,8 +11713,8 @@ function getRecentCommitMessages() {
11313
11713
 
11314
11714
  // src/lib/task-context-buffer.ts
11315
11715
  var import_promises6 = require("node:fs/promises");
11316
- var import_node_fs3 = require("node:fs");
11317
- var import_node_path4 = require("node:path");
11716
+ var import_node_fs4 = require("node:fs");
11717
+ var import_node_path5 = require("node:path");
11318
11718
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
11319
11719
  var MAX_BUFFER_BYTES = 500 * 1024;
11320
11720
  var MAX_PROMPT_CHARS = 2e3;
@@ -11353,7 +11753,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
11353
11753
  }
11354
11754
  async function readTaskContextBuffer(taskId) {
11355
11755
  const filePath = bufferPath(taskId);
11356
- if (!(0, import_node_fs3.existsSync)(filePath)) return null;
11756
+ if (!(0, import_node_fs4.existsSync)(filePath)) return null;
11357
11757
  try {
11358
11758
  const content = await (0, import_promises6.readFile)(filePath, "utf-8");
11359
11759
  if (!content.trim()) return null;
@@ -11387,12 +11787,12 @@ async function readTaskContextBuffer(taskId) {
11387
11787
  }
11388
11788
  async function cleanupTaskContextBuffers() {
11389
11789
  try {
11390
- if (!(0, import_node_fs3.existsSync)(TASK_CONTEXT_DIR)) return;
11790
+ if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
11391
11791
  const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
11392
11792
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
11393
11793
  for (const file of files) {
11394
11794
  if (!file.endsWith(".jsonl")) continue;
11395
- const filePath = (0, import_node_path4.join)(TASK_CONTEXT_DIR, file);
11795
+ const filePath = (0, import_node_path5.join)(TASK_CONTEXT_DIR, file);
11396
11796
  try {
11397
11797
  const stats = await (0, import_promises6.stat)(filePath);
11398
11798
  if (stats.mtimeMs < cutoffMs) {
@@ -11406,13 +11806,13 @@ async function cleanupTaskContextBuffers() {
11406
11806
  }
11407
11807
  function bufferPath(taskId) {
11408
11808
  const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
11409
- return (0, import_node_path4.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11809
+ return (0, import_node_path5.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
11410
11810
  }
11411
11811
  async function appendEntry(taskId, entry) {
11412
11812
  try {
11413
11813
  await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
11414
11814
  const filePath = bufferPath(taskId);
11415
- if ((0, import_node_fs3.existsSync)(filePath)) {
11815
+ if ((0, import_node_fs4.existsSync)(filePath)) {
11416
11816
  const stats = await (0, import_promises6.stat)(filePath);
11417
11817
  if (stats.size >= MAX_BUFFER_BYTES) {
11418
11818
  const content = await (0, import_promises6.readFile)(filePath, "utf-8");
@@ -11423,7 +11823,7 @@ async function appendEntry(taskId, entry) {
11423
11823
  }
11424
11824
  }
11425
11825
  const line = JSON.stringify(entry) + "\n";
11426
- const existing = (0, import_node_fs3.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
11826
+ const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
11427
11827
  await (0, import_promises6.writeFile)(filePath, existing + line);
11428
11828
  } catch {
11429
11829
  }
@@ -11431,8 +11831,8 @@ async function appendEntry(taskId, entry) {
11431
11831
 
11432
11832
  // src/lib/memory-retrieval.ts
11433
11833
  var import_promises7 = require("node:fs/promises");
11434
- var import_node_fs4 = require("node:fs");
11435
- var import_node_path5 = require("node:path");
11834
+ var import_node_fs5 = require("node:fs");
11835
+ var import_node_path6 = require("node:path");
11436
11836
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
11437
11837
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
11438
11838
  var DEFAULT_BUDGET_TOKENS = 2e3;
@@ -11525,19 +11925,19 @@ function parseFrontmatter(content) {
11525
11925
  return { fm, body: match[2].trim() };
11526
11926
  }
11527
11927
  async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
11528
- if (!(0, import_node_fs4.existsSync)(memoryDir())) return null;
11928
+ if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
11529
11929
  const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
11530
11930
  const promptTokens = tokenize(promptText);
11531
11931
  const nodes = [];
11532
11932
  for (const domain of DOMAINS) {
11533
- const domainDir = (0, import_node_path5.join)(memoryDir(), domain);
11534
- if (!(0, import_node_fs4.existsSync)(domainDir)) continue;
11933
+ const domainDir = (0, import_node_path6.join)(memoryDir(), domain);
11934
+ if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
11535
11935
  try {
11536
11936
  const files = await (0, import_promises7.readdir)(domainDir);
11537
11937
  for (const file of files) {
11538
11938
  if (!file.endsWith(".md")) continue;
11539
11939
  try {
11540
- const content = await (0, import_promises7.readFile)((0, import_node_path5.join)(domainDir, file), "utf-8");
11940
+ const content = await (0, import_promises7.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
11541
11941
  const { fm, body } = parseFrontmatter(content);
11542
11942
  if (fm.status && fm.status !== "active") continue;
11543
11943
  nodes.push({
@@ -11596,8 +11996,8 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
11596
11996
 
11597
11997
  // src/lib/memory-sync.ts
11598
11998
  var import_promises8 = require("node:fs/promises");
11599
- var import_node_fs5 = require("node:fs");
11600
- var import_node_path6 = require("node:path");
11999
+ var import_node_fs6 = require("node:fs");
12000
+ var import_node_path7 = require("node:path");
11601
12001
  var import_node_crypto = require("node:crypto");
11602
12002
 
11603
12003
  // src/lib/glob-match.ts
@@ -11668,32 +12068,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
11668
12068
  async function ensureMemoryDir() {
11669
12069
  await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
11670
12070
  for (const domain of DOMAINS2) {
11671
- await (0, import_promises8.mkdir)((0, import_node_path6.join)(memoryDir2(), domain), { recursive: true });
12071
+ await (0, import_promises8.mkdir)((0, import_node_path7.join)(memoryDir2(), domain), { recursive: true });
11672
12072
  }
11673
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"))) {
11674
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
12073
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"))) {
12074
+ await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
11675
12075
  }
11676
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "index.md"))) {
11677
- 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");
12076
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "index.md"))) {
12077
+ 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");
11678
12078
  }
11679
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "log.md"))) {
11680
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
12079
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md"))) {
12080
+ await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
11681
12081
  }
11682
12082
  }
11683
12083
  async function buildManifest() {
11684
- if (!(0, import_node_fs5.existsSync)(memoryDir2())) {
12084
+ if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
11685
12085
  return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
11686
12086
  }
11687
12087
  const nodes = [];
11688
12088
  for (const domain of DOMAINS2) {
11689
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11690
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12089
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
12090
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11691
12091
  try {
11692
12092
  const files = await (0, import_promises8.readdir)(domainDir);
11693
12093
  for (const file of files) {
11694
12094
  if (!file.endsWith(".md")) continue;
11695
12095
  const filePath = `${domain}/${file}`;
11696
- const fullPath = (0, import_node_path6.join)(memoryDir2(), filePath);
12096
+ const fullPath = (0, import_node_path7.join)(memoryDir2(), filePath);
11697
12097
  try {
11698
12098
  const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
11699
12099
  const hash = (0, import_node_crypto.createHash)("sha256").update(content).digest("hex").slice(0, 16);
@@ -11706,13 +12106,13 @@ async function buildManifest() {
11706
12106
  }
11707
12107
  let indexHash = null;
11708
12108
  try {
11709
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "index.md"), "utf-8");
12109
+ const indexContent = await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "utf-8");
11710
12110
  indexHash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
11711
12111
  } catch {
11712
12112
  }
11713
12113
  let logLength = 0;
11714
12114
  try {
11715
- const logContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "utf-8");
12115
+ const logContent = await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8");
11716
12116
  logLength = logContent.split("\n").length;
11717
12117
  } catch {
11718
12118
  }
@@ -11723,15 +12123,15 @@ function hashContent(content) {
11723
12123
  }
11724
12124
  async function readOnDiskNodes() {
11725
12125
  const out = /* @__PURE__ */ new Map();
11726
- if (!(0, import_node_fs5.existsSync)(memoryDir2())) return out;
12126
+ if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
11727
12127
  for (const domain of DOMAINS2) {
11728
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11729
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12128
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
12129
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11730
12130
  try {
11731
12131
  for (const file of await (0, import_promises8.readdir)(domainDir)) {
11732
12132
  if (!file.endsWith(".md")) continue;
11733
12133
  try {
11734
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8")));
12134
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8")));
11735
12135
  } catch {
11736
12136
  }
11737
12137
  }
@@ -11777,8 +12177,8 @@ async function computeEditedNodeUploads() {
11777
12177
  const uploads = [];
11778
12178
  for (const [path, prevHash] of prev) {
11779
12179
  if (prevHash == null) continue;
11780
- const full = (0, import_node_path6.join)(memoryDir2(), path);
11781
- if (!(0, import_node_fs5.existsSync)(full)) continue;
12180
+ const full = (0, import_node_path7.join)(memoryDir2(), path);
12181
+ if (!(0, import_node_fs6.existsSync)(full)) continue;
11782
12182
  let content;
11783
12183
  try {
11784
12184
  content = await (0, import_promises8.readFile)(full, "utf-8");
@@ -11814,15 +12214,15 @@ async function applyMemoryWrites(writes, opts = {}) {
11814
12214
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
11815
12215
  for (const n of notes) logLines.push(` - ${n}`);
11816
12216
  try {
11817
- 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";
11818
- await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
12217
+ 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";
12218
+ await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
11819
12219
  } catch {
11820
12220
  }
11821
12221
  await recordSyncedNodePaths();
11822
12222
  return count;
11823
12223
  }
11824
12224
  async function applyOneWrite(write, treePaths) {
11825
- const fullPath = (0, import_node_path6.join)(memoryDir2(), write.path);
12225
+ const fullPath = (0, import_node_path7.join)(memoryDir2(), write.path);
11826
12226
  const notes = [];
11827
12227
  let content = write.content;
11828
12228
  if (treePaths && treePaths.length > 0) {
@@ -11832,7 +12232,7 @@ async function applyOneWrite(write, treePaths) {
11832
12232
  notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
11833
12233
  }
11834
12234
  }
11835
- if ((0, import_node_fs5.existsSync)(fullPath)) {
12235
+ if ((0, import_node_fs6.existsSync)(fullPath)) {
11836
12236
  let existing = "";
11837
12237
  try {
11838
12238
  existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
@@ -11844,7 +12244,7 @@ async function applyOneWrite(write, treePaths) {
11844
12244
  return { written: false, notes };
11845
12245
  }
11846
12246
  }
11847
- await (0, import_promises8.mkdir)((0, import_node_path6.dirname)(fullPath), { recursive: true });
12247
+ await (0, import_promises8.mkdir)((0, import_node_path7.dirname)(fullPath), { recursive: true });
11848
12248
  await (0, import_promises8.writeFile)(fullPath, content);
11849
12249
  return { written: true, notes };
11850
12250
  }
@@ -11885,8 +12285,8 @@ async function regenerateIndex() {
11885
12285
  ];
11886
12286
  let totalNodes = 0;
11887
12287
  for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
11888
- const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
11889
- if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
12288
+ const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
12289
+ if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
11890
12290
  try {
11891
12291
  const files = await (0, import_promises8.readdir)(domainDir);
11892
12292
  const mdFiles = files.filter((f) => f.endsWith(".md"));
@@ -11896,7 +12296,7 @@ async function regenerateIndex() {
11896
12296
  for (const file of mdFiles.sort()) {
11897
12297
  const slug = file.replace(/\.md$/, "");
11898
12298
  try {
11899
- const content = await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
12299
+ const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
11900
12300
  const title = pickFrontmatter(content, "title") ?? slug;
11901
12301
  const kind = pickFrontmatter(content, "kind") ?? "-";
11902
12302
  const confidence = pickFrontmatter(content, "confidence");
@@ -11920,7 +12320,7 @@ async function regenerateIndex() {
11920
12320
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
11921
12321
  }
11922
12322
  const next = lines.join("\n") + "\n";
11923
- const indexPath = (0, import_node_path6.join)(memoryDir2(), "index.md");
12323
+ const indexPath = (0, import_node_path7.join)(memoryDir2(), "index.md");
11924
12324
  let existing = null;
11925
12325
  try {
11926
12326
  existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
@@ -12002,9 +12402,9 @@ function hasLegacyMemoryBlock(text) {
12002
12402
  return findMarker(text, LEGACY_MD_START) !== -1;
12003
12403
  }
12004
12404
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
12005
- const claudeMdPath = (0, import_node_path6.join)(cwd, "CLAUDE.md");
12405
+ const claudeMdPath = (0, import_node_path7.join)(cwd, "CLAUDE.md");
12006
12406
  let existing = "";
12007
- if ((0, import_node_fs5.existsSync)(claudeMdPath)) {
12407
+ if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
12008
12408
  existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
12009
12409
  }
12010
12410
  let startTag = CLAUDE_MD_START;
@@ -12134,7 +12534,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
12134
12534
  `;
12135
12535
 
12136
12536
  // src/commands/intent.ts
12137
- var import_node_fs6 = require("node:fs");
12537
+ var import_node_fs7 = require("node:fs");
12138
12538
  function registerIntentCommands(program2) {
12139
12539
  const intent = program2.command("intent").description("Manage intent capture");
12140
12540
  intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
@@ -12143,7 +12543,7 @@ function registerIntentCommands(program2) {
12143
12543
  process.chdir(repoRoot());
12144
12544
  } catch {
12145
12545
  }
12146
- if (!(0, import_node_fs6.existsSync)(VERITY_DIR)) {
12546
+ if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
12147
12547
  process.exit(0);
12148
12548
  }
12149
12549
  const chunks = [];
@@ -12655,262 +13055,73 @@ function registerFeedbackCommand(program2) {
12655
13055
  printError(urlResult.error);
12656
13056
  process.exit(1);
12657
13057
  }
12658
- const body = {
12659
- run_id: runId,
12660
- pattern_id: patternId,
12661
- action
12662
- };
12663
- if (note) body.note = note;
12664
- if (opts.file) body.file_path = opts.file;
12665
- if (opts.line != null) body.line = opts.line;
12666
- const result = await apiRequest({
12667
- method: "POST",
12668
- path: "/feedback/findings",
12669
- serviceUrl: urlResult.data,
12670
- token: tokenResult.data.token,
12671
- body,
12672
- verbose: globals.verbose
12673
- });
12674
- if (!result.ok) {
12675
- printError(`Couldn't submit finding feedback: ${result.error}`);
12676
- process.exit(1);
12677
- }
12678
- const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
12679
- printInfo(status);
12680
- });
12681
- feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
12682
- if (!message) return;
12683
- const globals = program2.opts();
12684
- await sendGeneralFeedback(message, opts, globals);
12685
- });
12686
- }
12687
- async function sendGeneralFeedback(message, opts, globals) {
12688
- const tokenResult = await resolveToken(globals.token);
12689
- if (!tokenResult.ok) {
12690
- printError(tokenResult.error);
12691
- printInfo(`Your message: ${message}`);
12692
- process.exit(1);
12693
- }
12694
- const urlResult = await resolveServiceUrl(globals.serviceUrl);
12695
- if (!urlResult.ok) {
12696
- printError(urlResult.error);
12697
- printInfo(`Your message: ${message}`);
12698
- process.exit(1);
12699
- }
12700
- const body = { message };
12701
- const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
12702
- const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
12703
- if (sessionId) body.session_id = sessionId;
12704
- if (model) body.agent_model = model;
12705
- const result = await apiRequest({
12706
- method: "POST",
12707
- path: "/feedback",
12708
- serviceUrl: urlResult.data,
12709
- token: tokenResult.data.token,
12710
- body,
12711
- verbose: globals.verbose
12712
- });
12713
- if (!result.ok) {
12714
- printError(`Couldn't send feedback: ${result.error}`);
12715
- printInfo(`Your message: ${message}`);
12716
- process.exit(1);
12717
- }
12718
- printInfo("Thanks, feedback sent!");
12719
- }
12720
-
12721
- // src/commands/analyze.ts
12722
- var import_node_fs18 = require("node:fs");
12723
- var import_node_path13 = require("node:path");
12724
-
12725
- // src/lib/git.ts
12726
- var import_node_child_process5 = require("node:child_process");
12727
- var import_node_fs7 = require("node:fs");
12728
- var import_node_path7 = require("node:path");
12729
- function resolveFile(relpath) {
12730
- if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
12731
- if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
12732
- try {
12733
- const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
12734
- for (const entry of entries) {
12735
- if (!entry.isDirectory()) continue;
12736
- const candidate = (0, import_node_path7.join)(".claude/worktrees", entry.name, relpath);
12737
- if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
12738
- }
12739
- } catch {
12740
- }
12741
- }
12742
- return null;
12743
- }
12744
- function execGit(cmd) {
12745
- try {
12746
- return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
12747
- } catch {
12748
- return "";
12749
- }
12750
- }
12751
- function splitLines(s) {
12752
- return s.split("\n").filter((l) => l.length > 0);
12753
- }
12754
- var SHA_RE = /^[0-9a-f]{40}$/;
12755
- function readBaselineSha() {
12756
- if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
12757
- let sha;
12758
- try {
12759
- sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
12760
- } catch {
12761
- return null;
12762
- }
12763
- if (!SHA_RE.test(sha)) return null;
12764
- const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
12765
- if (!reachable) {
12766
- try {
12767
- (0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
12768
- } catch {
12769
- }
12770
- return null;
12771
- }
12772
- return sha;
12773
- }
12774
- function writeBaselineSha(sha) {
12775
- if (!SHA_RE.test(sha)) return;
12776
- try {
12777
- (0, import_node_fs7.mkdirSync)((0, import_node_path7.dirname)(BASELINE_SHA_FILE), { recursive: true });
12778
- (0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
12779
- } catch {
12780
- }
12781
- }
12782
- function getChangedFiles() {
12783
- const sets = /* @__PURE__ */ new Set();
12784
- let hasRecentCommitFiles = false;
12785
- for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
12786
- for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
12787
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
12788
- const baseline = readBaselineSha();
12789
- if (baseline) {
12790
- const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
12791
- if (committed.length > 0) {
12792
- hasRecentCommitFiles = true;
12793
- for (const f of committed) sets.add(f);
12794
- }
12795
- } else {
12796
- const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
12797
- const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
12798
- const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
12799
- const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
12800
- if (commitAge < 120 && !hasUnstaged && !hasStaged) {
12801
- const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
12802
- if (recentFiles.length > 0) {
12803
- hasRecentCommitFiles = true;
12804
- for (const f of recentFiles) sets.add(f);
12805
- }
12806
- }
12807
- }
12808
- for (const f of getWorktreeFiles()) sets.add(f);
12809
- const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12810
- return { files: filtered, hasRecentCommitFiles };
12811
- }
12812
- function getStagedFiles() {
12813
- return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12814
- }
12815
- function getPushRangeFiles() {
12816
- const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
12817
- const resolvers = [
12818
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
12819
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
12820
- () => {
12821
- const branch = execGit("git rev-parse --abbrev-ref HEAD");
12822
- return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
12823
- }
12824
- ];
12825
- for (const resolve of resolvers) {
12826
- const range = resolve();
12827
- if (range) return { files: diff(range), range };
12828
- }
12829
- const baseline = readBaselineSha();
12830
- if (baseline) {
12831
- const files = diff(`${baseline}..HEAD`);
12832
- if (files.length > 0) return { files, range: `${baseline}..HEAD` };
12833
- }
12834
- const last = diff("HEAD~1..HEAD");
12835
- return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
12836
- }
12837
- function getPushRangeMessages() {
12838
- const { range } = getPushRangeFiles();
12839
- if (!range) return "";
12840
- return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
12841
- }
12842
- function getWorktreeFiles() {
12843
- const result = [];
12844
- const worktreeDir = ".claude/worktrees";
12845
- if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
12846
- try {
12847
- const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
12848
- const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
12849
- for (const entry of entries) {
12850
- if (!entry.isDirectory()) continue;
12851
- const wtDir = (0, import_node_path7.join)(worktreeDir, entry.name);
12852
- scanDir(wtDir, wtDir, fiveMinAgo, result);
12853
- }
12854
- } catch {
12855
- }
12856
- return result;
12857
- }
12858
- function scanDir(baseDir, dir, minMtime, result) {
12859
- try {
12860
- const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
12861
- for (const entry of entries) {
12862
- const fullPath = (0, import_node_path7.join)(dir, entry.name);
12863
- if (entry.isDirectory()) {
12864
- if (entry.name === "node_modules" || entry.name === ".git") continue;
12865
- scanDir(baseDir, fullPath, minMtime, result);
12866
- } else if (entry.isFile()) {
12867
- const ext = (0, import_node_path7.extname)(entry.name).slice(1);
12868
- if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
12869
- try {
12870
- const stat3 = (0, import_node_fs7.statSync)(fullPath);
12871
- if (stat3.mtimeMs >= minMtime) {
12872
- const relPath = fullPath.slice(baseDir.length + 1);
12873
- result.push(relPath);
12874
- }
12875
- } catch {
12876
- }
12877
- }
13058
+ const body = {
13059
+ run_id: runId,
13060
+ pattern_id: patternId,
13061
+ action
13062
+ };
13063
+ if (note) body.note = note;
13064
+ if (opts.file) body.file_path = opts.file;
13065
+ if (opts.line != null) body.line = opts.line;
13066
+ const result = await apiRequest({
13067
+ method: "POST",
13068
+ path: "/feedback/findings",
13069
+ serviceUrl: urlResult.data,
13070
+ token: tokenResult.data.token,
13071
+ body,
13072
+ verbose: globals.verbose
13073
+ });
13074
+ if (!result.ok) {
13075
+ printError(`Couldn't submit finding feedback: ${result.error}`);
13076
+ process.exit(1);
12878
13077
  }
12879
- } catch {
12880
- }
12881
- }
12882
- function filterAnalyzable(files) {
12883
- return files.filter((f) => {
12884
- const ext = (0, import_node_path7.extname)(f).slice(1);
12885
- return ANALYZABLE_EXTENSIONS.has(ext);
13078
+ const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
13079
+ printInfo(status);
12886
13080
  });
12887
- }
12888
- function filterReviewable(files) {
12889
- return files.filter((f) => {
12890
- const ext = (0, import_node_path7.extname)(f).slice(1);
12891
- if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
12892
- if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
12893
- const basename2 = f.split("/").pop() ?? "";
12894
- if (REVIEWABLE_FILENAMES.has(basename2)) return true;
12895
- if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
12896
- return false;
13081
+ feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
13082
+ if (!message) return;
13083
+ const globals = program2.opts();
13084
+ await sendGeneralFeedback(message, opts, globals);
12897
13085
  });
12898
13086
  }
12899
- function filterSecurity(files) {
12900
- return files.filter(
12901
- (f) => SECURITY_PATTERNS.some((p) => p.test(f))
12902
- );
12903
- }
12904
- function getCurrentCommit() {
12905
- return execGit("git rev-parse HEAD") || "no-git";
12906
- }
12907
- function listTrackedFiles() {
12908
- const set = /* @__PURE__ */ new Set();
12909
- for (const f of splitLines(execGit("git ls-files"))) set.add(f);
12910
- for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
12911
- return Array.from(set);
13087
+ async function sendGeneralFeedback(message, opts, globals) {
13088
+ const tokenResult = await resolveToken(globals.token);
13089
+ if (!tokenResult.ok) {
13090
+ printError(tokenResult.error);
13091
+ printInfo(`Your message: ${message}`);
13092
+ process.exit(1);
13093
+ }
13094
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
13095
+ if (!urlResult.ok) {
13096
+ printError(urlResult.error);
13097
+ printInfo(`Your message: ${message}`);
13098
+ process.exit(1);
13099
+ }
13100
+ const body = { message };
13101
+ const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
13102
+ const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
13103
+ if (sessionId) body.session_id = sessionId;
13104
+ if (model) body.agent_model = model;
13105
+ const result = await apiRequest({
13106
+ method: "POST",
13107
+ path: "/feedback",
13108
+ serviceUrl: urlResult.data,
13109
+ token: tokenResult.data.token,
13110
+ body,
13111
+ verbose: globals.verbose
13112
+ });
13113
+ if (!result.ok) {
13114
+ printError(`Couldn't send feedback: ${result.error}`);
13115
+ printInfo(`Your message: ${message}`);
13116
+ process.exit(1);
13117
+ }
13118
+ printInfo("Thanks, feedback sent!");
12912
13119
  }
12913
13120
 
13121
+ // src/commands/analyze.ts
13122
+ var import_node_fs19 = require("node:fs");
13123
+ var import_node_path14 = require("node:path");
13124
+
12914
13125
  // src/lib/files.ts
12915
13126
  var import_node_fs8 = require("node:fs");
12916
13127
  var import_node_path8 = require("node:path");
@@ -13464,15 +13675,207 @@ function cleanStaleSnapshots(dir, keepSet) {
13464
13675
  }
13465
13676
  }
13466
13677
 
13467
- // src/lib/offline.ts
13678
+ // src/lib/baseline.ts
13468
13679
  var import_node_fs13 = require("node:fs");
13680
+ var import_node_path11 = require("node:path");
13469
13681
  var import_node_crypto4 = require("node:crypto");
13682
+ var BASELINE_VERSION = 1;
13683
+ var BASELINE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
13684
+ var MIRROR_MAX_BYTES = 2 * 1024 * 1024;
13685
+ var DEFAULT_SESSION_KEY = "_default";
13686
+ function sessionKey(sessionId) {
13687
+ if (!sessionId) return DEFAULT_SESSION_KEY;
13688
+ return (0, import_node_crypto4.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
13689
+ }
13690
+ function sessionDir(key) {
13691
+ return (0, import_node_path11.join)(projectPath(BASELINE_DIR), key);
13692
+ }
13693
+ function manifestPath(dir) {
13694
+ return (0, import_node_path11.join)(dir, "manifest.json");
13695
+ }
13696
+ function mirrorPath(dir, repoRelPath) {
13697
+ return (0, import_node_path11.join)(dir, "files", repoRelPath);
13698
+ }
13699
+ function captureBaseline(opts = {}) {
13700
+ const key = sessionKey(opts.sessionId);
13701
+ const dir = sessionDir(key);
13702
+ const existing = readManifest(dir);
13703
+ const freshStart = opts.source === "startup" || opts.source === "clear";
13704
+ if (existing && !freshStart) {
13705
+ return { baseline: existing, created: false };
13706
+ }
13707
+ const head_sha = getCurrentCommit();
13708
+ const dirty = getDirtyFiles();
13709
+ try {
13710
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13711
+ } catch {
13712
+ }
13713
+ const filesDir = (0, import_node_path11.join)(dir, "files");
13714
+ const mirrored = [];
13715
+ try {
13716
+ (0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
13717
+ for (const p of dirty) {
13718
+ if (p.includes("..")) continue;
13719
+ const content = safeReadForMirror(projectPath(p));
13720
+ if (content === null) continue;
13721
+ const dest = mirrorPath(dir, p);
13722
+ try {
13723
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(dest), { recursive: true });
13724
+ (0, import_node_fs13.writeFileSync)(dest, content);
13725
+ mirrored.push(p);
13726
+ } catch {
13727
+ }
13728
+ }
13729
+ } catch {
13730
+ }
13731
+ const baseline = {
13732
+ session_id: opts.sessionId ?? "",
13733
+ head_sha,
13734
+ captured_at: Date.now(),
13735
+ dirty_paths: mirrored,
13736
+ version: BASELINE_VERSION
13737
+ };
13738
+ try {
13739
+ (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
13740
+ (0, import_node_fs13.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
13741
+ } catch {
13742
+ }
13743
+ pruneOldBaselines();
13744
+ return { baseline, created: true };
13745
+ }
13746
+ function readBaseline(sessionId) {
13747
+ return readManifest(sessionDir(sessionKey(sessionId)));
13748
+ }
13749
+ function readManifest(dir) {
13750
+ const mp = manifestPath(dir);
13751
+ if (!(0, import_node_fs13.existsSync)(mp)) return null;
13752
+ try {
13753
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(mp, "utf-8"));
13754
+ if (typeof parsed.head_sha !== "string" || typeof parsed.captured_at !== "number" || !Array.isArray(parsed.dirty_paths) || parsed.version !== BASELINE_VERSION) {
13755
+ return null;
13756
+ }
13757
+ return {
13758
+ session_id: typeof parsed.session_id === "string" ? parsed.session_id : "",
13759
+ head_sha: parsed.head_sha,
13760
+ captured_at: parsed.captured_at,
13761
+ dirty_paths: parsed.dirty_paths.filter((p) => typeof p === "string"),
13762
+ version: parsed.version
13763
+ };
13764
+ } catch {
13765
+ return null;
13766
+ }
13767
+ }
13768
+ var preImageCache = /* @__PURE__ */ new WeakMap();
13769
+ function preImage(repoRelPath, baseline) {
13770
+ let perBaseline = preImageCache.get(baseline);
13771
+ if (!perBaseline) {
13772
+ perBaseline = /* @__PURE__ */ new Map();
13773
+ preImageCache.set(baseline, perBaseline);
13774
+ }
13775
+ const cached = perBaseline.get(repoRelPath);
13776
+ if (cached) return cached;
13777
+ const resolved = resolvePreImage(repoRelPath, baseline);
13778
+ perBaseline.set(repoRelPath, resolved);
13779
+ return resolved;
13780
+ }
13781
+ function resolvePreImage(repoRelPath, baseline) {
13782
+ if (baseline.dirty_paths.includes(repoRelPath)) {
13783
+ const mp = mirrorPath(sessionDir(sessionKey(baseline.session_id)), repoRelPath);
13784
+ if ((0, import_node_fs13.existsSync)(mp)) {
13785
+ try {
13786
+ return { content: (0, import_node_fs13.readFileSync)(mp, "utf-8"), existed: true };
13787
+ } catch {
13788
+ }
13789
+ }
13790
+ }
13791
+ const atHead = showContentAtRef(baseline.head_sha, repoRelPath);
13792
+ if (atHead !== null) return { content: atHead, existed: true };
13793
+ return { content: "", existed: false };
13794
+ }
13795
+ function generateBaselineDiffs(files, baseline) {
13796
+ if (!baseline) return { diffs: [], has_baseline: false };
13797
+ const diffs = [];
13798
+ for (const file of files) {
13799
+ const language = file.language ?? detectLanguage(file.path);
13800
+ const pre = preImage(file.path, baseline);
13801
+ if (pre.existed) {
13802
+ if (pre.content === file.content) continue;
13803
+ const diff = computeDiff(pre.content, file.content, file.path);
13804
+ if (diff) diffs.push({ path: file.path, language, diff, status: "modified" });
13805
+ } else {
13806
+ const addedLines = file.content.split("\n").map((l) => `+${l}`).join("\n");
13807
+ diffs.push({
13808
+ path: file.path,
13809
+ language,
13810
+ diff: `--- /dev/null
13811
+ +++ b/${file.path}
13812
+ @@ -0,0 +1,${file.content.split("\n").length} @@
13813
+ ${addedLines}`,
13814
+ status: "added"
13815
+ });
13816
+ }
13817
+ }
13818
+ return { diffs, has_baseline: true };
13819
+ }
13820
+ function changedSinceBaseline(repoRelPath, baseline) {
13821
+ const pre = preImage(repoRelPath, baseline);
13822
+ let current;
13823
+ try {
13824
+ current = (0, import_node_fs13.readFileSync)(projectPath(repoRelPath), "utf-8");
13825
+ } catch {
13826
+ return pre.existed;
13827
+ }
13828
+ if (!pre.existed) return true;
13829
+ return current !== pre.content;
13830
+ }
13831
+ function safeReadForMirror(absPath) {
13832
+ try {
13833
+ if ((0, import_node_fs13.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
13834
+ const buf = (0, import_node_fs13.readFileSync)(absPath);
13835
+ if (buf.includes(0)) return null;
13836
+ return buf.toString("utf-8");
13837
+ } catch {
13838
+ return null;
13839
+ }
13840
+ }
13841
+ function pruneOldBaselines() {
13842
+ const root = projectPath(BASELINE_DIR);
13843
+ let entries;
13844
+ try {
13845
+ entries = (0, import_node_fs13.readdirSync)(root);
13846
+ } catch {
13847
+ return;
13848
+ }
13849
+ const now = Date.now();
13850
+ for (const name of entries) {
13851
+ const dir = (0, import_node_path11.join)(root, name);
13852
+ const manifest = readManifest(dir);
13853
+ if (!manifest) {
13854
+ try {
13855
+ if (now - (0, import_node_fs13.statSync)(dir).mtimeMs > BASELINE_TTL_MS) {
13856
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13857
+ }
13858
+ } catch {
13859
+ }
13860
+ continue;
13861
+ }
13862
+ if (now - manifest.captured_at <= BASELINE_TTL_MS) continue;
13863
+ try {
13864
+ (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
13865
+ } catch {
13866
+ }
13867
+ }
13868
+ }
13869
+
13870
+ // src/lib/offline.ts
13871
+ var import_node_fs14 = require("node:fs");
13872
+ var import_node_crypto5 = require("node:crypto");
13470
13873
  function cacheRequest(body) {
13471
13874
  try {
13472
- (0, import_node_fs13.mkdirSync)(CACHE_DIR, { recursive: true });
13473
- const suffix = (0, import_node_crypto4.randomBytes)(4).toString("hex");
13875
+ (0, import_node_fs14.mkdirSync)(CACHE_DIR, { recursive: true });
13876
+ const suffix = (0, import_node_crypto5.randomBytes)(4).toString("hex");
13474
13877
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
13475
- (0, import_node_fs13.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
13878
+ (0, import_node_fs14.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
13476
13879
  } catch {
13477
13880
  }
13478
13881
  }
@@ -13486,7 +13889,7 @@ function buildOfflineFallback(reason, staticResults) {
13486
13889
  }
13487
13890
 
13488
13891
  // src/lib/context-files.ts
13489
- var import_node_fs14 = require("node:fs");
13892
+ var import_node_fs15 = require("node:fs");
13490
13893
  var MAX_CONTEXT_FILES = 10;
13491
13894
  var MAX_CONTEXT_FILE_BYTES = 10240;
13492
13895
  var MAX_CONTEXT_TOTAL_BYTES = 51200;
@@ -13498,7 +13901,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
13498
13901
  if (result.length >= MAX_CONTEXT_FILES) break;
13499
13902
  if (deltaPaths.has(filePath)) continue;
13500
13903
  try {
13501
- const content = (0, import_node_fs14.readFileSync)(filePath, "utf8");
13904
+ const content = (0, import_node_fs15.readFileSync)(filePath, "utf8");
13502
13905
  const bytes = Buffer.byteLength(content);
13503
13906
  if (bytes > MAX_CONTEXT_FILE_BYTES) {
13504
13907
  logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
@@ -13543,20 +13946,20 @@ function gatherContextFiles(contextPaths, deltaFiles) {
13543
13946
  }
13544
13947
 
13545
13948
  // src/lib/cache-cleanup.ts
13546
- var import_node_fs15 = require("node:fs");
13547
- var import_node_path11 = require("node:path");
13949
+ var import_node_fs16 = require("node:fs");
13950
+ var import_node_path12 = require("node:path");
13548
13951
  var CACHE_TTL_DAYS = 7;
13549
13952
  function pruneStaleCache() {
13550
13953
  try {
13551
13954
  const dir = projectPath(CACHE_DIR);
13552
13955
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
13553
- for (const entry of (0, import_node_fs15.readdirSync)(dir)) {
13956
+ for (const entry of (0, import_node_fs16.readdirSync)(dir)) {
13554
13957
  if (!entry.startsWith("pending-")) continue;
13555
- const path = (0, import_node_path11.join)(dir, entry);
13958
+ const path = (0, import_node_path12.join)(dir, entry);
13556
13959
  try {
13557
- const stat3 = (0, import_node_fs15.statSync)(path);
13960
+ const stat3 = (0, import_node_fs16.statSync)(path);
13558
13961
  if (stat3.mtimeMs < cutoff) {
13559
- (0, import_node_fs15.unlinkSync)(path);
13962
+ (0, import_node_fs16.unlinkSync)(path);
13560
13963
  logEvent("cache_entry_pruned", {
13561
13964
  path: entry,
13562
13965
  age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
@@ -13628,10 +14031,11 @@ function reconcileAnalysisMode(predictedMode, signals) {
13628
14031
  signals.noFilesChanged,
13629
14032
  signals.assistantResponse,
13630
14033
  signals.conversationPrompts,
13631
- signals.actionSummary
14034
+ signals.actionSummary,
14035
+ signals.sessionAuthoredCode
13632
14036
  );
13633
14037
  }
13634
- const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0));
14038
+ const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
13635
14039
  const agentInvestigated = didAgentInvestigate(signals.actionSummary);
13636
14040
  switch (predictedMode) {
13637
14041
  case "skip":
@@ -13656,8 +14060,8 @@ function didAgentInvestigate(summary) {
13656
14060
  function isValidMode(mode) {
13657
14061
  return mode === "standard" || mode === "plan" || mode === "debug" || mode === "skip";
13658
14062
  }
13659
- function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary) {
13660
- const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
14063
+ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
14064
+ const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
13661
14065
  if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
13662
14066
  if (!agentAuthoredCode) return "skip";
13663
14067
  }
@@ -13737,7 +14141,7 @@ function isMetaTaskLabel(label2) {
13737
14141
  }
13738
14142
 
13739
14143
  // src/lib/transcript.ts
13740
- var import_node_fs16 = require("node:fs");
14144
+ var import_node_fs17 = require("node:fs");
13741
14145
  var MAX_READ_BYTES = 256 * 1024;
13742
14146
  var SMALL_FILE_BYTES = 64 * 1024;
13743
14147
  var MAX_FILES_LIST = 20;
@@ -13759,14 +14163,14 @@ async function extractActionSummary(transcriptPath) {
13759
14163
  function readTurnLines(transcriptPath) {
13760
14164
  let size;
13761
14165
  try {
13762
- size = (0, import_node_fs16.statSync)(transcriptPath).size;
14166
+ size = (0, import_node_fs17.statSync)(transcriptPath).size;
13763
14167
  } catch {
13764
14168
  return null;
13765
14169
  }
13766
14170
  if (size === 0) return null;
13767
14171
  let raw;
13768
14172
  if (size <= SMALL_FILE_BYTES) {
13769
- raw = (0, import_node_fs16.readFileSync)(transcriptPath, "utf-8");
14173
+ raw = (0, import_node_fs17.readFileSync)(transcriptPath, "utf-8");
13770
14174
  } else {
13771
14175
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
13772
14176
  const fd = require("node:fs").openSync(transcriptPath, "r");
@@ -13854,6 +14258,12 @@ function buildSummary(lines) {
13854
14258
  case "Edit":
13855
14259
  addPath(filesEdited, input.file_path);
13856
14260
  break;
14261
+ case "MultiEdit":
14262
+ addPath(filesEdited, input.file_path);
14263
+ break;
14264
+ case "NotebookEdit":
14265
+ addPath(filesEdited, input.notebook_path ?? input.file_path);
14266
+ break;
13857
14267
  case "Write":
13858
14268
  addPath(filesCreated, input.file_path);
13859
14269
  addPath(filesEdited, input.file_path);
@@ -13937,8 +14347,8 @@ function capArray(set, max) {
13937
14347
 
13938
14348
  // src/lib/seed-runner.ts
13939
14349
  var import_promises11 = require("node:fs/promises");
13940
- var import_node_fs17 = require("node:fs");
13941
- var import_node_path12 = require("node:path");
14350
+ var import_node_fs18 = require("node:fs");
14351
+ var import_node_path13 = require("node:path");
13942
14352
  var import_yaml2 = __toESM(require_dist());
13943
14353
 
13944
14354
  // src/lib/seed.ts
@@ -14177,7 +14587,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
14177
14587
  return fm;
14178
14588
  }
14179
14589
  async function runSeed(opts) {
14180
- if (!(0, import_node_fs17.existsSync)(STANDARD_FILE)) {
14590
+ if (!(0, import_node_fs18.existsSync)(STANDARD_FILE)) {
14181
14591
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
14182
14592
  }
14183
14593
  let standardDoc;
@@ -14189,7 +14599,7 @@ async function runSeed(opts) {
14189
14599
  }
14190
14600
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
14191
14601
  let readmeContent;
14192
- if ((0, import_node_fs17.existsSync)("README.md")) {
14602
+ if ((0, import_node_fs18.existsSync)("README.md")) {
14193
14603
  try {
14194
14604
  readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
14195
14605
  } catch {
@@ -14197,7 +14607,7 @@ async function runSeed(opts) {
14197
14607
  }
14198
14608
  let claudeMdContent;
14199
14609
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
14200
- if ((0, import_node_fs17.existsSync)(p)) {
14610
+ if ((0, import_node_fs18.existsSync)(p)) {
14201
14611
  try {
14202
14612
  claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
14203
14613
  break;
@@ -14220,8 +14630,8 @@ async function runSeed(opts) {
14220
14630
  if (candidates.length === 0) {
14221
14631
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
14222
14632
  }
14223
- const overviewPath = (0, import_node_path12.join)(MEMORY_DIR, "domain", "project-overview.md");
14224
- if ((0, import_node_fs17.existsSync)(overviewPath) && !opts.force) {
14633
+ const overviewPath = (0, import_node_path13.join)(MEMORY_DIR, "domain", "project-overview.md");
14634
+ if ((0, import_node_fs18.existsSync)(overviewPath) && !opts.force) {
14225
14635
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
14226
14636
  }
14227
14637
  if (opts.dryRun) {
@@ -14256,9 +14666,9 @@ async function runSeed(opts) {
14256
14666
  }
14257
14667
  const nodeId = res.data.node_id;
14258
14668
  const filePathRel = res.data.file_path;
14259
- const targetPath = (0, import_node_path12.join)(MEMORY_DIR, filePathRel);
14669
+ const targetPath = (0, import_node_path13.join)(MEMORY_DIR, filePathRel);
14260
14670
  try {
14261
- await (0, import_promises11.mkdir)((0, import_node_path12.dirname)(targetPath), { recursive: true });
14671
+ await (0, import_promises11.mkdir)((0, import_node_path13.dirname)(targetPath), { recursive: true });
14262
14672
  await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
14263
14673
  created++;
14264
14674
  opts.onCreated?.(nodeId, filePathRel, c);
@@ -14328,6 +14738,15 @@ async function runAnalyze(opts, globals) {
14328
14738
  }
14329
14739
  const { assistantMessage: assistantResponse, stopReason, transcriptPath, sessionId } = await readStopHookStdin();
14330
14740
  const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
14741
+ const baselineSessionId = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
14742
+ const baseline = readBaseline(baselineSessionId);
14743
+ if (baseline) {
14744
+ logEvent("baseline_loaded", {
14745
+ head: baseline.head_sha.slice(0, 12),
14746
+ dirty_count: baseline.dirty_paths.length,
14747
+ age_ms: Date.now() - baseline.captured_at
14748
+ });
14749
+ }
14331
14750
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
14332
14751
  const analyzable = filterAnalyzable(allChanged);
14333
14752
  const reviewable = filterReviewable(allChanged);
@@ -14337,7 +14756,7 @@ async function runAnalyze(opts, globals) {
14337
14756
  passAndExit("No analyzable files changed");
14338
14757
  }
14339
14758
  const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
14340
- const conversation = await readAndClearConversationBuffer();
14759
+ const conversation = await readAndClearConversationBuffer(sessionId ?? void 0);
14341
14760
  const specs = discoverSpecs();
14342
14761
  const plans = discoverPlans();
14343
14762
  const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
@@ -14387,13 +14806,14 @@ async function runAnalyze(opts, globals) {
14387
14806
  }
14388
14807
  const conversationPrompts = (conversation?.prompts ?? []).map((p) => p.prompt);
14389
14808
  let analysisMode;
14809
+ const sessionAuthoredCode = !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
14390
14810
  const modeOverride = opts.mode;
14391
14811
  if (modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride)) {
14392
14812
  analysisMode = modeOverride;
14393
14813
  } else {
14394
14814
  analysisMode = reconcileAnalysisMode(
14395
14815
  predictedMode,
14396
- { noFilesChanged, assistantResponse, actionSummary, conversationPrompts }
14816
+ { noFilesChanged, assistantResponse, actionSummary, conversationPrompts, sessionAuthoredCode }
14397
14817
  );
14398
14818
  }
14399
14819
  if (analysisMode === "skip") {
@@ -14451,10 +14871,16 @@ async function runAnalyze(opts, globals) {
14451
14871
  const baseForReview = agentNarrowed.length > 0 ? agentNarrowed : allForReview;
14452
14872
  const recentForReview = narrowToRecent(baseForReview);
14453
14873
  if (!opts.skipStatic && isCodacyAvailable()) {
14454
- const allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14455
- staticResults = runCodacyAnalysis(allScannable);
14874
+ let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
14875
+ if (baseline) {
14876
+ allScannable = allScannable.filter((f) => changedSinceBaseline(f, baseline));
14877
+ }
14878
+ if (allScannable.length > 0) {
14879
+ staticResults = runCodacyAnalysis(allScannable);
14880
+ }
14456
14881
  }
14457
- codeDelta = collectCodeDelta(recentForReview, {
14882
+ const deltaSet = baseline ? recentForReview.filter((f) => changedSinceBaseline(f, baseline)) : recentForReview;
14883
+ codeDelta = collectCodeDelta(deltaSet, {
14458
14884
  maxFiles: parseInt(opts.maxFiles, 10),
14459
14885
  maxFileBytes: parseInt(opts.maxFileSize, 10),
14460
14886
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
@@ -14469,7 +14895,12 @@ async function runAnalyze(opts, globals) {
14469
14895
  }
14470
14896
  }
14471
14897
  if (analysisMode !== "plan") {
14472
- snapshotResult = generateSnapshotDiffs(codeDelta.files);
14898
+ if (baseline) {
14899
+ const baselineDiffs = generateBaselineDiffs(codeDelta.files, baseline);
14900
+ snapshotResult = { has_snapshots: baselineDiffs.diffs.length > 0, diffs: baselineDiffs.diffs };
14901
+ } else {
14902
+ snapshotResult = generateSnapshotDiffs(codeDelta.files);
14903
+ }
14473
14904
  currentCommit = getCurrentCommit();
14474
14905
  const maxIterations = parseInt(opts.maxIterations, 10);
14475
14906
  const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
@@ -14501,9 +14932,9 @@ async function runAnalyze(opts, globals) {
14501
14932
  let autoSeedNotice = null;
14502
14933
  try {
14503
14934
  await ensureMemoryDir();
14504
- const seedMarker = (0, import_node_path13.join)(VERITY_DIR, ".seeded");
14505
- const hasStandard = (0, import_node_fs18.existsSync)(STANDARD_FILE);
14506
- const alreadyTried = (0, import_node_fs18.existsSync)(seedMarker);
14935
+ const seedMarker = (0, import_node_path14.join)(VERITY_DIR, ".seeded");
14936
+ const hasStandard = (0, import_node_fs19.existsSync)(STANDARD_FILE);
14937
+ const alreadyTried = (0, import_node_fs19.existsSync)(seedMarker);
14507
14938
  if (hasStandard && !alreadyTried) {
14508
14939
  const preManifest = await buildManifest();
14509
14940
  if (preManifest.nodes.length === 0) {
@@ -14516,7 +14947,7 @@ async function runAnalyze(opts, globals) {
14516
14947
  dryRun: false
14517
14948
  });
14518
14949
  if (seedResult.created > 0) {
14519
- (0, import_node_fs18.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
14950
+ (0, import_node_fs19.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
14520
14951
  `);
14521
14952
  autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
14522
14953
  logEvent("auto_seed_ran", {
@@ -14524,7 +14955,7 @@ async function runAnalyze(opts, globals) {
14524
14955
  failed: seedResult.failed
14525
14956
  });
14526
14957
  } else if (seedResult.skipped === "already_seeded") {
14527
- (0, import_node_fs18.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
14958
+ (0, import_node_fs19.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
14528
14959
  `);
14529
14960
  } else {
14530
14961
  logEvent("auto_seed_noop", {
@@ -14842,8 +15273,54 @@ async function runAnalyze(opts, globals) {
14842
15273
  }
14843
15274
  }
14844
15275
 
15276
+ // src/commands/baseline.ts
15277
+ var import_node_fs20 = require("node:fs");
15278
+ function registerBaselineCommands(program2) {
15279
+ const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
15280
+ 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) => {
15281
+ try {
15282
+ try {
15283
+ process.chdir(repoRoot());
15284
+ } catch {
15285
+ }
15286
+ if (!(0, import_node_fs20.existsSync)(VERITY_DIR)) {
15287
+ process.exit(0);
15288
+ }
15289
+ let sessionId = opts.sessionId;
15290
+ let source = opts.source;
15291
+ if (!process.stdin.isTTY) {
15292
+ const input = (await readStdin()).trim();
15293
+ if (input) {
15294
+ try {
15295
+ const event = JSON.parse(input);
15296
+ sessionId = sessionId ?? event.session_id;
15297
+ source = source ?? event.source;
15298
+ } catch {
15299
+ }
15300
+ }
15301
+ }
15302
+ const result = captureBaseline({ sessionId, source });
15303
+ logEvent("baseline_capture", {
15304
+ created: result.created,
15305
+ source: source ?? null,
15306
+ dirty_count: result.baseline.dirty_paths.length,
15307
+ head: result.baseline.head_sha.slice(0, 12)
15308
+ });
15309
+ } catch {
15310
+ }
15311
+ process.exit(0);
15312
+ });
15313
+ }
15314
+ async function readStdin() {
15315
+ const chunks = [];
15316
+ for await (const chunk of process.stdin) {
15317
+ chunks.push(chunk);
15318
+ }
15319
+ return Buffer.concat(chunks).toString("utf-8");
15320
+ }
15321
+
14845
15322
  // src/commands/review.ts
14846
- var import_node_fs19 = require("node:fs");
15323
+ var import_node_fs21 = require("node:fs");
14847
15324
  function registerReviewCommand(program2) {
14848
15325
  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) => {
14849
15326
  const globals = program2.opts();
@@ -14862,7 +15339,7 @@ async function runReview(opts, globals) {
14862
15339
  const securityFiles = filterSecurity(allFiles);
14863
15340
  let staticResults;
14864
15341
  if (isCodacyAvailable()) {
14865
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs19.existsSync)(f) || resolveFile(f) !== null);
15342
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs21.existsSync)(f) || resolveFile(f) !== null);
14866
15343
  staticResults = runCodacyAnalysis(scannable);
14867
15344
  } else {
14868
15345
  staticResults = {
@@ -14887,10 +15364,10 @@ async function runReview(opts, globals) {
14887
15364
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
14888
15365
  specs = [];
14889
15366
  for (const p of specPaths) {
14890
- if (!(0, import_node_fs19.existsSync)(p)) continue;
15367
+ if (!(0, import_node_fs21.existsSync)(p)) continue;
14891
15368
  try {
14892
- const { readFileSync: readFileSync11 } = await import("node:fs");
14893
- const content = readFileSync11(p, "utf-8");
15369
+ const { readFileSync: readFileSync12 } = await import("node:fs");
15370
+ const content = readFileSync12(p, "utf-8");
14894
15371
  specs.push({ path: p, content: content.slice(0, 10240) });
14895
15372
  } catch {
14896
15373
  }
@@ -14947,10 +15424,10 @@ async function runReview(opts, globals) {
14947
15424
  }
14948
15425
 
14949
15426
  // src/commands/guard.ts
14950
- var import_node_fs20 = require("node:fs");
14951
- var import_node_path14 = require("node:path");
15427
+ var import_node_fs22 = require("node:fs");
15428
+ var import_node_path15 = require("node:path");
14952
15429
  var GUARD_BLOCK_CAP = 2;
14953
- var GUARD_ITER_FILE = (0, import_node_path14.join)(VERITY_DIR, ".guard-iteration");
15430
+ var GUARD_ITER_FILE = (0, import_node_path15.join)(VERITY_DIR, ".guard-iteration");
14954
15431
  function readPreToolUseStdin() {
14955
15432
  const empty = { command: "", cwd: null, sessionId: null };
14956
15433
  return new Promise((resolve) => {
@@ -15014,7 +15491,7 @@ function classifyCommand(command, on) {
15014
15491
  }
15015
15492
  function readIterMap() {
15016
15493
  try {
15017
- const raw = JSON.parse((0, import_node_fs20.readFileSync)(GUARD_ITER_FILE, "utf-8"));
15494
+ const raw = JSON.parse((0, import_node_fs22.readFileSync)(GUARD_ITER_FILE, "utf-8"));
15018
15495
  if (raw && typeof raw === "object") {
15019
15496
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
15020
15497
  return { [raw.moment]: raw.count };
@@ -15034,10 +15511,10 @@ function readIter(moment) {
15034
15511
  }
15035
15512
  function writeIter(moment, count) {
15036
15513
  try {
15037
- (0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
15514
+ (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
15038
15515
  const map = readIterMap();
15039
15516
  map[moment] = count;
15040
- (0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15517
+ (0, import_node_fs22.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15041
15518
  } catch {
15042
15519
  }
15043
15520
  }
@@ -15047,10 +15524,10 @@ function resetIter(moment) {
15047
15524
  if (!(moment in map)) return;
15048
15525
  delete map[moment];
15049
15526
  if (Object.keys(map).length === 0) {
15050
- if ((0, import_node_fs20.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs20.unlinkSync)(GUARD_ITER_FILE);
15527
+ if ((0, import_node_fs22.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs22.unlinkSync)(GUARD_ITER_FILE);
15051
15528
  } else {
15052
- (0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
15053
- (0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15529
+ (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
15530
+ (0, import_node_fs22.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
15054
15531
  }
15055
15532
  } catch {
15056
15533
  }
@@ -15114,7 +15591,7 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
15114
15591
  const securityFiles = filterSecurity(files);
15115
15592
  let staticResults;
15116
15593
  if (isCodacyAvailable()) {
15117
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs20.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
15594
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs22.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
15118
15595
  staticResults = runCodacyAnalysis(scannable);
15119
15596
  } else {
15120
15597
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -15159,7 +15636,7 @@ function emitAllowNotice(userMsg, agentMsg) {
15159
15636
  async function runGuard(opts, globals) {
15160
15637
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
15161
15638
  const { command, cwd, sessionId } = await readPreToolUseStdin();
15162
- if (cwd && (0, import_node_fs20.existsSync)(cwd)) {
15639
+ if (cwd && (0, import_node_fs22.existsSync)(cwd)) {
15163
15640
  try {
15164
15641
  process.chdir(cwd);
15165
15642
  } catch {
@@ -15264,14 +15741,14 @@ function writeBlockMessage(moment, response) {
15264
15741
  }
15265
15742
 
15266
15743
  // src/commands/init.ts
15267
- var import_node_fs22 = require("node:fs");
15744
+ var import_node_fs24 = require("node:fs");
15268
15745
  var import_promises12 = require("node:fs/promises");
15269
- var import_node_path16 = require("node:path");
15746
+ var import_node_path17 = require("node:path");
15270
15747
  var import_node_child_process9 = require("node:child_process");
15271
15748
 
15272
15749
  // src/commands/migrate.ts
15273
- var import_node_fs21 = require("node:fs");
15274
- var import_node_path15 = require("node:path");
15750
+ var import_node_fs23 = require("node:fs");
15751
+ var import_node_path16 = require("node:path");
15275
15752
  var import_node_child_process8 = require("node:child_process");
15276
15753
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
15277
15754
  function defaultNpmRemover(pkg) {
@@ -15307,12 +15784,12 @@ async function runMigration(opts = {}) {
15307
15784
  return { actions, migrated: actions.length > 0 };
15308
15785
  }
15309
15786
  function migrateProjectDir(root, actions) {
15310
- const gateDir = (0, import_node_path15.join)(root, ".gate");
15311
- const verityDir = (0, import_node_path15.join)(root, ".verity");
15312
- if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) {
15787
+ const gateDir = (0, import_node_path16.join)(root, ".gate");
15788
+ const verityDir = (0, import_node_path16.join)(root, ".verity");
15789
+ if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) {
15313
15790
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
15314
15791
  }
15315
- if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
15792
+ if ((0, import_node_fs23.existsSync)(gateDir) && (0, import_node_fs23.existsSync)(verityDir)) {
15316
15793
  return migrateProjectDirCarry(gateDir, verityDir, actions);
15317
15794
  }
15318
15795
  return false;
@@ -15333,13 +15810,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
15333
15810
  }
15334
15811
  }
15335
15812
  if (moved) {
15336
- if ((0, import_node_fs21.existsSync)(gateDir)) {
15813
+ if ((0, import_node_fs23.existsSync)(gateDir)) {
15337
15814
  const carried = carryLegacyContents(gateDir, verityDir);
15338
15815
  if (carried > 0) {
15339
15816
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
15340
15817
  }
15341
15818
  try {
15342
- (0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
15819
+ (0, import_node_fs23.rmSync)(gateDir, { recursive: true, force: true });
15343
15820
  } catch {
15344
15821
  }
15345
15822
  }
@@ -15355,18 +15832,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
15355
15832
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
15356
15833
  }
15357
15834
  try {
15358
- (0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
15835
+ (0, import_node_fs23.rmSync)(gateDir, { recursive: true, force: true });
15359
15836
  } catch {
15360
15837
  }
15361
15838
  return carried > 0;
15362
15839
  }
15363
15840
  function migrateGlobalCredentials(home, actions) {
15364
15841
  if (!home) return;
15365
- const gateCreds = (0, import_node_path15.join)(home, ".gate", "credentials");
15366
- const verityCreds = (0, import_node_path15.join)(home, ".verity", "credentials");
15367
- if (!(0, import_node_fs21.existsSync)(gateCreds)) return;
15368
- if (!(0, import_node_fs21.existsSync)(verityCreds)) {
15369
- (0, import_node_fs21.mkdirSync)((0, import_node_path15.join)(home, ".verity"), { recursive: true });
15842
+ const gateCreds = (0, import_node_path16.join)(home, ".gate", "credentials");
15843
+ const verityCreds = (0, import_node_path16.join)(home, ".verity", "credentials");
15844
+ if (!(0, import_node_fs23.existsSync)(gateCreds)) return;
15845
+ if (!(0, import_node_fs23.existsSync)(verityCreds)) {
15846
+ (0, import_node_fs23.mkdirSync)((0, import_node_path16.join)(home, ".verity"), { recursive: true });
15370
15847
  moveFile(gateCreds, verityCreds);
15371
15848
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
15372
15849
  return;
@@ -15388,8 +15865,8 @@ async function migrateLegacyHooks(root, actions) {
15388
15865
  }
15389
15866
  }
15390
15867
  async function migrateClaudeMd(root, actions) {
15391
- const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
15392
- const hadLegacyBlock = (0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15868
+ const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
15869
+ const hadLegacyBlock = (0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
15393
15870
  if (!hadLegacyBlock) return;
15394
15871
  try {
15395
15872
  await ensureClaudeMdPointer(root);
@@ -15399,9 +15876,9 @@ async function migrateClaudeMd(root, actions) {
15399
15876
  }
15400
15877
  }
15401
15878
  function migrateStandardFile(root, actions) {
15402
- const gateMd = (0, import_node_path15.join)(root, "GATE.md");
15403
- const verityMd = (0, import_node_path15.join)(root, "VERITY.md");
15404
- if (!(0, import_node_fs21.existsSync)(gateMd) || (0, import_node_fs21.existsSync)(verityMd)) return;
15879
+ const gateMd = (0, import_node_path16.join)(root, "GATE.md");
15880
+ const verityMd = (0, import_node_path16.join)(root, "VERITY.md");
15881
+ if (!(0, import_node_fs23.existsSync)(gateMd) || (0, import_node_fs23.existsSync)(verityMd)) return;
15405
15882
  let moved = false;
15406
15883
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
15407
15884
  try {
@@ -15413,7 +15890,7 @@ function migrateStandardFile(root, actions) {
15413
15890
  if (!moved) moveFile(gateMd, verityMd);
15414
15891
  const content = readFileSyncSafe(verityMd);
15415
15892
  const refreshed = content.split("GATE.md").join("VERITY.md");
15416
- if (refreshed !== content) (0, import_node_fs21.writeFileSync)(verityMd, refreshed);
15893
+ if (refreshed !== content) (0, import_node_fs23.writeFileSync)(verityMd, refreshed);
15417
15894
  actions.push("Renamed GATE.md \u2192 VERITY.md");
15418
15895
  }
15419
15896
  function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
@@ -15447,14 +15924,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
15447
15924
  }
15448
15925
  if (toAppend.length > 0) {
15449
15926
  const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
15450
- (0, import_node_fs21.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
15927
+ (0, import_node_fs23.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
15451
15928
  }
15452
- (0, import_node_fs21.rmSync)(gateCreds, { force: true });
15929
+ (0, import_node_fs23.rmSync)(gateCreds, { force: true });
15453
15930
  return toAppend.length;
15454
15931
  }
15455
15932
  function readFileSyncSafe(path) {
15456
15933
  try {
15457
- return (0, import_node_fs21.readFileSync)(path, "utf-8");
15934
+ return (0, import_node_fs23.readFileSync)(path, "utf-8");
15458
15935
  } catch {
15459
15936
  return "";
15460
15937
  }
@@ -15469,35 +15946,35 @@ function hasStagedChanges(root) {
15469
15946
  }
15470
15947
  function moveDir(from, to) {
15471
15948
  try {
15472
- (0, import_node_fs21.renameSync)(from, to);
15949
+ (0, import_node_fs23.renameSync)(from, to);
15473
15950
  } catch (err) {
15474
15951
  if (err.code !== "EXDEV") throw err;
15475
- (0, import_node_fs21.cpSync)(from, to, { recursive: true });
15476
- (0, import_node_fs21.rmSync)(from, { recursive: true, force: true });
15952
+ (0, import_node_fs23.cpSync)(from, to, { recursive: true });
15953
+ (0, import_node_fs23.rmSync)(from, { recursive: true, force: true });
15477
15954
  }
15478
15955
  }
15479
15956
  function moveFile(from, to) {
15480
15957
  try {
15481
- (0, import_node_fs21.renameSync)(from, to);
15958
+ (0, import_node_fs23.renameSync)(from, to);
15482
15959
  } catch (err) {
15483
15960
  if (err.code !== "EXDEV") throw err;
15484
- (0, import_node_fs21.cpSync)(from, to);
15485
- (0, import_node_fs21.rmSync)(from, { force: true });
15961
+ (0, import_node_fs23.cpSync)(from, to);
15962
+ (0, import_node_fs23.rmSync)(from, { force: true });
15486
15963
  }
15487
15964
  }
15488
15965
  function carryLegacyContents(gateDir, verityDir) {
15489
15966
  let copied = 0;
15490
15967
  const walk = (relDir) => {
15491
- const srcDir = (0, import_node_path15.join)(gateDir, relDir);
15492
- for (const entry of (0, import_node_fs21.readdirSync)(srcDir)) {
15493
- const rel = relDir ? (0, import_node_path15.join)(relDir, entry) : entry;
15494
- const src = (0, import_node_path15.join)(gateDir, rel);
15495
- const dest = (0, import_node_path15.join)(verityDir, rel);
15496
- if ((0, import_node_fs21.statSync)(src).isDirectory()) {
15968
+ const srcDir = (0, import_node_path16.join)(gateDir, relDir);
15969
+ for (const entry of (0, import_node_fs23.readdirSync)(srcDir)) {
15970
+ const rel = relDir ? (0, import_node_path16.join)(relDir, entry) : entry;
15971
+ const src = (0, import_node_path16.join)(gateDir, rel);
15972
+ const dest = (0, import_node_path16.join)(verityDir, rel);
15973
+ if ((0, import_node_fs23.statSync)(src).isDirectory()) {
15497
15974
  walk(rel);
15498
- } else if (!(0, import_node_fs21.existsSync)(dest)) {
15499
- (0, import_node_fs21.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15500
- (0, import_node_fs21.cpSync)(src, dest);
15975
+ } else if (!(0, import_node_fs23.existsSync)(dest)) {
15976
+ (0, import_node_fs23.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
15977
+ (0, import_node_fs23.cpSync)(src, dest);
15501
15978
  copied++;
15502
15979
  }
15503
15980
  }
@@ -15506,22 +15983,22 @@ function carryLegacyContents(gateDir, verityDir) {
15506
15983
  return copied;
15507
15984
  }
15508
15985
  async function needsMigration(root = repoRoot()) {
15509
- const gateDir = (0, import_node_path15.join)(root, ".gate");
15510
- const verityDir = (0, import_node_path15.join)(root, ".verity");
15511
- if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) return true;
15512
- if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
15513
- 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"))) {
15986
+ const gateDir = (0, import_node_path16.join)(root, ".gate");
15987
+ const verityDir = (0, import_node_path16.join)(root, ".verity");
15988
+ if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) return true;
15989
+ if ((0, import_node_fs23.existsSync)(gateDir) && (0, import_node_fs23.existsSync)(verityDir)) {
15990
+ 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"))) {
15514
15991
  return true;
15515
15992
  }
15516
- 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"))) {
15993
+ 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"))) {
15517
15994
  return true;
15518
15995
  }
15519
15996
  }
15520
- const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
15521
- if ((0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15997
+ const claudeMd = (0, import_node_path16.join)(root, "CLAUDE.md");
15998
+ if ((0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15522
15999
  return true;
15523
16000
  }
15524
- 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"))) {
16001
+ 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"))) {
15525
16002
  return true;
15526
16003
  }
15527
16004
  if (await hasLegacyHooksAt(root)) return true;
@@ -15549,15 +16026,15 @@ function registerMigrateCommand(program2) {
15549
16026
  // src/commands/init.ts
15550
16027
  function resolveDataDir() {
15551
16028
  const candidates = [
15552
- (0, import_node_path16.join)(__dirname, "..", "data"),
16029
+ (0, import_node_path17.join)(__dirname, "..", "data"),
15553
16030
  // installed: node_modules/@codacy/verity-cli/data
15554
- (0, import_node_path16.join)(__dirname, "..", "..", "data"),
16031
+ (0, import_node_path17.join)(__dirname, "..", "..", "data"),
15555
16032
  // edge case: nested resolution
15556
- (0, import_node_path16.join)(process.cwd(), "cli", "data")
16033
+ (0, import_node_path17.join)(process.cwd(), "cli", "data")
15557
16034
  // local dev: running from repo root
15558
16035
  ];
15559
16036
  for (const candidate of candidates) {
15560
- if ((0, import_node_fs22.existsSync)((0, import_node_path16.join)(candidate, "skills"))) {
16037
+ if ((0, import_node_fs24.existsSync)((0, import_node_path17.join)(candidate, "skills"))) {
15561
16038
  return candidate;
15562
16039
  }
15563
16040
  }
@@ -15573,7 +16050,7 @@ function registerInitCommand(program2) {
15573
16050
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
15574
16051
  const force = opts.force ?? false;
15575
16052
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
15576
- const isProject = projectMarkers.some((m) => (0, import_node_fs22.existsSync)(m));
16053
+ const isProject = projectMarkers.some((m) => (0, import_node_fs24.existsSync)(m));
15577
16054
  if (!isProject) {
15578
16055
  printError("No project detected in the current directory.");
15579
16056
  printInfo('Run "verity init" from your project root.');
@@ -15636,21 +16113,21 @@ function registerInitCommand(program2) {
15636
16113
  console.log("");
15637
16114
  printInfo("Installing skills...");
15638
16115
  const dataDir = resolveDataDir();
15639
- const skillsSource = (0, import_node_path16.join)(dataDir, "skills");
16116
+ const skillsSource = (0, import_node_path17.join)(dataDir, "skills");
15640
16117
  const skillsDest = ".claude/skills";
15641
16118
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
15642
16119
  let skillsInstalled = 0;
15643
16120
  for (const skill of skills) {
15644
- const src = (0, import_node_path16.join)(skillsSource, skill);
15645
- const dest = (0, import_node_path16.join)(skillsDest, skill);
15646
- if (!(0, import_node_fs22.existsSync)(src)) {
16121
+ const src = (0, import_node_path17.join)(skillsSource, skill);
16122
+ const dest = (0, import_node_path17.join)(skillsDest, skill);
16123
+ if (!(0, import_node_fs24.existsSync)(src)) {
15647
16124
  printWarn(` Skill data not found: ${skill}`);
15648
16125
  continue;
15649
16126
  }
15650
- if ((0, import_node_fs22.existsSync)(dest) && !force) {
15651
- const srcSkill = (0, import_node_path16.join)(src, "SKILL.md");
15652
- const destSkill = (0, import_node_path16.join)(dest, "SKILL.md");
15653
- if ((0, import_node_fs22.existsSync)(destSkill)) {
16127
+ if ((0, import_node_fs24.existsSync)(dest) && !force) {
16128
+ const srcSkill = (0, import_node_path17.join)(src, "SKILL.md");
16129
+ const destSkill = (0, import_node_path17.join)(dest, "SKILL.md");
16130
+ if ((0, import_node_fs24.existsSync)(destSkill)) {
15654
16131
  try {
15655
16132
  const srcContent = await (0, import_promises12.readFile)(srcSkill, "utf-8");
15656
16133
  const destContent = await (0, import_promises12.readFile)(destSkill, "utf-8");
@@ -15674,6 +16151,7 @@ function registerInitCommand(program2) {
15674
16151
  await writeSettings(hookResult.data);
15675
16152
  printInfo(" Stop hook: verity analyze \u2713");
15676
16153
  printInfo(" Intent hook: verity intent capture \u2713");
16154
+ printInfo(" Baseline hook: verity baseline capture \u2713");
15677
16155
  } else {
15678
16156
  printWarn(` ${hookResult.error}`);
15679
16157
  printInfo(' Run "verity hooks install --force" to overwrite.');
@@ -15686,7 +16164,7 @@ function registerInitCommand(program2) {
15686
16164
  } catch (err) {
15687
16165
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
15688
16166
  }
15689
- const globalVerityDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".verity");
16167
+ const globalVerityDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".verity");
15690
16168
  await (0, import_promises12.mkdir)(globalVerityDir, { recursive: true });
15691
16169
  console.log("");
15692
16170
  printInfo("Verity initialized!");
@@ -15709,8 +16187,8 @@ function registerInitCommand(program2) {
15709
16187
  }
15710
16188
 
15711
16189
  // src/commands/uninstall.ts
15712
- var import_node_fs23 = require("node:fs");
15713
- var import_node_path17 = require("node:path");
16190
+ var import_node_fs25 = require("node:fs");
16191
+ var import_node_path18 = require("node:path");
15714
16192
  var SKILL_NAMES = [
15715
16193
  "verity-setup",
15716
16194
  "verity-analyze",
@@ -15729,11 +16207,11 @@ function registerUninstallCommand(program2) {
15729
16207
  const actions = [];
15730
16208
  const skillsRoot = projectPath(".claude/skills");
15731
16209
  for (const name of SKILL_NAMES) {
15732
- const dir = (0, import_node_path17.join)(skillsRoot, name);
15733
- if ((0, import_node_fs23.existsSync)(dir)) {
16210
+ const dir = (0, import_node_path18.join)(skillsRoot, name);
16211
+ if ((0, import_node_fs25.existsSync)(dir)) {
15734
16212
  actions.push({
15735
16213
  label: `Remove .claude/skills/${name}/`,
15736
- apply: () => (0, import_node_fs23.rmSync)(dir, { recursive: true, force: true })
16214
+ apply: () => (0, import_node_fs25.rmSync)(dir, { recursive: true, force: true })
15737
16215
  });
15738
16216
  }
15739
16217
  }
@@ -15747,24 +16225,24 @@ function registerUninstallCommand(program2) {
15747
16225
  });
15748
16226
  }
15749
16227
  const verityDir = projectPath(VERITY_DIR);
15750
- if ((0, import_node_fs23.existsSync)(verityDir)) {
16228
+ if ((0, import_node_fs25.existsSync)(verityDir)) {
15751
16229
  actions.push({
15752
16230
  label: `Remove ${VERITY_DIR}/`,
15753
- apply: () => (0, import_node_fs23.rmSync)(verityDir, { recursive: true, force: true })
16231
+ apply: () => (0, import_node_fs25.rmSync)(verityDir, { recursive: true, force: true })
15754
16232
  });
15755
16233
  }
15756
16234
  if (!keepVerityMd) {
15757
16235
  const verityMd = projectPath(VERITY_MD_FILE);
15758
- if ((0, import_node_fs23.existsSync)(verityMd)) {
16236
+ if ((0, import_node_fs25.existsSync)(verityMd)) {
15759
16237
  actions.push({
15760
16238
  label: `Remove ${VERITY_MD_FILE}`,
15761
- apply: () => (0, import_node_fs23.rmSync)(verityMd, { force: true })
16239
+ apply: () => (0, import_node_fs25.rmSync)(verityMd, { force: true })
15762
16240
  });
15763
16241
  }
15764
16242
  }
15765
16243
  const cleanupEmptyDir = (path) => {
15766
- if ((0, import_node_fs23.existsSync)(path) && (0, import_node_fs23.statSync)(path).isDirectory() && (0, import_node_fs23.readdirSync)(path).length === 0) {
15767
- (0, import_node_fs23.rmdirSync)(path);
16244
+ if ((0, import_node_fs25.existsSync)(path) && (0, import_node_fs25.statSync)(path).isDirectory() && (0, import_node_fs25.readdirSync)(path).length === 0) {
16245
+ (0, import_node_fs25.rmdirSync)(path);
15768
16246
  }
15769
16247
  };
15770
16248
  actions.push({
@@ -15775,11 +16253,11 @@ function registerUninstallCommand(program2) {
15775
16253
  }
15776
16254
  });
15777
16255
  const home = process.env.HOME ?? "";
15778
- const globalVerityDir = (0, import_node_path17.join)(home, ".verity");
15779
- if (purgeGlobal && (0, import_node_fs23.existsSync)(globalVerityDir)) {
16256
+ const globalVerityDir = (0, import_node_path18.join)(home, ".verity");
16257
+ if (purgeGlobal && (0, import_node_fs25.existsSync)(globalVerityDir)) {
15780
16258
  actions.push({
15781
16259
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
15782
- apply: () => (0, import_node_fs23.rmSync)(globalVerityDir, { recursive: true, force: true })
16260
+ apply: () => (0, import_node_fs25.rmSync)(globalVerityDir, { recursive: true, force: true })
15783
16261
  });
15784
16262
  }
15785
16263
  if (actions.length === 0) {
@@ -15973,8 +16451,8 @@ function registerTaskCommands(program2) {
15973
16451
  }
15974
16452
 
15975
16453
  // src/commands/reset.ts
15976
- var import_node_fs24 = require("node:fs");
15977
- var import_node_path18 = require("node:path");
16454
+ var import_node_fs26 = require("node:fs");
16455
+ var import_node_path19 = require("node:path");
15978
16456
  function registerResetCommand(program2) {
15979
16457
  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) => {
15980
16458
  const globals = program2.opts();
@@ -16011,11 +16489,11 @@ function registerResetCommand(program2) {
16011
16489
  }
16012
16490
  const cacheDir = projectPath(CACHE_DIR);
16013
16491
  let purged = 0;
16014
- if ((0, import_node_fs24.existsSync)(cacheDir)) {
16015
- for (const entry of (0, import_node_fs24.readdirSync)(cacheDir)) {
16492
+ if ((0, import_node_fs26.existsSync)(cacheDir)) {
16493
+ for (const entry of (0, import_node_fs26.readdirSync)(cacheDir)) {
16016
16494
  if (entry.startsWith("pending-")) {
16017
16495
  try {
16018
- (0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(cacheDir, entry));
16496
+ (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(cacheDir, entry));
16019
16497
  purged++;
16020
16498
  } catch {
16021
16499
  }
@@ -16030,19 +16508,19 @@ function registerResetCommand(program2) {
16030
16508
  projectPath(`${VERITY_DIR}/.last-analysis`)
16031
16509
  ];
16032
16510
  for (const file of filesToClear) {
16033
- if ((0, import_node_fs24.existsSync)(file)) {
16511
+ if ((0, import_node_fs26.existsSync)(file)) {
16034
16512
  try {
16035
- (0, import_node_fs24.writeFileSync)(file, "");
16513
+ (0, import_node_fs26.writeFileSync)(file, "");
16036
16514
  } catch {
16037
16515
  }
16038
16516
  }
16039
16517
  }
16040
16518
  if (opts.all) {
16041
16519
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
16042
- if ((0, import_node_fs24.existsSync)(logsDir)) {
16043
- for (const entry of (0, import_node_fs24.readdirSync)(logsDir)) {
16520
+ if ((0, import_node_fs26.existsSync)(logsDir)) {
16521
+ for (const entry of (0, import_node_fs26.readdirSync)(logsDir)) {
16044
16522
  try {
16045
- (0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(logsDir, entry));
16523
+ (0, import_node_fs26.unlinkSync)((0, import_node_path19.join)(logsDir, entry));
16046
16524
  } catch {
16047
16525
  }
16048
16526
  }
@@ -16301,7 +16779,7 @@ function registerRunCommand(program2) {
16301
16779
 
16302
16780
  // src/lib/telemetry.ts
16303
16781
  var import_promises13 = require("node:fs/promises");
16304
- var import_node_path19 = require("node:path");
16782
+ var import_node_path20 = require("node:path");
16305
16783
  var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
16306
16784
  var GITIGNORE_FILE = ".gitignore";
16307
16785
  var GITIGNORE_ENTRY = ".claude/settings.local.json";
@@ -16339,7 +16817,7 @@ async function readSettingsLocal() {
16339
16817
  }
16340
16818
  async function writeSettingsLocal(settings) {
16341
16819
  const file = projectPath(SETTINGS_LOCAL_FILE2);
16342
- await (0, import_promises13.mkdir)((0, import_node_path19.dirname)(file), { recursive: true });
16820
+ await (0, import_promises13.mkdir)((0, import_node_path20.dirname)(file), { recursive: true });
16343
16821
  await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
16344
16822
  }
16345
16823
  async function ensureGitignore() {
@@ -16435,7 +16913,7 @@ function registerTelemetryCommands(program2) {
16435
16913
  }
16436
16914
 
16437
16915
  // src/cli.ts
16438
- program.name("verity").description("CLI for Verity quality gate service").version("0.24.0-experimental.b2914b9").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16916
+ program.name("verity").description("CLI for Verity quality gate service").version("0.24.0-experimental.bdf0db7").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16439
16917
  registerAuthCommands(program);
16440
16918
  registerHooksCommands(program);
16441
16919
  registerIntentCommands(program);
@@ -16444,6 +16922,7 @@ registerConfigCommands(program);
16444
16922
  registerStatusCommand(program);
16445
16923
  registerFeedbackCommand(program);
16446
16924
  registerAnalyzeCommand(program);
16925
+ registerBaselineCommands(program);
16447
16926
  registerReviewCommand(program);
16448
16927
  registerGuardCommand(program);
16449
16928
  registerInitCommand(program);