@codacy/verity-cli 0.35.0-experimental.3211f79 → 0.35.0-experimental.3a13a5e

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
@@ -10192,14 +10192,14 @@ var require_public_api = __commonJS2({
10192
10192
  const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options);
10193
10193
  const parser$1 = new parser.Parser(lineCounter2?.addNewLine);
10194
10194
  const composer$1 = new composer.Composer(options);
10195
- const docs2 = Array.from(composer$1.compose(parser$1.parse(source2)));
10195
+ const docs = Array.from(composer$1.compose(parser$1.parse(source2)));
10196
10196
  if (prettyErrors && lineCounter2)
10197
- for (const doc of docs2) {
10197
+ for (const doc of docs) {
10198
10198
  doc.errors.forEach(errors.prettifyError(source2, lineCounter2));
10199
10199
  doc.warnings.forEach(errors.prettifyError(source2, lineCounter2));
10200
10200
  }
10201
- if (docs2.length > 0)
10202
- return docs2;
10201
+ if (docs.length > 0)
10202
+ return docs;
10203
10203
  return Object.assign([], { empty: true }, composer$1.streamInfo());
10204
10204
  }
10205
10205
  function parseDocument(source2, options = {}) {
@@ -23881,7 +23881,7 @@ async function readHookStdin() {
23881
23881
 
23882
23882
  // src/commands/standard.ts
23883
23883
  var import_promises10 = require("node:fs/promises");
23884
- var import_yaml5 = __toESM2(require_dist());
23884
+ var import_yaml6 = __toESM2(require_dist());
23885
23885
 
23886
23886
  // src/lib/synthesize.ts
23887
23887
  var import_node_fs35 = require("node:fs");
@@ -24520,36 +24520,8 @@ function collectRiskSignals(root, opts = {}) {
24520
24520
  return out2.slice(0, opts.limit ?? 10);
24521
24521
  }
24522
24522
 
24523
- // src/lib/repo-profile.ts
24524
- var PROFILE_SCHEMA = "repo-profile/v1";
24525
- var PROFILE_FILE = ".verity/repo-profile.json";
24526
- var LIMITS = {
24527
- languages: 20,
24528
- frameworks: 50,
24529
- dependencies: 300,
24530
- folders: 300,
24531
- commits: 500,
24532
- // The server allows 20; 10 keeps a full profile well under its body cap.
24533
- pathsPerCommit: 10,
24534
- docs: 8,
24535
- // A long doc is cut by section (fitDoc), so these bound how much of the
24536
- // team's conventions reach the writer, not where a doc is cut.
24537
- docBytes: 12288,
24538
- nestedDocBytes: 6144,
24539
- contributorDocBytes: 4096,
24540
- docsTotalBytes: 32768,
24541
- subject: 120,
24542
- path: 200,
24543
- name: 100
24544
- };
24545
- var WINDOW_DAYS = 180;
24546
- var MAX_PROFILE_BYTES = 120 * 1024;
24547
- var ROOT_DOCS = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".github/copilot-instructions.md"];
24548
- var CONTRIBUTOR_DOCS = ["CONTRIBUTING.md", "ARCHITECTURE.md"];
24549
- var README = "README.md";
24550
- var README_BYTES = 3072;
24551
- var NESTED_DOC = /^(?:[^./][^/]*\/){1,4}(?:AGENTS|CLAUDE)\.md$/;
24552
- var NOT_THE_TEAMS = /(?:^|\/)(?:examples?|samples?|templates?|[\w-]*-template|fixtures?|tests?|__tests__|vendor|third_party|node_modules)\//i;
24523
+ // src/lib/profile-text.ts
24524
+ var PATH_MAX = 200;
24553
24525
  var SECRET_PATTERNS = [
24554
24526
  /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\b/g,
24555
24527
  /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
@@ -24576,63 +24548,77 @@ function redact(text2) {
24576
24548
  function oneLine(text2, max) {
24577
24549
  return redact(text2.replace(/[\r\n\t]+/g, " ")).trim().slice(0, max);
24578
24550
  }
24579
- var DEP = /^[A-Za-z0-9@][A-Za-z0-9@._/:+-]*$/;
24580
- function dependencies(root, files) {
24581
- const seen = /* @__PURE__ */ new Set();
24582
- for (const name2 of declaredDependencies(root, files)) {
24583
- if (name2.length <= LIMITS.name && DEP.test(name2)) seen.add(name2);
24584
- if (seen.size >= LIMITS.dependencies) break;
24585
- }
24586
- return [...seen];
24587
- }
24588
24551
  function safePath(p) {
24589
- if (!p || p.length > LIMITS.path || p.startsWith("/") || p.includes("\\")) return null;
24552
+ if (!p || p.length > PATH_MAX || p.startsWith("/") || p.includes("\\")) return null;
24590
24553
  if (p.split("/").some((seg) => seg === "" || seg === "..")) return null;
24591
24554
  if (/[\u0000-\u001f\u007f]/.test(p)) return null;
24592
24555
  return p;
24593
24556
  }
24594
- function tree(files) {
24595
- const counts = /* @__PURE__ */ new Map();
24596
- for (const file of files) {
24597
- const parts2 = file.split("/");
24598
- for (let depth = 1; depth <= Math.min(3, parts2.length - 1); depth++) {
24599
- const folder = parts2.slice(0, depth).join("/");
24600
- counts.set(folder, (counts.get(folder) ?? 0) + 1);
24601
- }
24557
+
24558
+ // src/lib/profile-docs.ts
24559
+ var DOC_LIMITS = {
24560
+ docs: 8,
24561
+ // A long doc is cut by section (fitDoc), so these bound how much of the
24562
+ // team's conventions reach the writer, not where a doc is cut.
24563
+ docBytes: 12288,
24564
+ nestedDocBytes: 6144,
24565
+ contributorDocBytes: 4096,
24566
+ docsTotalBytes: 32768
24567
+ };
24568
+ var ROOT_DOCS = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".github/copilot-instructions.md"];
24569
+ var CONTRIBUTOR_DOCS = ["CONTRIBUTING.md", "ARCHITECTURE.md"];
24570
+ var README = "README.md";
24571
+ var README_BYTES = 3072;
24572
+ var NESTED_DOC = /^(?:[^./][^/]*\/){1,4}(?:AGENTS|CLAUDE)\.md$/;
24573
+ var NOT_THE_TEAMS = /(?:^|\/)(?:examples?|samples?|templates?|[\w-]*-template|fixtures?|tests?|__tests__|vendor|third_party|node_modules)\//i;
24574
+ var MANAGED_START = "<!-- verity-memory:start -->";
24575
+ var MANAGED_END = "<!-- verity-memory:end -->";
24576
+ var PRESERVE_START2 = "<!-- verity-memory:preserve -->";
24577
+ var PRESERVE_END2 = "<!-- /verity-memory:preserve -->";
24578
+ var FENCE_OPEN = /^[ \t]*(?:(?:[-*+]|\d+[.)])[ \t]+)*(`{3,}|~{3,})/;
24579
+ var FENCE_CLOSE = /^[ \t]*(`{3,}|~{3,})[ \t]*$/;
24580
+ var QUOTE = /^(?:[ \t]*>)+ ?/;
24581
+ var LIST_ITEM = /^[ \t]*(?:[-*+]|\d+[.)])[ \t]/;
24582
+ function indentWidth(line2) {
24583
+ let w = 0;
24584
+ for (const ch of line2) {
24585
+ if (ch === " ") w++;
24586
+ else if (ch === " ") w += 4;
24587
+ else break;
24602
24588
  }
24603
- return [...counts.entries()].filter(([path]) => safePath(path) !== null).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, LIMITS.folders).map(([path, n2]) => ({ path, files: n2 })).sort((a, b) => a.path.localeCompare(b.path));
24589
+ return w;
24604
24590
  }
24605
- function history(root) {
24606
- const res = runGitSync(root, [
24607
- "log",
24608
- `--since=${WINDOW_DAYS} days ago`,
24609
- "-n",
24610
- String(LIMITS.commits),
24611
- "--no-merges",
24612
- "--format=@@%x1f%s",
24613
- "--name-only",
24614
- "--"
24615
- ], { timeoutMs: 3e4 });
24616
- const commits = [];
24617
- if (!res.ok) return { window_days: WINDOW_DAYS, commits };
24618
- let current = null;
24619
- for (const line2 of res.stdout.split("\n")) {
24620
- if (line2.startsWith("@@")) {
24621
- if (current && current.subject) commits.push(current);
24622
- const raw = line2.slice(3);
24623
- current = { subject: oneLine(raw, LIMITS.subject), paths: [], fix: FIX_SUBJECT.test(raw) };
24591
+ function stripCode(text2) {
24592
+ const out2 = [];
24593
+ let fence = null;
24594
+ let underItem = false;
24595
+ for (const line2 of text2.split("\n")) {
24596
+ const body2 = line2.replace(QUOTE, "");
24597
+ if (fence) {
24598
+ const close = FENCE_CLOSE.exec(body2);
24599
+ if (close && close[1][0] === fence[0] && close[1].length >= fence.length) fence = null;
24624
24600
  continue;
24625
24601
  }
24626
- const path = safePath(line2.trim());
24627
- if (current && path && current.paths.length < LIMITS.pathsPerCommit) current.paths.push(path);
24602
+ const open = FENCE_OPEN.exec(body2);
24603
+ if (open) {
24604
+ fence = open[1];
24605
+ underItem = false;
24606
+ continue;
24607
+ }
24608
+ if (body2.trim() === "") {
24609
+ out2.push(line2);
24610
+ underItem = false;
24611
+ continue;
24612
+ }
24613
+ if (indentWidth(body2) >= 4) {
24614
+ if (underItem && LIST_ITEM.test(body2)) out2.push(line2);
24615
+ continue;
24616
+ }
24617
+ out2.push(line2);
24618
+ underItem = LIST_ITEM.test(body2);
24628
24619
  }
24629
- if (current && current.subject) commits.push(current);
24630
- return { window_days: WINDOW_DAYS, commits: commits.slice(0, LIMITS.commits) };
24620
+ return out2.join("\n");
24631
24621
  }
24632
- var MANAGED_START = "<!-- verity-memory:start -->";
24633
- var MANAGED_END = "<!-- verity-memory:end -->";
24634
- var PRESERVE_START2 = "<!-- verity-memory:preserve -->";
24635
- var PRESERVE_END2 = "<!-- /verity-memory:preserve -->";
24636
24622
  function docText(raw) {
24637
24623
  let text2 = raw;
24638
24624
  const start2 = text2.indexOf(MANAGED_START);
@@ -24644,7 +24630,7 @@ function docText(raw) {
24644
24630
  const kept = ps >= 0 && pe > ps ? block.slice(ps + PRESERVE_START2.length, pe) : "";
24645
24631
  text2 = text2.slice(0, start2) + kept + text2.slice(end + MANAGED_END.length);
24646
24632
  }
24647
- text2 = text2.replace(/^(```|~~~)[^\n]*\n[\s\S]*?^\1[^\n]*$/gm, "").replace(/<!--[\s\S]*?-->/g, "").replace(/\n{3,}/g, "\n\n");
24633
+ text2 = stripCode(text2.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*$/, "")).replace(/\n{3,}/g, "\n\n");
24648
24634
  return redact(text2).trim();
24649
24635
  }
24650
24636
  var RULE_TITLE = /convention|pattern|rule|gotcha|footgun|pitfall|best practice|guideline|principle|standard|style|architecture|design|security|database|migration|schema|\bapi\b|error|state|naming|structure|i18n|internationali[sz]|translation|important|critical|warning|do not|don't|never|must|avoid/i;
@@ -24677,18 +24663,23 @@ function sectionRank(s) {
24677
24663
  return title * 1e3 + Math.min(999, s.lines.filter((l) => NORMATIVE.test(l)).length);
24678
24664
  }
24679
24665
  function fitDoc(text2, budget) {
24680
- if (text2.length <= budget) return text2;
24666
+ if (utf8(text2) <= budget) return text2;
24681
24667
  const all = docSections(text2);
24682
- const size = (heading, lines) => (heading ? heading.length + 1 : 0) + lines.join("\n").length + 1;
24668
+ const lineBytes = (line2) => utf8(line2) + 1;
24669
+ const GAP = lineBytes("\u2026");
24683
24670
  const chosen = /* @__PURE__ */ new Map();
24671
+ const headingsCounted = /* @__PURE__ */ new Set();
24684
24672
  let used2 = 0;
24685
24673
  for (const s of [...all].sort((a, b) => sectionRank(b) - sectionRank(a) || a.order - b.order)) {
24686
24674
  if (!s.lines.some((l) => l.trim())) continue;
24675
+ const parents = s.parents.filter((p) => !headingsCounted.has(p) && !chosen.has(p));
24676
+ const parentBytes = parents.reduce((n2, p) => n2 + lineBytes(all[p].heading ?? ""), 0);
24687
24677
  const lean = s.lines.filter((l) => NORMATIVE.test(l));
24688
24678
  for (const lines of [s.lines, lean]) {
24689
- const n2 = size(s.heading, lines);
24679
+ const n2 = (s.heading ? lineBytes(s.heading) : 0) + lines.reduce((m, l) => m + lineBytes(l), 0) + parentBytes + GAP;
24690
24680
  if (lines.length && used2 + n2 <= budget) {
24691
24681
  chosen.set(s.order, lines);
24682
+ for (const p of parents) headingsCounted.add(p);
24692
24683
  used2 += n2;
24693
24684
  break;
24694
24685
  }
@@ -24712,23 +24703,38 @@ function fitDoc(text2, budget) {
24712
24703
  if (s.heading) out2.push(s.heading);
24713
24704
  out2.push(...lines);
24714
24705
  }
24715
- return out2.join("\n").replace(/\n{3,}/g, "\n\n").trim().slice(0, budget);
24706
+ return cutToBytes(out2.join("\n").replace(/\n{3,}/g, "\n\n").trim(), budget);
24707
+ }
24708
+ function utf8(text2) {
24709
+ return Buffer.byteLength(text2, "utf8");
24710
+ }
24711
+ function cutToBytes(text2, max) {
24712
+ if (utf8(text2) <= max) return text2;
24713
+ let out2 = "";
24714
+ let n2 = 0;
24715
+ for (const ch of text2) {
24716
+ const b = utf8(ch);
24717
+ if (n2 + b > max) break;
24718
+ out2 += ch;
24719
+ n2 += b;
24720
+ }
24721
+ return out2;
24716
24722
  }
24717
24723
  var normal = (line2) => line2.trim().toLowerCase().replace(/\s+/g, " ");
24718
- function docs(root, files) {
24724
+ function readDocs(root, files) {
24719
24725
  const nested = files.filter((f) => NESTED_DOC.test(f) && !NOT_THE_TEAMS.test(f) && safePath(f) !== null).sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
24720
24726
  const plan = [
24721
- ...ROOT_DOCS.map((n2) => [n2, LIMITS.docBytes]),
24722
- ...nested.map((n2) => [n2, LIMITS.nestedDocBytes]),
24723
- ...CONTRIBUTOR_DOCS.map((n2) => [n2, LIMITS.contributorDocBytes]),
24727
+ ...ROOT_DOCS.map((n2) => [n2, DOC_LIMITS.docBytes]),
24728
+ ...nested.map((n2) => [n2, DOC_LIMITS.nestedDocBytes]),
24729
+ ...CONTRIBUTOR_DOCS.map((n2) => [n2, DOC_LIMITS.contributorDocBytes]),
24724
24730
  [README, README_BYTES]
24725
24731
  ];
24726
24732
  const out2 = [];
24727
24733
  const sent = /* @__PURE__ */ new Set();
24728
24734
  let total = 0;
24729
24735
  for (const [name2, cap2] of plan) {
24730
- const room = Math.min(cap2, LIMITS.docsTotalBytes - total);
24731
- if (out2.length >= LIMITS.docs || room < 200) break;
24736
+ const room = Math.min(cap2, DOC_LIMITS.docsTotalBytes - total);
24737
+ if (out2.length >= DOC_LIMITS.docs || room < 200) break;
24732
24738
  if (name2 === README && out2.some((d) => ROOT_DOCS.includes(d.name))) continue;
24733
24739
  const read2 = readRegularFileInRoot(root, name2, 256 * 1024);
24734
24740
  if (!read2.ok) continue;
@@ -24736,13 +24742,78 @@ function docs(root, files) {
24736
24742
  const text2 = fitDoc(fresh, room);
24737
24743
  if (!text2) continue;
24738
24744
  for (const l of text2.split("\n")) if (normal(l).length >= 25) sent.add(normal(l));
24739
- total += text2.length;
24745
+ total += utf8(text2);
24740
24746
  out2.push({ name: name2, text: text2 });
24741
24747
  }
24742
24748
  return out2;
24743
24749
  }
24750
+
24751
+ // src/lib/repo-profile.ts
24752
+ var PROFILE_SCHEMA = "repo-profile/v1";
24753
+ var PROFILE_FILE = ".verity/repo-profile.json";
24754
+ var LIMITS = {
24755
+ languages: 20,
24756
+ frameworks: 50,
24757
+ dependencies: 300,
24758
+ folders: 300,
24759
+ commits: 500,
24760
+ // The server allows 20; 10 keeps a full profile well under its body cap.
24761
+ pathsPerCommit: 10,
24762
+ subject: 120,
24763
+ path: 200,
24764
+ name: 100
24765
+ };
24766
+ var WINDOW_DAYS = 180;
24767
+ var MAX_PROFILE_BYTES = 120 * 1024;
24768
+ var DEP = /^[A-Za-z0-9@][A-Za-z0-9@._/:+-]*$/;
24769
+ function dependencies(root, files) {
24770
+ const seen = /* @__PURE__ */ new Set();
24771
+ for (const name2 of declaredDependencies(root, files)) {
24772
+ if (name2.length <= LIMITS.name && DEP.test(name2)) seen.add(name2);
24773
+ if (seen.size >= LIMITS.dependencies) break;
24774
+ }
24775
+ return [...seen];
24776
+ }
24777
+ function tree(files) {
24778
+ const counts = /* @__PURE__ */ new Map();
24779
+ for (const file of files) {
24780
+ const parts2 = file.split("/");
24781
+ for (let depth = 1; depth <= Math.min(3, parts2.length - 1); depth++) {
24782
+ const folder = parts2.slice(0, depth).join("/");
24783
+ counts.set(folder, (counts.get(folder) ?? 0) + 1);
24784
+ }
24785
+ }
24786
+ return [...counts.entries()].filter(([path]) => safePath(path) !== null).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, LIMITS.folders).map(([path, n2]) => ({ path, files: n2 })).sort((a, b) => a.path.localeCompare(b.path));
24787
+ }
24788
+ function history(root) {
24789
+ const res = runGitSync(root, [
24790
+ "log",
24791
+ `--since=${WINDOW_DAYS} days ago`,
24792
+ "-n",
24793
+ String(LIMITS.commits),
24794
+ "--no-merges",
24795
+ "--format=@@%x1f%s",
24796
+ "--name-only",
24797
+ "--"
24798
+ ], { timeoutMs: 3e4 });
24799
+ const commits = [];
24800
+ if (!res.ok) return { window_days: WINDOW_DAYS, commits };
24801
+ let current = null;
24802
+ for (const line2 of res.stdout.split("\n")) {
24803
+ if (line2.startsWith("@@")) {
24804
+ if (current && current.subject) commits.push(current);
24805
+ const raw = line2.slice(3);
24806
+ current = { subject: oneLine(raw, LIMITS.subject), paths: [], fix: FIX_SUBJECT.test(raw) };
24807
+ continue;
24808
+ }
24809
+ const path = safePath(line2.trim());
24810
+ if (current && path && current.paths.length < LIMITS.pathsPerCommit) current.paths.push(path);
24811
+ }
24812
+ if (current && current.subject) commits.push(current);
24813
+ return { window_days: WINDOW_DAYS, commits: commits.slice(0, LIMITS.commits) };
24814
+ }
24744
24815
  function fitProfile(p) {
24745
- const size = () => JSON.stringify(p).length;
24816
+ const size = () => Buffer.byteLength(JSON.stringify(p), "utf8");
24746
24817
  if (size() <= MAX_PROFILE_BYTES) return p;
24747
24818
  const commits = p.history.commits;
24748
24819
  for (const wantFix of [false, true]) {
@@ -24750,6 +24821,9 @@ function fitProfile(p) {
24750
24821
  if (commits[i2].fix === wantFix) commits.splice(i2, 1);
24751
24822
  }
24752
24823
  }
24824
+ while (size() > MAX_PROFILE_BYTES && p.docs.length > 0) p.docs.pop();
24825
+ while (size() > MAX_PROFILE_BYTES && p.tree.length > 0) p.tree.pop();
24826
+ while (size() > MAX_PROFILE_BYTES && p.dependencies.length > 0) p.dependencies.pop();
24753
24827
  return p;
24754
24828
  }
24755
24829
  function buildRepoProfile(root = repoRoot()) {
@@ -24771,7 +24845,7 @@ function buildRepoProfile(root = repoRoot()) {
24771
24845
  dependencies: dependencies(root, files),
24772
24846
  tree: tree(files),
24773
24847
  history: history(root),
24774
- docs: docs(root, files)
24848
+ docs: readDocs(root, files)
24775
24849
  });
24776
24850
  }
24777
24851
  function describeProfile(p) {
@@ -24795,8 +24869,29 @@ function consentLines(p) {
24795
24869
  ];
24796
24870
  }
24797
24871
 
24872
+ // src/commands/standard-generate.ts
24873
+ var import_yaml5 = __toESM2(require_dist());
24874
+
24798
24875
  // src/lib/generate-standard.ts
24799
24876
  var import_yaml4 = __toESM2(require_dist());
24877
+ async function generationStatus(opts) {
24878
+ const res = await apiRequest({
24879
+ method: "GET",
24880
+ path: "/standards/generate",
24881
+ serviceUrl: opts.serviceUrl,
24882
+ token: opts.token,
24883
+ verbose: opts.verbose,
24884
+ cmd: "standard_generate_status"
24885
+ });
24886
+ if (!res.ok) {
24887
+ if (res.http_status === 404 && /Unknown route/.test(res.error)) return "unsupported";
24888
+ if (res.http_status === 400 && /Version must be a positive integer/.test(res.error)) return "unsupported";
24889
+ return "unknown";
24890
+ }
24891
+ if (res.data.enabled === true) return "on";
24892
+ if (res.data.enabled === false) return "off";
24893
+ return "unknown";
24894
+ }
24800
24895
  var GENERATE_TIMEOUT_MS = 6e4;
24801
24896
  function rulesFrom(content, before) {
24802
24897
  const list2 = Array.isArray(content.custom_patterns) ? content.custom_patterns : [];
@@ -24895,6 +24990,94 @@ async function promptYes(question, opts) {
24895
24990
  return a === "" || a === "y" || a === "yes";
24896
24991
  }
24897
24992
 
24993
+ // src/commands/standard-generate.ts
24994
+ function generationBase() {
24995
+ const local = readRepoFile(STANDARD_FILE);
24996
+ try {
24997
+ const parsed = local !== null ? (0, import_yaml5.parse)(local) : null;
24998
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : buildStandard({ detected: detectProject() }, loadCatalog());
24999
+ } catch (err2) {
25000
+ printError(`${STANDARD_FILE} is not valid YAML: ${err2.message}. Fix it, or delete it, and run this again.`);
25001
+ process.exit(1);
25002
+ }
25003
+ }
25004
+ async function consentToSend(profile, yes) {
25005
+ try {
25006
+ writeRepoFile(PROFILE_FILE, JSON.stringify(profile, null, 2) + "\n");
25007
+ } catch (err2) {
25008
+ printError(`Could not write ${PROFILE_FILE} for you to read: ${err2.message}`);
25009
+ process.exit(1);
25010
+ }
25011
+ for (const line2 of consentLines(profile)) printInfo(line2);
25012
+ if (yes) return true;
25013
+ if (!interactive()) {
25014
+ printError(`Nothing was sent. Read ${PROFILE_FILE}, then run this again with --yes to send it.`);
25015
+ process.exit(1);
25016
+ }
25017
+ const send = await promptYes("Send the profile? [Y/n] ", { nonInteractive: false });
25018
+ if (!send) printInfo("Not sent. Nothing changed.");
25019
+ return send;
25020
+ }
25021
+ async function runGenerate(globals, opts) {
25022
+ const tokenResult = await resolveToken(globals.token);
25023
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
25024
+ if (!tokenResult.ok || !urlResult.ok) {
25025
+ printError("Sign in first: verity login. Rule generation needs your Verity account.");
25026
+ process.exit(1);
25027
+ }
25028
+ const status = await generationStatus({ serviceUrl: urlResult.data, token: tokenResult.data.token, verbose: globals.verbose });
25029
+ if (opts.status) {
25030
+ process.stdout.write(`${status}
25031
+ `);
25032
+ return;
25033
+ }
25034
+ if (status === "off") {
25035
+ printWarn("Rule generation is not switched on for this project yet. The built-in rules apply. Nothing was sent.");
25036
+ return;
25037
+ }
25038
+ if (status === "unsupported") {
25039
+ printWarn("This Verity service does not write project rules yet: it is older than this CLI. The built-in rules apply. Nothing was sent.");
25040
+ return;
25041
+ }
25042
+ const base = generationBase();
25043
+ const profile = buildRepoProfile();
25044
+ if (!await consentToSend(profile, opts.yes === true)) return;
25045
+ const spinner = startSpinner("Writing rules for this repository");
25046
+ const outcome = await generateStandard({
25047
+ serviceUrl: urlResult.data,
25048
+ token: tokenResult.data.token,
25049
+ profile,
25050
+ base,
25051
+ verbose: globals.verbose
25052
+ });
25053
+ if (outcome.status === "created") {
25054
+ spinner.succeed(`${outcome.rules.length} ${outcome.rules.length === 1 ? "rule" : "rules"} for this repository \xB7 Standard v${outcome.version}`);
25055
+ for (const r of outcome.rules) {
25056
+ printInfo(` ${r.id}`);
25057
+ printInfo(` ${r.description}`);
25058
+ printInfo(` Applies: ${r.scope} \xB7 ${r.basis}`);
25059
+ }
25060
+ if (outcome.rejected > 0) printInfo(` ${outcome.rejected} proposed rule(s) were left out: too generic, or not safe to use.`);
25061
+ printInfo(`Edit them in ${STANDARD_FILE}, then: verity standard push`);
25062
+ if (outcome.warning) printWarn(outcome.warning);
25063
+ return;
25064
+ }
25065
+ if (outcome.status === "exists") {
25066
+ spinner.warn('This project already has a Standard on the Verity service. Run "verity init" to use it.');
25067
+ return;
25068
+ }
25069
+ if (outcome.status === "disabled") {
25070
+ spinner.warn("Rule generation is not switched on for this project yet. The built-in rules apply.");
25071
+ return;
25072
+ }
25073
+ if (outcome.status === "unsupported") {
25074
+ spinner.warn("This Verity service does not write project rules yet: it is older than this CLI. The built-in rules apply.");
25075
+ return;
25076
+ }
25077
+ spinner.warn(`The rules could not be written: ${outcome.reason}`);
25078
+ process.exit(1);
25079
+ }
25080
+
24898
25081
  // src/commands/standard.ts
24899
25082
  function registerStandardCommands(program2) {
24900
25083
  const standard = program2.command("standard").description("Manage the project Standard");
@@ -24911,15 +25094,15 @@ function registerStandardCommands(program2) {
24911
25094
  serviceUrl: urlResult.ok ? urlResult.data : void 0
24912
25095
  });
24913
25096
  if ("refused" in result) {
24914
- printWarn(result.refused);
25097
+ printWarn(terminalText(result.refused, 300));
24915
25098
  process.exit(1);
24916
25099
  }
24917
- printInfo(` ${describeDetected(result.detected)}`);
24918
- for (const path of result.written) printInfo(` ${path} \u2713`);
24919
- for (const note of result.notes) printWarn(` ${note}`);
25100
+ printInfo(` ${terminalText(describeDetected(result.detected), 400)}`);
25101
+ for (const path of result.written) printInfo(` ${terminalText(path, 200)} \u2713`);
25102
+ for (const note of result.notes) printWarn(` ${terminalText(note, 300)}`);
24920
25103
  if (opts.push) {
24921
25104
  const pushed = await pushStandard(globals);
24922
- for (const line2 of pushed.lines) printInfo(` ${line2}`);
25105
+ for (const line2 of pushed.lines) printInfo(` ${terminalText(line2, 600)}`);
24923
25106
  if (pushed.standardVersion !== null) {
24924
25107
  await correctVerityMdVersion({
24925
25108
  standard: result.standard,
@@ -24930,7 +25113,7 @@ function registerStandardCommands(program2) {
24930
25113
  }
24931
25114
  }
24932
25115
  });
24933
- standard.command("generate").description("Have Verity write project-specific rules from a profile of this repository (asks before sending it)").option("--yes", `Send the profile without asking (you have read ${PROFILE_FILE})`).action((opts) => runGenerate(program2.opts(), opts));
25116
+ standard.command("generate").description("Have Verity write project-specific rules from a profile of this repository (asks before sending it)").option("--yes", `Send the profile without asking (you have read ${PROFILE_FILE})`).option("--status", "Print whether the service writes project rules for this project: on, off, unsupported or unknown. Sends nothing.").action((opts) => runGenerate(program2.opts(), opts));
24934
25117
  standard.command("push").description("Upload the project Standard to the service").option("--file <path>", "Path to standard YAML file", STANDARD_FILE).option("--created-by <name>", "Who created this version", "claude-code").action(async (opts) => {
24935
25118
  const globals = program2.opts();
24936
25119
  const tokenResult = await resolveToken(globals.token);
@@ -24952,7 +25135,7 @@ function registerStandardCommands(program2) {
24952
25135
  }
24953
25136
  let content;
24954
25137
  try {
24955
- content = (0, import_yaml5.parse)(yamlContent);
25138
+ content = (0, import_yaml6.parse)(yamlContent);
24956
25139
  } catch (err2) {
24957
25140
  printError(`Invalid YAML in ${opts.file}: ${err2.message}`);
24958
25141
  process.exit(1);
@@ -24968,10 +25151,10 @@ function registerStandardCommands(program2) {
24968
25151
  verbose: globals.verbose
24969
25152
  });
24970
25153
  if (!result.ok) {
24971
- printError(result.error);
25154
+ printError(terminalText(result.error, 300));
24972
25155
  process.exit(1);
24973
25156
  }
24974
- printInfo(`Standard uploaded (version ${result.data.version})`);
25157
+ printInfo(`Standard uploaded (version ${versionText(result.data.version)})`);
24975
25158
  printJson(result.data);
24976
25159
  });
24977
25160
  standard.command("get").description("Get the current Standard from the service").option("--version <n>", "Specific version number").action(async (opts) => {
@@ -24986,7 +25169,15 @@ function registerStandardCommands(program2) {
24986
25169
  printError(urlResult.error);
24987
25170
  process.exit(1);
24988
25171
  }
24989
- const path = opts.version ? `/standards/${opts.version}` : "/standards/latest";
25172
+ let path = "/standards/latest";
25173
+ if (opts.version !== void 0) {
25174
+ const version = Number(opts.version);
25175
+ if (!/^[1-9][0-9]*$/.test(String(opts.version)) || !Number.isSafeInteger(version)) {
25176
+ printError(`--version: "${terminalText(opts.version, 40)}" is not a Standard version. Use a positive whole number, such as --version 3.`);
25177
+ process.exit(1);
25178
+ }
25179
+ path = `/standards/${version}`;
25180
+ }
24990
25181
  const result = await apiRequest({
24991
25182
  method: "GET",
24992
25183
  path,
@@ -24995,7 +25186,7 @@ function registerStandardCommands(program2) {
24995
25186
  verbose: globals.verbose
24996
25187
  });
24997
25188
  if (!result.ok) {
24998
- printError(result.error);
25189
+ printError(terminalText(result.error, 300));
24999
25190
  process.exit(1);
25000
25191
  }
25001
25192
  printJson(result.data);
@@ -25020,93 +25211,24 @@ function registerStandardCommands(program2) {
25020
25211
  verbose: globals.verbose
25021
25212
  });
25022
25213
  if (!result.ok) {
25023
- printError(result.error);
25214
+ printError(terminalText(result.error, 300));
25024
25215
  process.exit(1);
25025
25216
  }
25026
- const versions = result.data.versions;
25217
+ const versions = Array.isArray(result.data.versions) ? result.data.versions : [];
25027
25218
  if (versions.length === 0) {
25028
25219
  printInfo("No Standard versions found.");
25029
25220
  return;
25030
25221
  }
25031
25222
  printInfo("Standard versions:");
25032
25223
  for (const v of versions) {
25033
- const date = v.created_at.slice(0, 19).replace("T", " ");
25224
+ const date = typeof v.created_at === "string" ? v.created_at.slice(0, 19).replace("T", " ") : "unknown date";
25034
25225
  const by = v.created_by ?? "unknown";
25035
- printInfo(` v${v.version} ${date} (by ${by})`);
25226
+ printInfo(` v${versionText(v.version)} ${terminalText(date, 40)} (by ${terminalText(by, 80)})`);
25036
25227
  }
25037
25228
  });
25038
25229
  }
25039
- function generationBase() {
25040
- const local = readRepoFile(STANDARD_FILE);
25041
- try {
25042
- const parsed = local !== null ? (0, import_yaml5.parse)(local) : null;
25043
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : buildStandard({ detected: detectProject() }, loadCatalog());
25044
- } catch (err2) {
25045
- printError(`${STANDARD_FILE} is not valid YAML: ${err2.message}. Fix it, or delete it, and run this again.`);
25046
- process.exit(1);
25047
- }
25048
- }
25049
- async function consentToSend(profile, yes) {
25050
- try {
25051
- writeRepoFile(PROFILE_FILE, JSON.stringify(profile, null, 2) + "\n");
25052
- } catch (err2) {
25053
- printError(`Could not write ${PROFILE_FILE} for you to read: ${err2.message}`);
25054
- process.exit(1);
25055
- }
25056
- for (const line2 of consentLines(profile)) printInfo(line2);
25057
- if (yes) return true;
25058
- if (!interactive()) {
25059
- printError(`Nothing was sent. Read ${PROFILE_FILE}, then run this again with --yes to send it.`);
25060
- process.exit(1);
25061
- }
25062
- const send = await promptYes("Send the profile? [Y/n] ", { nonInteractive: false });
25063
- if (!send) printInfo("Not sent. Nothing changed.");
25064
- return send;
25065
- }
25066
- async function runGenerate(globals, opts) {
25067
- const tokenResult = await resolveToken(globals.token);
25068
- const urlResult = await resolveServiceUrl(globals.serviceUrl);
25069
- if (!tokenResult.ok || !urlResult.ok) {
25070
- printError("Sign in first: verity login. Rule generation needs your Verity account.");
25071
- process.exit(1);
25072
- }
25073
- const base = generationBase();
25074
- const profile = buildRepoProfile();
25075
- if (!await consentToSend(profile, opts.yes === true)) return;
25076
- const spinner = startSpinner("Writing rules for this repository");
25077
- const outcome = await generateStandard({
25078
- serviceUrl: urlResult.data,
25079
- token: tokenResult.data.token,
25080
- profile,
25081
- base,
25082
- verbose: globals.verbose
25083
- });
25084
- if (outcome.status === "created") {
25085
- spinner.succeed(`${outcome.rules.length} ${outcome.rules.length === 1 ? "rule" : "rules"} for this repository \xB7 Standard v${outcome.version}`);
25086
- for (const r of outcome.rules) {
25087
- printInfo(` ${r.id}`);
25088
- printInfo(` ${r.description}`);
25089
- printInfo(` Applies: ${r.scope} \xB7 ${r.basis}`);
25090
- }
25091
- if (outcome.rejected > 0) printInfo(` ${outcome.rejected} proposed rule(s) were left out: too generic, or not safe to use.`);
25092
- printInfo(`Edit them in ${STANDARD_FILE}, then: verity standard push`);
25093
- if (outcome.warning) printWarn(outcome.warning);
25094
- return;
25095
- }
25096
- if (outcome.status === "exists") {
25097
- spinner.warn('This project already has a Standard on the Verity service. Run "verity init" to use it.');
25098
- return;
25099
- }
25100
- if (outcome.status === "disabled") {
25101
- spinner.warn("Rule generation is not switched on for this project yet. The built-in rules apply.");
25102
- return;
25103
- }
25104
- if (outcome.status === "unsupported") {
25105
- spinner.warn("This Verity service does not write project rules yet: it is older than this CLI. The built-in rules apply.");
25106
- return;
25107
- }
25108
- spinner.warn(`The rules could not be written: ${outcome.reason}`);
25109
- process.exit(1);
25230
+ function versionText(value) {
25231
+ return typeof value === "number" && Number.isSafeInteger(value) ? String(value) : "?";
25110
25232
  }
25111
25233
 
25112
25234
  // src/commands/config.ts
@@ -25163,11 +25285,11 @@ async function currentReviewMoments(pluginMode) {
25163
25285
  const cfg = readProjectConfig();
25164
25286
  const hooks = await checkAllVerityHooks();
25165
25287
  const turn = cfg.turn_review !== "off" && (pluginMode || hooks.stop);
25166
- const git2 = pluginMode ? cfg.git_moments : hooks.guard ? hooks.guardOn.length > 0 ? hooks.guardOn : cfg.git_moments : [];
25288
+ const git3 = pluginMode ? cfg.git_moments : hooks.guard ? hooks.guardOn.length > 0 ? hooks.guardOn : cfg.git_moments : [];
25167
25289
  return [
25168
25290
  ...turn ? ["stop"] : [],
25169
- ...git2.includes("commit") ? ["pre-commit"] : [],
25170
- ...git2.includes("push") ? ["pre-push"] : []
25291
+ ...git3.includes("commit") ? ["pre-commit"] : [],
25292
+ ...git3.includes("push") ? ["pre-push"] : []
25171
25293
  ];
25172
25294
  }
25173
25295
  async function setReviewMoments(moments, pluginMode) {
@@ -26120,7 +26242,7 @@ function createRun(opts, globals) {
26120
26242
 
26121
26243
  // src/commands/analyze/index.ts
26122
26244
  var import_node_fs55 = require("node:fs");
26123
- var import_node_path42 = require("node:path");
26245
+ var import_node_path43 = require("node:path");
26124
26246
 
26125
26247
  // src/lib/repo-context.ts
26126
26248
  var import_node_child_process11 = require("node:child_process");
@@ -28244,7 +28366,7 @@ function channelSilence(input) {
28244
28366
  // src/lib/cli-version.ts
28245
28367
  function cliVersion() {
28246
28368
  try {
28247
- return true ? "0.35.0-experimental.3211f79" : "dev";
28369
+ return true ? "0.35.0-experimental.3a13a5e" : "dev";
28248
28370
  } catch {
28249
28371
  return "dev";
28250
28372
  }
@@ -28285,6 +28407,8 @@ async function sendSkipBeacon(ctx, reason) {
28285
28407
 
28286
28408
  // src/lib/static-analysis.ts
28287
28409
  var import_node_child_process12 = require("node:child_process");
28410
+ var import_node_module = require("node:module");
28411
+ var import_node_path34 = require("node:path");
28288
28412
  var import_node_fs44 = require("node:fs");
28289
28413
 
28290
28414
  // src/lib/static-config.ts
@@ -28331,19 +28455,44 @@ var SEVERITY_ORDER = {
28331
28455
  Low: 3
28332
28456
  };
28333
28457
  function isCodacyAvailable() {
28334
- return codacyAnalysisPath() !== null;
28458
+ return resolveAnalyzer() !== null;
28459
+ }
28460
+ var NO_BUNDLED_ANALYZER_ENV = "VERITY_NO_BUNDLED_ANALYZER";
28461
+ function bundledAnalyzer(from = __filename) {
28462
+ try {
28463
+ const pkgPath = (0, import_node_module.createRequire)(from).resolve("@codacy/analysis-cli/package.json");
28464
+ const pkg = JSON.parse((0, import_node_fs44.readFileSync)(pkgPath, "utf-8"));
28465
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["codacy-analysis"];
28466
+ if (typeof bin !== "string" || bin === "") return null;
28467
+ const entry = (0, import_node_path34.join)((0, import_node_path34.dirname)(pkgPath), bin);
28468
+ if (!(0, import_node_fs44.statSync)(entry).isFile()) return null;
28469
+ return { path: entry, version: typeof pkg.version === "string" ? pkg.version : "unknown" };
28470
+ } catch {
28471
+ return null;
28472
+ }
28335
28473
  }
28336
- function codacyAnalysisPath() {
28337
- return whichSync("codacy-analysis");
28474
+ function resolveAnalyzer(opts = {}) {
28475
+ const env = opts.env ?? process.env;
28476
+ if (env[NO_BUNDLED_ANALYZER_ENV] !== "1") {
28477
+ const bundled = (opts.bundled ?? bundledAnalyzer)();
28478
+ if (bundled) return { source: "bundled", path: bundled.path, version: bundled.version };
28479
+ }
28480
+ const onPath = (opts.onPath ?? (() => whichSync("codacy-analysis")))();
28481
+ return onPath ? { source: "path", path: onPath } : null;
28482
+ }
28483
+ function analyzerCommand(analyzer, argv, execPath = process.execPath) {
28484
+ if (analyzer.source === "bundled") return { command: execPath, args: [analyzer.path, ...argv] };
28485
+ return { command: analyzer.path, args: argv };
28338
28486
  }
28339
28487
  function startTrivyPrewarm(opts = {}) {
28340
28488
  if ((opts.platform ?? process.platform) === "win32") return false;
28341
28489
  if ((opts.env ?? process.env).CI) return false;
28342
- const analyzer = codacyAnalysisPath();
28490
+ const analyzer = (opts.resolve ?? resolveAnalyzer)();
28343
28491
  const config = ensureStaticConfig();
28344
28492
  if (!analyzer || !config) return false;
28345
28493
  try {
28346
- const child = (opts.run ?? import_node_child_process12.spawn)(analyzer, buildAnalyzerArgv([STATIC_ANALYSIS_CONFIG_FILE], config), {
28494
+ const { command, args: args2 } = analyzerCommand(analyzer, buildAnalyzerArgv([STATIC_ANALYSIS_CONFIG_FILE], config));
28495
+ const child = (opts.run ?? import_node_child_process12.spawn)(command, args2, {
28347
28496
  cwd: repoRoot(),
28348
28497
  detached: true,
28349
28498
  stdio: "ignore"
@@ -28390,7 +28539,7 @@ function runCodacyAnalysisIfAvailable(files) {
28390
28539
  if (!isCodacyAvailable()) {
28391
28540
  return withFailure(
28392
28541
  "analyzer_unavailable",
28393
- "@codacy/analysis-cli was not found on PATH \u2014 no static analysis ran"
28542
+ "@codacy/analysis-cli was not bundled with this Verity install and was not found on PATH \u2014 no static analysis ran. Reinstall Verity: npm install -g @codacy/verity-cli"
28394
28543
  );
28395
28544
  }
28396
28545
  return runCodacyAnalysis(files);
@@ -28413,8 +28562,10 @@ function runCodacyAnalysis(files) {
28413
28562
  "Could not write .verity/static-analysis.json \u2014 no static analysis ran"
28414
28563
  );
28415
28564
  }
28416
- const analyzer = codacyAnalysisPath() ?? "codacy-analysis";
28417
- const proc = (0, import_node_child_process12.spawnSync)(analyzer, buildAnalyzerArgv(existingFiles, configPath), {
28565
+ const argv = buildAnalyzerArgv(existingFiles, configPath);
28566
+ const resolved = resolveAnalyzer();
28567
+ const { command, args: args2 } = resolved ? analyzerCommand(resolved, argv) : { command: "codacy-analysis", args: argv };
28568
+ const proc = (0, import_node_child_process12.spawnSync)(command, args2, {
28418
28569
  encoding: "utf-8",
28419
28570
  maxBuffer: 10 * 1024 * 1024
28420
28571
  });
@@ -28678,7 +28829,7 @@ async function scope(run4) {
28678
28829
 
28679
28830
  // src/lib/specs.ts
28680
28831
  var import_node_fs45 = require("node:fs");
28681
- var import_node_path34 = require("node:path");
28832
+ var import_node_path35 = require("node:path");
28682
28833
  var SPEC_CANDIDATES = [
28683
28834
  "CLAUDE.md",
28684
28835
  "AGENTS.md",
@@ -28700,7 +28851,7 @@ var UNCONSULTED_TOTAL_BYTES = 30720;
28700
28851
  function readSpecFiles(specsOpt, root) {
28701
28852
  const out2 = [];
28702
28853
  for (const raw of specsOpt.split(",").map((f) => f.trim()).filter(Boolean)) {
28703
- const candidate = (0, import_node_path34.isAbsolute)(raw) ? (0, import_node_path34.relative)(root, raw) : raw;
28854
+ const candidate = (0, import_node_path35.isAbsolute)(raw) ? (0, import_node_path35.relative)(root, raw) : raw;
28704
28855
  const content = readFileInside(root, candidate, MAX_EXPLICIT_SPEC_FILE_BYTES);
28705
28856
  if (content === null) continue;
28706
28857
  out2.push({ path: raw, content });
@@ -28759,7 +28910,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
28759
28910
  try {
28760
28911
  const entries = (0, import_node_fs45.readdirSync)(dir, { withFileTypes: true });
28761
28912
  for (const entry of entries) {
28762
- const fullPath = (0, import_node_path34.join)(dir, entry.name);
28913
+ const fullPath = (0, import_node_path35.join)(dir, entry.name);
28763
28914
  if (entry.isFile() && entry.name.endsWith(".md")) {
28764
28915
  result.push(fullPath);
28765
28916
  } else if (entry.isDirectory() && depth < maxDepth - 1) {
@@ -28772,10 +28923,10 @@ function findMdFiles(dir, maxDepth, depth = 0) {
28772
28923
  }
28773
28924
  function discoverPlans() {
28774
28925
  const home = process.env.HOME ?? "";
28775
- const homePlansDir = (0, import_node_path34.join)(home, ".claude", "plans");
28926
+ const homePlansDir = (0, import_node_path35.join)(home, ".claude", "plans");
28776
28927
  const sources = [];
28777
28928
  if (isRealDirectoryChain(process.cwd(), [".claude", "plans"])) {
28778
- sources.push({ root: process.cwd(), prefix: (0, import_node_path34.join)(".claude", "plans") });
28929
+ sources.push({ root: process.cwd(), prefix: (0, import_node_path35.join)(".claude", "plans") });
28779
28930
  }
28780
28931
  if (home && (0, import_node_fs45.existsSync)(homePlansDir)) sources.push({ root: homePlansDir, prefix: "" });
28781
28932
  const candidates2 = [];
@@ -28783,14 +28934,14 @@ function discoverPlans() {
28783
28934
  for (const source2 of sources) {
28784
28935
  let names2;
28785
28936
  try {
28786
- names2 = (0, import_node_fs45.readdirSync)(source2.prefix ? (0, import_node_path34.join)(source2.root, source2.prefix) : source2.root);
28937
+ names2 = (0, import_node_fs45.readdirSync)(source2.prefix ? (0, import_node_path35.join)(source2.root, source2.prefix) : source2.root);
28787
28938
  } catch {
28788
28939
  continue;
28789
28940
  }
28790
28941
  for (const f of names2) {
28791
28942
  if (!f.endsWith(".md") || seen.has(f)) continue;
28792
28943
  seen.add(f);
28793
- const relPath = source2.prefix ? (0, import_node_path34.join)(source2.prefix, f) : f;
28944
+ const relPath = source2.prefix ? (0, import_node_path35.join)(source2.prefix, f) : f;
28794
28945
  const stat3 = statRegularInRoot(source2.root, relPath);
28795
28946
  if (!stat3.ok) continue;
28796
28947
  candidates2.push({ name: f, root: source2.root, relPath, mtime: stat3.mtimeMs, size: stat3.size });
@@ -28808,7 +28959,7 @@ function discoverPlans() {
28808
28959
  function isRealDirectoryChain(root, parts2) {
28809
28960
  let current = root;
28810
28961
  for (const part of parts2) {
28811
- current = (0, import_node_path34.join)(current, part);
28962
+ current = (0, import_node_path35.join)(current, part);
28812
28963
  try {
28813
28964
  if (!(0, import_node_fs45.lstatSync)(current).isDirectory()) return false;
28814
28965
  } catch {
@@ -29012,7 +29163,7 @@ async function mode(run4) {
29012
29163
 
29013
29164
  // src/lib/fold.ts
29014
29165
  var import_node_fs46 = require("node:fs");
29015
- var import_node_path35 = require("node:path");
29166
+ var import_node_path36 = require("node:path");
29016
29167
  var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
29017
29168
  "user",
29018
29169
  "assistant",
@@ -29244,9 +29395,9 @@ function fold(transcriptPath, opts = {}) {
29244
29395
  return result;
29245
29396
  }
29246
29397
  try {
29247
- const sidecarDir = (0, import_node_path35.join)(
29248
- (0, import_node_path35.dirname)(transcriptPath),
29249
- (0, import_node_path35.basename)(transcriptPath).replace(/\.jsonl$/, ""),
29398
+ const sidecarDir = (0, import_node_path36.join)(
29399
+ (0, import_node_path36.dirname)(transcriptPath),
29400
+ (0, import_node_path36.basename)(transcriptPath).replace(/\.jsonl$/, ""),
29250
29401
  "subagents"
29251
29402
  );
29252
29403
  if ((0, import_node_fs46.existsSync)(sidecarDir)) {
@@ -29256,7 +29407,7 @@ function fold(transcriptPath, opts = {}) {
29256
29407
  const walk2 = (d, depth) => {
29257
29408
  if (depth > 4) return;
29258
29409
  for (const e of (0, import_node_fs46.readdirSync)(d, { withFileTypes: true })) {
29259
- const p = (0, import_node_path35.join)(d, e.name);
29410
+ const p = (0, import_node_path36.join)(d, e.name);
29260
29411
  if (e.isDirectory()) {
29261
29412
  walk2(p, depth + 1);
29262
29413
  } else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
@@ -29629,7 +29780,7 @@ async function evidence(run4) {
29629
29780
 
29630
29781
  // src/lib/cache-cleanup.ts
29631
29782
  var import_node_fs47 = require("node:fs");
29632
- var import_node_path36 = require("node:path");
29783
+ var import_node_path37 = require("node:path");
29633
29784
  var CACHE_TTL_DAYS = 7;
29634
29785
  function pruneStaleCache() {
29635
29786
  try {
@@ -29637,7 +29788,7 @@ function pruneStaleCache() {
29637
29788
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
29638
29789
  for (const entry of (0, import_node_fs47.readdirSync)(dir)) {
29639
29790
  if (!entry.startsWith("pending-")) continue;
29640
- const path = (0, import_node_path36.join)(dir, entry);
29791
+ const path = (0, import_node_path37.join)(dir, entry);
29641
29792
  try {
29642
29793
  const stat3 = (0, import_node_fs47.statSync)(path);
29643
29794
  if (stat3.mtimeMs < cutoff) {
@@ -29872,7 +30023,7 @@ function languageOf2(path) {
29872
30023
 
29873
30024
  // src/lib/code/runtime.ts
29874
30025
  var import_node_fs49 = require("node:fs");
29875
- var import_node_path37 = require("node:path");
30026
+ var import_node_path38 = require("node:path");
29876
30027
  var import_web_tree_sitter2 = __toESM2(require_web_tree_sitter2());
29877
30028
 
29878
30029
  // src/lib/code/grammars.json
@@ -29932,12 +30083,12 @@ var grammars_default = {
29932
30083
  var MAX_PARSE_BYTES = 512 * 1024;
29933
30084
  function grammarCandidates(file, pkg, env = process.env) {
29934
30085
  return [
29935
- ...env.VERITY_GRAMMAR_DIR ? [(0, import_node_path37.join)(env.VERITY_GRAMMAR_DIR, file)] : [],
29936
- (0, import_node_path37.join)(__dirname, "..", "data", "grammars", file),
30086
+ ...env.VERITY_GRAMMAR_DIR ? [(0, import_node_path38.join)(env.VERITY_GRAMMAR_DIR, file)] : [],
30087
+ (0, import_node_path38.join)(__dirname, "..", "data", "grammars", file),
29937
30088
  // bundled: cli/bin/ → cli/data/grammars/
29938
- (0, import_node_path37.join)(__dirname, "..", "..", "..", "data", "grammars", file),
30089
+ (0, import_node_path38.join)(__dirname, "..", "..", "..", "data", "grammars", file),
29939
30090
  // tsx: cli/src/lib/code/ → cli/data/grammars/
29940
- (0, import_node_path37.join)(__dirname, "..", "..", "..", "node_modules", pkg, file)
30091
+ (0, import_node_path38.join)(__dirname, "..", "..", "..", "node_modules", pkg, file)
29941
30092
  // tsx, before a build: the pinned devDependency
29942
30093
  ];
29943
30094
  }
@@ -30673,14 +30824,14 @@ function accessMap(input) {
30673
30824
  const targetWrites = (r) => effects.some((e) => e.op === "write" && sameResource(r, e.resource));
30674
30825
  const excluded = /* @__PURE__ */ new Set([target, ...helpers, ...fileFns.filter((f) => inside(f, target))]);
30675
30826
  const accessors = [];
30676
- const consider = (fns, p, sameFile, fileState) => {
30827
+ const consider = (fns, p, sameFile2, fileState) => {
30677
30828
  for (const f of fns) {
30678
- if (sameFile && excluded.has(f)) continue;
30679
- if (sameFile && inside(target, f)) continue;
30680
- const theirs = effectsWithHelpers(f, fns, lang, 2, fileState).filter((e) => !(sameFile && e.via.includes(target.name)) && searchable.some((r) => sameResource(r, e.resource) && (e.op === "write" || targetWrites(r))));
30829
+ if (sameFile2 && excluded.has(f)) continue;
30830
+ if (sameFile2 && inside(target, f)) continue;
30831
+ const theirs = effectsWithHelpers(f, fns, lang, 2, fileState).filter((e) => !(sameFile2 && e.via.includes(target.name)) && searchable.some((r) => sameResource(r, e.resource) && (e.op === "write" || targetWrites(r))));
30681
30832
  if (theirs.length === 0) continue;
30682
- if (sameFile && [...helperNames].includes(f.name)) continue;
30683
- accessors.push({ fn: f, effects: theirs, writes: theirs.some((e) => e.op === "write"), sameFile });
30833
+ if (sameFile2 && [...helperNames].includes(f.name)) continue;
30834
+ accessors.push({ fn: f, effects: theirs, writes: theirs.some((e) => e.op === "write"), sameFile: sameFile2 });
30684
30835
  }
30685
30836
  };
30686
30837
  consider(fileFns, path, true, state);
@@ -31070,7 +31221,7 @@ var MAX_CALLERS = 30;
31070
31221
  var MAX_READ_BYTES4 = 512 * 1024;
31071
31222
  var IDENT = /^[A-Za-z_$][\w$]*$/;
31072
31223
  var TEST_OR_TOOLING = /(^|\/)(tests?|__tests__|spec|specs|fixtures?|e2e|testdata|experiments|scripts)\/|\.(test|spec)\.[cm]?[jt]sx?$|_test\.(py|go)$|(^|\/)test_[^/]+\.py$/;
31073
- var dirname18 = (p) => p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "";
31224
+ var dirname19 = (p) => p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "";
31074
31225
  function joinPath(dir, rel) {
31075
31226
  const out2 = dir ? dir.split("/") : [];
31076
31227
  for (const part of rel.split("/")) {
@@ -31195,7 +31346,7 @@ function parseJsonc(text2) {
31195
31346
  return JSON.parse(out2.replace(/,(\s*[}\]])/g, "$1"));
31196
31347
  }
31197
31348
  function tsconfigFor(source2, fromPath, cache) {
31198
- for (let dir = dirname18(fromPath); ; dir = dirname18(dir)) {
31349
+ for (let dir = dirname19(fromPath); ; dir = dirname19(dir)) {
31199
31350
  const file = dir ? `${dir}/tsconfig.json` : "tsconfig.json";
31200
31351
  if (!cache.has(file)) {
31201
31352
  const read2 = source2.read(file, 256 * 1024);
@@ -31219,7 +31370,7 @@ function tsconfigFor(source2, fromPath, cache) {
31219
31370
  function tsCandidates(spec, fromPath, cfg) {
31220
31371
  const bases = [];
31221
31372
  if (spec.startsWith(".")) {
31222
- const b = joinPath(dirname18(fromPath), spec);
31373
+ const b = joinPath(dirname19(fromPath), spec);
31223
31374
  if (b !== null) bases.push(b);
31224
31375
  } else if (cfg) {
31225
31376
  const root = cfg.baseUrl !== null ? joinPath(cfg.dir, cfg.baseUrl) : cfg.dir;
@@ -31251,8 +31402,8 @@ function pyCandidates(spec, fromPath) {
31251
31402
  const mod = spec.slice(dots).replace(/\./g, "/");
31252
31403
  const out2 = [];
31253
31404
  if (dots > 0) {
31254
- let dir = dirname18(fromPath);
31255
- for (let i2 = 1; i2 < dots; i2++) dir = dirname18(dir);
31405
+ let dir = dirname19(fromPath);
31406
+ for (let i2 = 1; i2 < dots; i2++) dir = dirname19(dir);
31256
31407
  const b = mod ? joinPath(dir, mod) : dir;
31257
31408
  if (b !== null) out2.push(`${b}.py`, `${b}/__init__.py`);
31258
31409
  } else {
@@ -31279,7 +31430,7 @@ function moduleFiles(source2, spec, fromPath, lang, cache = /* @__PURE__ */ new
31279
31430
  const module2 = mod.ok ? /^\s*module\s+(\S+)/m.exec(mod.content)?.[1] : void 0;
31280
31431
  if (!module2 || !(spec === module2 || spec.startsWith(`${module2}/`))) return [];
31281
31432
  const dir = spec.slice(module2.length + 1);
31282
- return source2.list(dir || void 0).filter((p) => dirname18(p) === dir && p.endsWith(".go") && !p.endsWith("_test.go"));
31433
+ return source2.list(dir || void 0).filter((p) => dirname19(p) === dir && p.endsWith(".go") && !p.endsWith("_test.go"));
31283
31434
  }
31284
31435
  if (lang === "java") {
31285
31436
  const suffix = `${spec.replace(/\./g, "/")}.java`;
@@ -31330,7 +31481,7 @@ function definitionOf(input) {
31330
31481
  if (type?.startsWith(RESULT_OF)) {
31331
31482
  const [fnName, idx] = type.slice(RESULT_OF.length).split("#");
31332
31483
  type = null;
31333
- const others = lang === "go" ? source2.list(dirname18(path) || void 0).filter((p) => dirname18(p) === dirname18(path) && p.endsWith(".go") && p !== path) : (() => {
31484
+ const others = lang === "go" ? source2.list(dirname19(path) || void 0).filter((p) => dirname19(p) === dirname19(path) && p.endsWith(".go") && p !== path) : (() => {
31334
31485
  const imp = importOf(content, lang, fnName, null);
31335
31486
  return imp ? moduleFiles(source2, imp.spec, path, lang, tsCache) : [];
31336
31487
  })();
@@ -31377,7 +31528,7 @@ function definitionOf(input) {
31377
31528
  }
31378
31529
  }
31379
31530
  if (out2.length === 0 && lang === "go" && !receiver) {
31380
- for (const f of source2.list(dirname18(path) || void 0).filter((p) => dirname18(p) === dirname18(path) && p.endsWith(".go") && p !== path)) {
31531
+ for (const f of source2.list(dirname19(path) || void 0).filter((p) => dirname19(p) === dirname19(path) && p.endsWith(".go") && p !== path)) {
31381
31532
  add(functionsNamed(outlineOf2(f), name2), "package");
31382
31533
  if (Date.now() > input.deadlineAt) break;
31383
31534
  }
@@ -32273,8 +32424,8 @@ async function repoContext(run4) {
32273
32424
  // src/lib/seed-runner.ts
32274
32425
  var import_promises13 = require("node:fs/promises");
32275
32426
  var import_node_fs50 = require("node:fs");
32276
- var import_node_path38 = require("node:path");
32277
- var import_yaml6 = __toESM2(require_dist());
32427
+ var import_node_path39 = require("node:path");
32428
+ var import_yaml7 = __toESM2(require_dist());
32278
32429
 
32279
32430
  // src/lib/seed.ts
32280
32431
  function extractReadmeIntro(content) {
@@ -32496,7 +32647,7 @@ function normalizeTitle(title) {
32496
32647
  async function localSeedTitles(memoryDir3 = MEMORY_DIR) {
32497
32648
  const titles = /* @__PURE__ */ new Set();
32498
32649
  for (const dir of NODE_DIRS) {
32499
- const full = (0, import_node_path38.join)(memoryDir3, dir);
32650
+ const full = (0, import_node_path39.join)(memoryDir3, dir);
32500
32651
  if (!(0, import_node_fs50.existsSync)(full)) continue;
32501
32652
  let files;
32502
32653
  try {
@@ -32508,7 +32659,7 @@ async function localSeedTitles(memoryDir3 = MEMORY_DIR) {
32508
32659
  if (!file.endsWith(".md")) continue;
32509
32660
  let content;
32510
32661
  try {
32511
- content = await (0, import_promises13.readFile)((0, import_node_path38.join)(full, file), "utf-8");
32662
+ content = await (0, import_promises13.readFile)((0, import_node_path39.join)(full, file), "utf-8");
32512
32663
  } catch {
32513
32664
  continue;
32514
32665
  }
@@ -32585,7 +32736,7 @@ async function runSeed(opts) {
32585
32736
  let standardDoc;
32586
32737
  try {
32587
32738
  const raw = await (0, import_promises13.readFile)(STANDARD_FILE, "utf-8");
32588
- standardDoc = (0, import_yaml6.parse)(raw);
32739
+ standardDoc = (0, import_yaml7.parse)(raw);
32589
32740
  } catch {
32590
32741
  return { created: 0, existing: 0, failed: 0, skipped: "no_standard", candidates: [] };
32591
32742
  }
@@ -32685,7 +32836,7 @@ async function runSeed(opts) {
32685
32836
  }
32686
32837
  const content = typeof res.data.content === "string" && res.data.content.length > 0 ? res.data.content : renderNodeMarkdown(c, nodeId, createdAt);
32687
32838
  try {
32688
- await (0, import_promises13.mkdir)((0, import_node_path38.dirname)(targetPath), { recursive: true });
32839
+ await (0, import_promises13.mkdir)((0, import_node_path39.dirname)(targetPath), { recursive: true });
32689
32840
  await (0, import_promises13.writeFile)(targetPath, content);
32690
32841
  if (deduplicated) existing++;
32691
32842
  else {
@@ -32702,7 +32853,7 @@ async function runSeed(opts) {
32702
32853
 
32703
32854
  // src/commands/analyze/phases/08-memory-manifest.ts
32704
32855
  var import_node_fs51 = require("node:fs");
32705
- var import_node_path39 = require("node:path");
32856
+ var import_node_path40 = require("node:path");
32706
32857
  async function memoryManifest(run4) {
32707
32858
  const { globals } = run4;
32708
32859
  const { serviceUrl, token } = run4;
@@ -32712,7 +32863,7 @@ async function memoryManifest(run4) {
32712
32863
  let autoSeedNotice = null;
32713
32864
  try {
32714
32865
  await ensureMemoryDir();
32715
- const seedMarker = (0, import_node_path39.join)(VERITY_DIR, ".seeded");
32866
+ const seedMarker = (0, import_node_path40.join)(VERITY_DIR, ".seeded");
32716
32867
  const hasStandard = (0, import_node_fs51.existsSync)(STANDARD_FILE);
32717
32868
  const alreadyTried = (0, import_node_fs51.existsSync)(seedMarker);
32718
32869
  if (hasStandard && !alreadyTried) {
@@ -32833,7 +32984,7 @@ function computeIncrement(reviewedPaths, hashOf, priorAuthored) {
32833
32984
  }
32834
32985
 
32835
32986
  // src/commands/analyze/phases/10-working-memory.ts
32836
- var import_node_path40 = require("node:path");
32987
+ var import_node_path41 = require("node:path");
32837
32988
  async function workingMemory(run4) {
32838
32989
  const { opts } = run4;
32839
32990
  const { allForReview, baseline, conversation, foldResult, sessionId, token, transcriptPath } = run4;
@@ -32845,7 +32996,7 @@ async function workingMemory(run4) {
32845
32996
  const priorState = foldForMarks(memorySession.d);
32846
32997
  incrementReport = computeIncrement(
32847
32998
  allForReview,
32848
- (p) => fileHash((0, import_node_path40.join)(repoRoot(), p)),
32999
+ (p) => fileHash((0, import_node_path41.join)(repoRoot(), p)),
32849
33000
  priorState.authored_all.map((a) => ({
32850
33001
  path: a.path,
32851
33002
  hash_at_last_verdict: a.hash_at_last_verdict
@@ -33474,7 +33625,7 @@ async function transmit(run4) {
33474
33625
 
33475
33626
  // src/commands/analyze/phases/13-reconcile.ts
33476
33627
  var import_node_fs54 = require("node:fs");
33477
- var import_node_path41 = require("node:path");
33628
+ var import_node_path42 = require("node:path");
33478
33629
  async function reconcile(run4) {
33479
33630
  const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory: memory2, memorySession, response, reviewable, securityFiles, turnId } = run4;
33480
33631
  const sentPaths = codeDelta.files.map((f) => f.path);
@@ -33503,7 +33654,7 @@ async function reconcile(run4) {
33503
33654
  const st = foldDossier(memorySession.d);
33504
33655
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line2) => {
33505
33656
  try {
33506
- const src = (0, import_node_fs54.readFileSync)((0, import_node_path41.join)(repoRoot(), file), "utf8").split("\n");
33657
+ const src = (0, import_node_fs54.readFileSync)((0, import_node_path42.join)(repoRoot(), file), "utf8").split("\n");
33507
33658
  const at = src[line2 - 1];
33508
33659
  return at === void 0 ? null : lineSha(at);
33509
33660
  } catch {
@@ -34067,11 +34218,11 @@ async function render(run4) {
34067
34218
  const sev = String(f.severity ?? "").toLowerCase();
34068
34219
  return sev === "critical" || sev === "high";
34069
34220
  });
34070
- const fingerprint = findingsFingerprint(blocking);
34221
+ const fingerprint2 = findingsFingerprint(blocking);
34071
34222
  const prior = readBlockState(currentCommit, {
34072
34223
  newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
34073
34224
  });
34074
- const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
34225
+ const sameProblem = isSameProblem(prior.fingerprint, fingerprint2);
34075
34226
  const maxIterations = parseInt(opts.maxIterations, 10);
34076
34227
  const attempts = sameProblem ? prior.attempts + 1 : 1;
34077
34228
  const blocks = prior.blocks + 1;
@@ -34084,7 +34235,7 @@ async function render(run4) {
34084
34235
  maxIterations
34085
34236
  });
34086
34237
  if (decisionNow.block) {
34087
- writeBlockState(currentCommit, { attempts, blocks, fingerprint });
34238
+ writeBlockState(currentCommit, { attempts, blocks, fingerprint: fingerprint2 });
34088
34239
  iteration = attempts;
34089
34240
  } else {
34090
34241
  release = decisionNow.release;
@@ -34095,7 +34246,7 @@ async function render(run4) {
34095
34246
  blocks,
34096
34247
  reviewed_files: codeDelta.files.length,
34097
34248
  cycle_cut: silenced,
34098
- fingerprint
34249
+ fingerprint: fingerprint2
34099
34250
  });
34100
34251
  }
34101
34252
  }
@@ -34325,7 +34476,7 @@ function registerAnalyzeCommand(program2) {
34325
34476
  var tracing = () => process.env.VERITY_TRACE_PHASES === "1";
34326
34477
  async function runAnalyze(opts, globals) {
34327
34478
  if (!verityConfigured()) {
34328
- (0, import_node_fs55.writeSync)(2, isMachineStore((0, import_node_path42.join)(repoRoot(), VERITY_DIR)) ? "[verity] nothing to review in your home directory \u2014 run Verity inside a project.\n" : '[verity] not set up in this project \u2014 run "verity init" first.\n');
34479
+ (0, import_node_fs55.writeSync)(2, isMachineStore((0, import_node_path43.join)(repoRoot(), VERITY_DIR)) ? "[verity] nothing to review in your home directory \u2014 run Verity inside a project.\n" : '[verity] not set up in this project \u2014 run "verity init" first.\n');
34329
34480
  process.exit(0);
34330
34481
  }
34331
34482
  const run4 = createRun(opts, globals);
@@ -34349,23 +34500,23 @@ async function runAnalyze(opts, globals) {
34349
34500
  var import_node_fs60 = require("node:fs");
34350
34501
  var import_node_child_process17 = require("node:child_process");
34351
34502
  var import_node_crypto18 = require("node:crypto");
34352
- var import_node_path47 = require("node:path");
34503
+ var import_node_path48 = require("node:path");
34353
34504
 
34354
34505
  // src/lib/prove/runner.ts
34355
34506
  var import_node_child_process15 = require("node:child_process");
34356
34507
  var import_node_os9 = require("node:os");
34357
34508
  var import_node_fs58 = require("node:fs");
34358
34509
  var import_node_os10 = require("node:os");
34359
- var import_node_path45 = require("node:path");
34510
+ var import_node_path46 = require("node:path");
34360
34511
 
34361
34512
  // src/lib/prove/scaffold.ts
34362
34513
  var import_node_fs57 = require("node:fs");
34363
- var import_node_path44 = require("node:path");
34514
+ var import_node_path45 = require("node:path");
34364
34515
 
34365
34516
  // src/lib/prove/safe-write.ts
34366
34517
  var import_node_fs56 = require("node:fs");
34367
34518
  var import_node_crypto15 = require("node:crypto");
34368
- var import_node_path43 = require("node:path");
34519
+ var import_node_path44 = require("node:path");
34369
34520
  var SafeWriteError = class extends Error {
34370
34521
  constructor(reason, path) {
34371
34522
  super(`refusing to write ${path}: ${reason}`);
@@ -34377,7 +34528,7 @@ var SafeWriteError = class extends Error {
34377
34528
  var FLAGS3 = import_node_fs56.constants;
34378
34529
  var NOFOLLOW2 = FLAGS3.O_NOFOLLOW ?? 0;
34379
34530
  function assertPlainRelative(rel) {
34380
- if (!rel || (0, import_node_path43.isAbsolute)(rel)) throw new SafeWriteError("bad-path", rel);
34531
+ if (!rel || (0, import_node_path44.isAbsolute)(rel)) throw new SafeWriteError("bad-path", rel);
34381
34532
  for (const part of rel.split(/[\\/]/)) {
34382
34533
  if (part === ".." || part === "" || part === ".") throw new SafeWriteError("bad-path", rel);
34383
34534
  }
@@ -34386,7 +34537,7 @@ function ensureDirsNoFollow(root, rel) {
34386
34537
  const parts2 = rel.split(/[\\/]/);
34387
34538
  let cur = root;
34388
34539
  for (const part of parts2.slice(0, -1)) {
34389
- cur = (0, import_node_path43.join)(cur, part);
34540
+ cur = (0, import_node_path44.join)(cur, part);
34390
34541
  let st;
34391
34542
  try {
34392
34543
  st = (0, import_node_fs56.lstatSync)(cur);
@@ -34397,10 +34548,10 @@ function ensureDirsNoFollow(root, rel) {
34397
34548
  if (st.isSymbolicLink()) throw new SafeWriteError("symlink", cur);
34398
34549
  if (!st.isDirectory()) throw new SafeWriteError("not-regular", cur);
34399
34550
  }
34400
- return (0, import_node_path43.join)(root, rel);
34551
+ return (0, import_node_path44.join)(root, rel);
34401
34552
  }
34402
34553
  function insideRoot(realRoot, realPath) {
34403
- return realPath === realRoot || realPath.startsWith(realRoot.endsWith(import_node_path43.sep) ? realRoot : realRoot + import_node_path43.sep);
34554
+ return realPath === realRoot || realPath.startsWith(realRoot.endsWith(import_node_path44.sep) ? realRoot : realRoot + import_node_path44.sep);
34404
34555
  }
34405
34556
  function removeQuietly(path) {
34406
34557
  try {
@@ -34411,7 +34562,7 @@ function removeQuietly(path) {
34411
34562
  function writeFileInRootNoFollow(root, rel, content) {
34412
34563
  assertPlainRelative(rel);
34413
34564
  const dest = ensureDirsNoFollow(root, rel);
34414
- const dir = (0, import_node_path43.dirname)(dest);
34565
+ const dir = (0, import_node_path44.dirname)(dest);
34415
34566
  try {
34416
34567
  const st = (0, import_node_fs56.lstatSync)(dest);
34417
34568
  if (st.isSymbolicLink()) throw new SafeWriteError("symlink", dest);
@@ -34425,7 +34576,7 @@ function writeFileInRootNoFollow(root, rel, content) {
34425
34576
  } catch {
34426
34577
  throw new SafeWriteError("unwritable", root);
34427
34578
  }
34428
- const tmp = (0, import_node_path43.join)(dir, `.${(0, import_node_path43.basename)(dest)}.${(0, import_node_crypto15.randomBytes)(6).toString("hex")}.tmp`);
34579
+ const tmp = (0, import_node_path44.join)(dir, `.${(0, import_node_path44.basename)(dest)}.${(0, import_node_crypto15.randomBytes)(6).toString("hex")}.tmp`);
34429
34580
  let fd;
34430
34581
  try {
34431
34582
  fd = (0, import_node_fs56.openSync)(tmp, import_node_fs56.constants.O_WRONLY | import_node_fs56.constants.O_CREAT | import_node_fs56.constants.O_EXCL | NOFOLLOW2, 420);
@@ -34469,7 +34620,7 @@ function pathHasNoLink(root, rel) {
34469
34620
  }
34470
34621
  let cur = root;
34471
34622
  for (const part of rel.split(/[\\/]/)) {
34472
- cur = cur + import_node_path43.sep + part;
34623
+ cur = cur + import_node_path44.sep + part;
34473
34624
  try {
34474
34625
  if ((0, import_node_fs56.lstatSync)(cur).isSymbolicLink()) return false;
34475
34626
  } catch {
@@ -34781,20 +34932,20 @@ This directory is machine-local and gitignored (see ../.gitignore). Models are
34781
34932
  never committed; what reaches the team is the bug report and the fix.
34782
34933
  `;
34783
34934
  function scaffoldProofs(root) {
34784
- const proofsDir = (0, import_node_path44.join)(root, PROOFS_DIR);
34935
+ const proofsDir = (0, import_node_path45.join)(root, PROOFS_DIR);
34785
34936
  const written = [];
34786
34937
  const kept = [];
34787
34938
  for (const stray of ["lakefile.lean"]) {
34788
34939
  const rel = `${PROOFS_DIR}/${stray}`;
34789
- if (!pathHasNoLink(root, rel)) throw new SafeWriteError("symlink", (0, import_node_path44.join)(root, rel));
34940
+ if (!pathHasNoLink(root, rel)) throw new SafeWriteError("symlink", (0, import_node_path45.join)(root, rel));
34790
34941
  try {
34791
- (0, import_node_fs57.rmSync)((0, import_node_path44.join)(root, rel), { force: true });
34942
+ (0, import_node_fs57.rmSync)((0, import_node_path45.join)(root, rel), { force: true });
34792
34943
  } catch {
34793
34944
  }
34794
34945
  }
34795
34946
  for (const f of PROOFS_TEMPLATE_FILES) {
34796
34947
  const rel = `${PROOFS_DIR}/${f.path}`;
34797
- if (!pathHasNoLink(root, rel)) throw new SafeWriteError("symlink", (0, import_node_path44.join)(root, rel));
34948
+ if (!pathHasNoLink(root, rel)) throw new SafeWriteError("symlink", (0, import_node_path45.join)(root, rel));
34798
34949
  const current = readRegularFileInRoot(root, rel, 4 * 1024 * 1024);
34799
34950
  if (current.ok && current.content === f.content) {
34800
34951
  kept.push(rel);
@@ -34807,8 +34958,8 @@ function scaffoldProofs(root) {
34807
34958
  [`${PROOFS_DIR}/.gitignore`, LOCAL_GITIGNORE],
34808
34959
  [`${MODELS_DIR}/README.md`, MODELS_README]
34809
34960
  ]) {
34810
- if (!pathHasNoLink(root, rel)) throw new SafeWriteError("symlink", (0, import_node_path44.join)(root, rel));
34811
- if (existsNoFollow((0, import_node_path44.join)(root, rel))) {
34961
+ if (!pathHasNoLink(root, rel)) throw new SafeWriteError("symlink", (0, import_node_path45.join)(root, rel));
34962
+ if (existsNoFollow((0, import_node_path45.join)(root, rel))) {
34812
34963
  kept.push(rel);
34813
34964
  continue;
34814
34965
  }
@@ -35077,17 +35228,17 @@ function renderReport(r) {
35077
35228
 
35078
35229
  // src/lib/prove/runner.ts
35079
35230
  function verityLeanHome(env = process.env) {
35080
- return env.VERITY_LEAN_HOME ?? (0, import_node_path45.join)(env.HOME ?? (0, import_node_os10.homedir)(), ".verity", "lean");
35231
+ return env.VERITY_LEAN_HOME ?? (0, import_node_path46.join)(env.HOME ?? (0, import_node_os10.homedir)(), ".verity", "lean");
35081
35232
  }
35082
35233
  function findToolchain(env = process.env) {
35083
- const managedBin = (0, import_node_path45.join)(verityLeanHome(env), LEAN_TOOLCHAIN.replace(/^leanprover\/lean4:/, ""), "bin");
35084
- const elanBin = (0, import_node_path45.join)(env.HOME ?? (0, import_node_os10.homedir)(), ".elan", "bin");
35085
- const elanHas = (0, import_node_fs58.existsSync)((0, import_node_path45.join)(env.HOME ?? (0, import_node_os10.homedir)(), ".elan", "toolchains", LEAN_TOOLCHAIN.replace("/", "--").replace(":", "---")));
35086
- const userPath = (env.PATH ?? "").split(import_node_path45.delimiter).filter((d) => d && (elanHas || (0, import_node_path45.resolve)(d) !== (0, import_node_path45.resolve)(elanBin)));
35087
- const path = [managedBin, ...elanHas ? [elanBin] : [], ...userPath].join(import_node_path45.delimiter);
35234
+ const managedBin = (0, import_node_path46.join)(verityLeanHome(env), LEAN_TOOLCHAIN.replace(/^leanprover\/lean4:/, ""), "bin");
35235
+ const elanBin = (0, import_node_path46.join)(env.HOME ?? (0, import_node_os10.homedir)(), ".elan", "bin");
35236
+ const elanHas = (0, import_node_fs58.existsSync)((0, import_node_path46.join)(env.HOME ?? (0, import_node_os10.homedir)(), ".elan", "toolchains", LEAN_TOOLCHAIN.replace("/", "--").replace(":", "---")));
35237
+ const userPath = (env.PATH ?? "").split(import_node_path46.delimiter).filter((d) => d && (elanHas || (0, import_node_path46.resolve)(d) !== (0, import_node_path46.resolve)(elanBin)));
35238
+ const path = [managedBin, ...elanHas ? [elanBin] : [], ...userPath].join(import_node_path46.delimiter);
35088
35239
  const merged = { ...env, PATH: path, ELAN_TOOLCHAIN: env.ELAN_TOOLCHAIN ?? LEAN_TOOLCHAIN };
35089
35240
  const exe = process.platform === "win32" ? "lake.exe" : "lake";
35090
- const candidates2 = [(0, import_node_path45.join)(managedBin, exe), ...elanHas ? [(0, import_node_path45.join)(elanBin, exe)] : [], exe];
35241
+ const candidates2 = [(0, import_node_path46.join)(managedBin, exe), ...elanHas ? [(0, import_node_path46.join)(elanBin, exe)] : [], exe];
35091
35242
  for (const candidate of candidates2) {
35092
35243
  try {
35093
35244
  (0, import_node_child_process15.execFileSync)(candidate, ["--version"], { env: merged, stdio: ["ignore", "pipe", "pipe"], timeout: 1e4 });
@@ -35166,7 +35317,7 @@ function proofsWorkspaceProblem(root) {
35166
35317
  for (const rel of [".verity", PROOFS_DIR, `${PROOFS_DIR}/.lake`]) {
35167
35318
  let st;
35168
35319
  try {
35169
- st = (0, import_node_fs58.lstatSync)((0, import_node_path45.join)(root, rel));
35320
+ st = (0, import_node_fs58.lstatSync)((0, import_node_path46.join)(root, rel));
35170
35321
  } catch {
35171
35322
  break;
35172
35323
  }
@@ -35186,44 +35337,44 @@ function refused(problem) {
35186
35337
  function buildLibrary(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
35187
35338
  const problem = proofsWorkspaceProblem(root);
35188
35339
  if (problem) return refused(problem);
35189
- return run2(tc, (0, import_node_path45.join)(root, PROOFS_DIR), ["build"], timeoutMs);
35340
+ return run2(tc, (0, import_node_path46.join)(root, PROOFS_DIR), ["build"], timeoutMs);
35190
35341
  }
35191
35342
  async function buildLibraryAsync(tc, root, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS) {
35192
35343
  const problem = proofsWorkspaceProblem(root);
35193
35344
  if (problem) return refused(problem);
35194
- return runAsync(tc, (0, import_node_path45.join)(root, PROOFS_DIR), ["build"], timeoutMs);
35345
+ return runAsync(tc, (0, import_node_path46.join)(root, PROOFS_DIR), ["build"], timeoutMs);
35195
35346
  }
35196
35347
  function isModelInRoot(modelsAbs, rel) {
35197
- if (!rel.endsWith(".lean") || (0, import_node_path45.isAbsolute)(rel) || rel.startsWith("..")) return false;
35348
+ if (!rel.endsWith(".lean") || (0, import_node_path46.isAbsolute)(rel) || rel.startsWith("..")) return false;
35198
35349
  return statRegularInRoot(modelsAbs, rel).ok;
35199
35350
  }
35200
35351
  function resolveModelFile(root, arg) {
35201
- const modelsAbs = (0, import_node_path45.join)(root, MODELS_DIR);
35202
- const asPath = (0, import_node_path45.relative)(modelsAbs, (0, import_node_path45.isAbsolute)(arg) ? arg : (0, import_node_path45.resolve)(root, arg));
35352
+ const modelsAbs = (0, import_node_path46.join)(root, MODELS_DIR);
35353
+ const asPath = (0, import_node_path46.relative)(modelsAbs, (0, import_node_path46.isAbsolute)(arg) ? arg : (0, import_node_path46.resolve)(root, arg));
35203
35354
  const candidates2 = arg.includes("/") || arg.includes("\\") ? [asPath] : [arg, `${arg}.lean`];
35204
35355
  for (const rel of candidates2) {
35205
- if (isModelInRoot(modelsAbs, rel)) return (0, import_node_path45.join)(modelsAbs, rel);
35356
+ if (isModelInRoot(modelsAbs, rel)) return (0, import_node_path46.join)(modelsAbs, rel);
35206
35357
  }
35207
35358
  return null;
35208
35359
  }
35209
35360
  function listModelFiles(root) {
35210
- const modelsAbs = (0, import_node_path45.join)(root, MODELS_DIR);
35361
+ const modelsAbs = (0, import_node_path46.join)(root, MODELS_DIR);
35211
35362
  if (!(0, import_node_fs58.existsSync)(modelsAbs)) return [];
35212
- return (0, import_node_fs58.readdirSync)(modelsAbs).filter((f) => isModelInRoot(modelsAbs, f)).sort().map((f) => (0, import_node_path45.join)(modelsAbs, f));
35363
+ return (0, import_node_fs58.readdirSync)(modelsAbs).filter((f) => isModelInRoot(modelsAbs, f)).sort().map((f) => (0, import_node_path46.join)(modelsAbs, f));
35213
35364
  }
35214
35365
  function trimDiagnostics(text2) {
35215
35366
  return text2.split("\n").filter((l) => !l.startsWith("trace:")).join("\n").trim().slice(0, 4e3);
35216
35367
  }
35217
35368
  function checkModel(tc, root, modelFile, opts = {}) {
35218
- const proofsAbs = (0, import_node_path45.join)(root, PROOFS_DIR);
35219
- const rel = (0, import_node_path45.relative)(proofsAbs, modelFile);
35369
+ const proofsAbs = (0, import_node_path46.join)(root, PROOFS_DIR);
35370
+ const rel = (0, import_node_path46.relative)(proofsAbs, modelFile);
35220
35371
  const problem = proofsWorkspaceProblem(root);
35221
35372
  const res = problem ? refused(problem) : run2(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
35222
35373
  return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
35223
35374
  }
35224
35375
  async function checkModelAsync(tc, root, modelFile, opts = {}) {
35225
- const proofsAbs = (0, import_node_path45.join)(root, PROOFS_DIR);
35226
- const rel = (0, import_node_path45.relative)(proofsAbs, modelFile);
35376
+ const proofsAbs = (0, import_node_path46.join)(root, PROOFS_DIR);
35377
+ const rel = (0, import_node_path46.relative)(proofsAbs, modelFile);
35227
35378
  const problem = proofsWorkspaceProblem(root);
35228
35379
  const res = problem ? refused(problem) : await runAsync(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
35229
35380
  return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
@@ -35232,10 +35383,10 @@ function toReport(res, root, modelFile, timeoutMs) {
35232
35383
  const parsed = parseVerityLines(res.stdout);
35233
35384
  const combined = `${res.stdout}
35234
35385
  ${res.stderr}`;
35235
- const name2 = parsed.header?.model ?? (0, import_node_path45.basename)(modelFile, ".lean");
35386
+ const name2 = parsed.header?.model ?? (0, import_node_path46.basename)(modelFile, ".lean");
35236
35387
  const report = {
35237
35388
  model: name2,
35238
- file: (0, import_node_path45.relative)(root, modelFile),
35389
+ file: (0, import_node_path46.relative)(root, modelFile),
35239
35390
  ok: res.ok,
35240
35391
  header: parsed.header,
35241
35392
  properties: parsed.properties,
@@ -35281,7 +35432,7 @@ var import_node_fs59 = require("node:fs");
35281
35432
  var import_node_crypto17 = require("node:crypto");
35282
35433
  var import_node_child_process16 = require("node:child_process");
35283
35434
  var import_node_os11 = require("node:os");
35284
- var import_node_path46 = require("node:path");
35435
+ var import_node_path47 = require("node:path");
35285
35436
  var import_promises15 = require("node:stream/promises");
35286
35437
  var import_node_stream = require("node:stream");
35287
35438
  var RELEASES = "https://releases.lean-lang.org/lean4";
@@ -35295,7 +35446,7 @@ function freeBytes(dir) {
35295
35446
  const st = (0, import_node_fs59.statfsSync)(d);
35296
35447
  return Number(st.bavail) * Number(st.bsize);
35297
35448
  } catch {
35298
- const up = (0, import_node_path46.join)(d, "..");
35449
+ const up = (0, import_node_path47.join)(d, "..");
35299
35450
  if (up === d) return null;
35300
35451
  d = up;
35301
35452
  }
@@ -35337,13 +35488,13 @@ function leanAssetName(version = pinnedVersion()) {
35337
35488
  return suffix ? `lean-${v}-${suffix}.zip` : null;
35338
35489
  }
35339
35490
  function pinnedVersionDir(env = process.env) {
35340
- return (0, import_node_path46.join)(verityLeanHome(env), pinnedVersion());
35491
+ return (0, import_node_path47.join)(verityLeanHome(env), pinnedVersion());
35341
35492
  }
35342
35493
  function lakeBinary() {
35343
35494
  return (0, import_node_os11.platform)() === "win32" ? "lake.exe" : "lake";
35344
35495
  }
35345
35496
  function managedInstalled(env = process.env) {
35346
- return (0, import_node_fs59.existsSync)((0, import_node_path46.join)(pinnedVersionDir(env), "bin", lakeBinary()));
35497
+ return (0, import_node_fs59.existsSync)((0, import_node_path47.join)(pinnedVersionDir(env), "bin", lakeBinary()));
35347
35498
  }
35348
35499
  function unzip(zip, dest) {
35349
35500
  const attempts = (0, import_node_os11.platform)() === "linux" ? [["unzip", ["-q", zip, "-d", dest]], ["python3", ["-m", "zipfile", "-e", zip, dest]], ["tar", ["-xf", zip, "-C", dest]]] : [["tar", ["-xf", zip, "-C", dest]], ["unzip", ["-q", zip, "-d", dest]], ["python3", ["-m", "zipfile", "-e", zip, dest]]];
@@ -35356,7 +35507,7 @@ function unzip(zip, dest) {
35356
35507
  } catch (err2) {
35357
35508
  errors.push(`${cmd}: ${String(err2.message).split("\n")[0].slice(0, 120)}`);
35358
35509
  try {
35359
- for (const e of (0, import_node_fs59.readdirSync)(dest)) (0, import_node_fs59.rmSync)((0, import_node_path46.join)(dest, e), { recursive: true, force: true });
35510
+ for (const e of (0, import_node_fs59.readdirSync)(dest)) (0, import_node_fs59.rmSync)((0, import_node_path47.join)(dest, e), { recursive: true, force: true });
35360
35511
  } catch {
35361
35512
  }
35362
35513
  }
@@ -35432,10 +35583,10 @@ async function installLean(opts = {}) {
35432
35583
  const gb = (n2) => (n2 / 1024 ** 3).toFixed(1);
35433
35584
  return { ok: false, error: `not enough disk space for Lean: ${gb(free)} GB free where ${home} lives, needs about ${gb(needed)} GB` };
35434
35585
  }
35435
- const tmp = (0, import_node_path46.join)(home, ".tmp");
35586
+ const tmp = (0, import_node_path47.join)(home, ".tmp");
35436
35587
  (0, import_node_fs59.rmSync)(tmp, { recursive: true, force: true });
35437
35588
  (0, import_node_fs59.mkdirSync)(tmp, { recursive: true });
35438
- const zip = (0, import_node_path46.join)(tmp, asset);
35589
+ const zip = (0, import_node_path47.join)(tmp, asset);
35439
35590
  say(`downloading ${url}`);
35440
35591
  let lastPct = -1;
35441
35592
  try {
@@ -35458,7 +35609,7 @@ async function installLean(opts = {}) {
35458
35609
  return { ok: false, error: verified.error };
35459
35610
  }
35460
35611
  say("unpacking (this takes a minute)");
35461
- const unpack = (0, import_node_path46.join)(tmp, "unpack");
35612
+ const unpack = (0, import_node_path47.join)(tmp, "unpack");
35462
35613
  (0, import_node_fs59.mkdirSync)(unpack);
35463
35614
  const unpacked = unzip(zip, unpack);
35464
35615
  if (!unpacked.ok) {
@@ -35466,8 +35617,8 @@ async function installLean(opts = {}) {
35466
35617
  return { ok: false, error: `unpack failed: ${unpacked.error}` };
35467
35618
  }
35468
35619
  const entries = (0, import_node_fs59.readdirSync)(unpack);
35469
- const top = entries.length === 1 && (0, import_node_fs59.statSync)((0, import_node_path46.join)(unpack, entries[0])).isDirectory() ? (0, import_node_path46.join)(unpack, entries[0]) : unpack;
35470
- if (!(0, import_node_fs59.existsSync)((0, import_node_path46.join)(top, "bin", lakeBinary()))) {
35620
+ const top = entries.length === 1 && (0, import_node_fs59.statSync)((0, import_node_path47.join)(unpack, entries[0])).isDirectory() ? (0, import_node_path47.join)(unpack, entries[0]) : unpack;
35621
+ if (!(0, import_node_fs59.existsSync)((0, import_node_path47.join)(top, "bin", lakeBinary()))) {
35471
35622
  (0, import_node_fs59.rmSync)(tmp, { recursive: true, force: true });
35472
35623
  return { ok: false, error: "the archive did not contain bin/lake" };
35473
35624
  }
@@ -35551,7 +35702,7 @@ function startBackgroundInstall(opts) {
35551
35702
  const home = verityLeanHome(env);
35552
35703
  try {
35553
35704
  (0, import_node_fs60.mkdirSync)(home, { recursive: true });
35554
- const log = (0, import_node_fs60.openSync)((0, import_node_path47.join)(home, LOG_FILE), import_node_fs60.constants.O_WRONLY | import_node_fs60.constants.O_APPEND | import_node_fs60.constants.O_CREAT | (import_node_fs60.constants.O_NOFOLLOW ?? 0), 384);
35705
+ const log = (0, import_node_fs60.openSync)((0, import_node_path48.join)(home, LOG_FILE), import_node_fs60.constants.O_WRONLY | import_node_fs60.constants.O_APPEND | import_node_fs60.constants.O_CREAT | (import_node_fs60.constants.O_NOFOLLOW ?? 0), 384);
35555
35706
  try {
35556
35707
  const child = (opts.run ?? import_node_child_process17.spawn)(process.execPath, [opts.cli, "lean", "install", "--yes", "--background"], {
35557
35708
  detached: true,
@@ -35583,7 +35734,7 @@ function lockToken(env) {
35583
35734
  function acquireInstallLock(env = process.env, now = Date.now()) {
35584
35735
  const home = verityLeanHome(env);
35585
35736
  (0, import_node_fs60.mkdirSync)(home, { recursive: true });
35586
- const lock = (0, import_node_path47.join)(home, LOCK_FILE);
35737
+ const lock = (0, import_node_path48.join)(home, LOCK_FILE);
35587
35738
  if ((0, import_node_fs60.existsSync)(lock) && !installRunning(env, now)) (0, import_node_fs60.rmSync)(lock, { force: true });
35588
35739
  const token = (0, import_node_crypto18.randomBytes)(16).toString("hex");
35589
35740
  try {
@@ -35599,7 +35750,7 @@ function acquireInstallLock(env = process.env, now = Date.now()) {
35599
35750
  function refreshInstallLock(env = process.env, now = Date.now()) {
35600
35751
  if (!heldToken || lockToken(env) !== heldToken) return;
35601
35752
  try {
35602
- const fd = (0, import_node_fs60.openSync)((0, import_node_path47.join)(verityLeanHome(env), LOCK_FILE), import_node_fs60.constants.O_WRONLY | import_node_fs60.constants.O_TRUNC | (import_node_fs60.constants.O_NOFOLLOW ?? 0));
35753
+ const fd = (0, import_node_fs60.openSync)((0, import_node_path48.join)(verityLeanHome(env), LOCK_FILE), import_node_fs60.constants.O_WRONLY | import_node_fs60.constants.O_TRUNC | (import_node_fs60.constants.O_NOFOLLOW ?? 0));
35603
35754
  try {
35604
35755
  (0, import_node_fs60.writeFileSync)(fd, JSON.stringify({ pid: process.pid, at: now, token: heldToken }));
35605
35756
  } finally {
@@ -35609,7 +35760,7 @@ function refreshInstallLock(env = process.env, now = Date.now()) {
35609
35760
  }
35610
35761
  }
35611
35762
  function releaseInstallLock(env = process.env) {
35612
- if (heldToken && lockToken(env) === heldToken) (0, import_node_fs60.rmSync)((0, import_node_path47.join)(verityLeanHome(env), LOCK_FILE), { force: true });
35763
+ if (heldToken && lockToken(env) === heldToken) (0, import_node_fs60.rmSync)((0, import_node_path48.join)(verityLeanHome(env), LOCK_FILE), { force: true });
35613
35764
  heldToken = null;
35614
35765
  }
35615
35766
  function writeInstallState(state, env = process.env) {
@@ -35662,14 +35813,14 @@ function resumeInterruptedInstall(opts) {
35662
35813
 
35663
35814
  // src/commands/baseline.ts
35664
35815
  var import_node_fs63 = require("node:fs");
35665
- var import_node_path50 = require("node:path");
35816
+ var import_node_path51 = require("node:path");
35666
35817
 
35667
35818
  // src/lib/skew-notice.ts
35668
35819
  var import_node_fs61 = require("node:fs");
35669
- var import_node_path48 = require("node:path");
35820
+ var import_node_path49 = require("node:path");
35670
35821
  var MARKER = ".skew-notice.json";
35671
35822
  function markerPath2() {
35672
- return (0, import_node_path48.join)(verityHome(), MARKER);
35823
+ return (0, import_node_path49.join)(verityHome(), MARKER);
35673
35824
  }
35674
35825
  function today(now) {
35675
35826
  const y = now.getFullYear();
@@ -35695,7 +35846,7 @@ function recordSkewNotice(skew, now = /* @__PURE__ */ new Date()) {
35695
35846
 
35696
35847
  // src/lib/project-skills.ts
35697
35848
  var import_node_fs62 = require("node:fs");
35698
- var import_node_path49 = require("node:path");
35849
+ var import_node_path50 = require("node:path");
35699
35850
  var PROJECT_SKILL_NAMES = [
35700
35851
  "verity-setup",
35701
35852
  "verity-analyze",
@@ -35722,13 +35873,13 @@ var ALL = [...PROJECT_SKILL_NAMES, ...LEGACY_SKILL_NAMES];
35722
35873
  function staleProjectSkills() {
35723
35874
  const root = projectPath(".claude/skills");
35724
35875
  if (!(0, import_node_fs62.existsSync)(root)) return [];
35725
- return ALL.filter((name2) => (0, import_node_fs62.existsSync)((0, import_node_path49.join)(root, name2)));
35876
+ return ALL.filter((name2) => (0, import_node_fs62.existsSync)((0, import_node_path50.join)(root, name2)));
35726
35877
  }
35727
35878
  function removeProjectSkills() {
35728
35879
  const root = projectPath(".claude/skills");
35729
35880
  const removed = [];
35730
35881
  for (const name2 of staleProjectSkills()) {
35731
- (0, import_node_fs62.rmSync)((0, import_node_path49.join)(root, name2), { recursive: true, force: true });
35882
+ (0, import_node_fs62.rmSync)((0, import_node_path50.join)(root, name2), { recursive: true, force: true });
35732
35883
  removed.push(name2);
35733
35884
  }
35734
35885
  return removed;
@@ -35763,7 +35914,7 @@ function registerBaselineCommands(program2) {
35763
35914
  const loginWarning = "Until they have, do not send them to run `verity login` in a terminal: that runs the global one, which can sign this machine in to a different server.";
35764
35915
  const skewNote = !skew ? null : skewLoud ? `Before anything else, including /verity:init's questions, tell the user their global \`verity\` is ${skew.global}, the hooks run ${skew.hooks}, and \`${skew.remedy}\` fixes it. ${loginWarning}` : `The user's global \`verity\` is ${skew.global} and the hooks run ${skew.hooks}; \`${skew.remedy}\` fixes it. They were already told today, so do not repeat it unprompted \u2014 mention it only where it matters, such as before suggesting a \`verity\` command for their terminal. ${loginWarning}`;
35765
35916
  if (!verityConfigured()) {
35766
- if (isMachineStore((0, import_node_path50.join)(repoRoot(), VERITY_DIR))) process.exit(0);
35917
+ if (isMachineStore((0, import_node_path51.join)(repoRoot(), VERITY_DIR))) process.exit(0);
35767
35918
  const how = isPluginInvocation() ? "Tell the user to run /verity:init \u2014 they type it; you cannot invoke it." : "Tell the user to run `verity init` in their terminal.";
35768
35919
  const lines = [
35769
35920
  `Verity is installed but this project is not set up yet, so the quality gate will not review anything here. ${how}`
@@ -35907,30 +36058,30 @@ function hookSource(value) {
35907
36058
 
35908
36059
  // src/commands/review.ts
35909
36060
  var import_node_fs64 = require("node:fs");
35910
- var import_node_path51 = require("node:path");
36061
+ var import_node_path52 = require("node:path");
35911
36062
  function containReviewPaths(paths, base) {
35912
36063
  const kept = [];
35913
36064
  const refused2 = [];
35914
36065
  for (const raw of paths) {
35915
- const candidate = (0, import_node_path51.isAbsolute)(raw) ? relativeForm(base, raw) : raw;
36066
+ const candidate = (0, import_node_path52.isAbsolute)(raw) ? relativeForm(base, raw) : raw;
35916
36067
  if (candidate === "" || resolveInside(base, candidate) === null) refused2.push(raw);
35917
36068
  else kept.push(candidate);
35918
36069
  }
35919
36070
  return { kept, refused: refused2 };
35920
36071
  }
35921
36072
  function relativeForm(base, abs) {
35922
- const lexical = (0, import_node_path51.relative)(base, abs);
35923
- if (!(lexical === ".." || lexical.startsWith(`..${import_node_path51.sep}`) || (0, import_node_path51.isAbsolute)(lexical))) return lexical;
36073
+ const lexical = (0, import_node_path52.relative)(base, abs);
36074
+ if (!(lexical === ".." || lexical.startsWith(`..${import_node_path52.sep}`) || (0, import_node_path52.isAbsolute)(lexical))) return lexical;
35924
36075
  try {
35925
- let dir = (0, import_node_path51.dirname)(abs);
35926
- let rest = (0, import_node_path51.basename)(abs);
36076
+ let dir = (0, import_node_path52.dirname)(abs);
36077
+ let rest = (0, import_node_path52.basename)(abs);
35927
36078
  while (!(0, import_node_fs64.existsSync)(dir)) {
35928
- const up = (0, import_node_path51.dirname)(dir);
36079
+ const up = (0, import_node_path52.dirname)(dir);
35929
36080
  if (up === dir) return lexical;
35930
- rest = (0, import_node_path51.join)((0, import_node_path51.basename)(dir), rest);
36081
+ rest = (0, import_node_path52.join)((0, import_node_path52.basename)(dir), rest);
35931
36082
  dir = up;
35932
36083
  }
35933
- return (0, import_node_path51.relative)((0, import_node_fs64.realpathSync)(base), (0, import_node_path51.join)((0, import_node_fs64.realpathSync)(dir), rest));
36084
+ return (0, import_node_path52.relative)((0, import_node_fs64.realpathSync)(base), (0, import_node_path52.join)((0, import_node_fs64.realpathSync)(dir), rest));
35934
36085
  } catch {
35935
36086
  return lexical;
35936
36087
  }
@@ -36036,11 +36187,11 @@ async function runReview(opts, globals) {
36036
36187
 
36037
36188
  // src/commands/guard.ts
36038
36189
  var import_node_fs67 = require("node:fs");
36039
- var import_node_path55 = require("node:path");
36190
+ var import_node_path56 = require("node:path");
36040
36191
 
36041
36192
  // src/lib/vendored.ts
36042
36193
  var import_node_fs65 = require("node:fs");
36043
- var import_node_path52 = require("node:path");
36194
+ var import_node_path53 = require("node:path");
36044
36195
  var INSTALLED_SKILL = /^\.claude\/skills\/(verity-[a-z0-9-]+)\/(.+)$/;
36045
36196
  function dataDirOrNull() {
36046
36197
  try {
@@ -36055,7 +36206,7 @@ function shippedSkillFile(path, dataDir = dataDirOrNull()) {
36055
36206
  const [, skill, rel] = m;
36056
36207
  if (rel.split("/").some((seg) => seg === ".." || seg === ".")) return null;
36057
36208
  try {
36058
- return (0, import_node_fs65.readFileSync)((0, import_node_path52.join)(dataDir, "skills", skill, rel), "utf8");
36209
+ return (0, import_node_fs65.readFileSync)((0, import_node_path53.join)(dataDir, "skills", skill, rel), "utf8");
36059
36210
  } catch {
36060
36211
  return null;
36061
36212
  }
@@ -36166,8 +36317,8 @@ async function runAgentReview(options) {
36166
36317
 
36167
36318
  // src/lib/effort.ts
36168
36319
  var import_node_fs66 = require("node:fs");
36169
- var import_node_path53 = require("node:path");
36170
- var import_yaml7 = __toESM2(require_dist());
36320
+ var import_node_path54 = require("node:path");
36321
+ var import_yaml8 = __toESM2(require_dist());
36171
36322
  var EFFORT_PROFILES = {
36172
36323
  default: { pushDeadlineMs: 18e4, formal: { tier: "fast", maxRounds: 2, minScore: 0.7, costCapUsd: 1, maxTargets: 1, relaxedGates: false, dailyModels: 5, dailyCostUsd: 5 } },
36173
36324
  high: { pushDeadlineMs: 22e4, formal: { tier: "deep", maxRounds: 2, minScore: 0.6, costCapUsd: 2, maxTargets: 2, relaxedGates: false, dailyModels: 15, dailyCostUsd: 15 } },
@@ -36179,7 +36330,7 @@ function asEffort(value) {
36179
36330
  function resolveEffort(root, env = process.env) {
36180
36331
  if (env.VERITY_EFFORT) return asEffort(env.VERITY_EFFORT);
36181
36332
  try {
36182
- const standard = (0, import_yaml7.parse)((0, import_node_fs66.readFileSync)((0, import_node_path53.join)(root, STANDARD_FILE), "utf8"));
36333
+ const standard = (0, import_yaml8.parse)((0, import_node_fs66.readFileSync)((0, import_node_path54.join)(root, STANDARD_FILE), "utf8"));
36183
36334
  return asEffort(standard?.process_constraints?.git_moment_effort);
36184
36335
  } catch {
36185
36336
  return "default";
@@ -36190,7 +36341,7 @@ function effortProfile(root, env = process.env) {
36190
36341
  }
36191
36342
 
36192
36343
  // src/lib/prove/auto.ts
36193
- var import_node_path54 = require("node:path");
36344
+ var import_node_path55 = require("node:path");
36194
36345
  var import_node_crypto21 = require("node:crypto");
36195
36346
 
36196
36347
  // src/lib/prove/grounding.ts
@@ -37885,8 +38036,8 @@ function readVerified(root, now = Date.now()) {
37885
38036
  return [];
37886
38037
  }
37887
38038
  }
37888
- function findVerified(root, file, symbol, fingerprint, now = Date.now()) {
37889
- return readVerified(root, now).find((r) => r.file === file && r.symbol === symbol && r.fingerprint === fingerprint) ?? null;
38039
+ function findVerified(root, file, symbol, fingerprint2, now = Date.now()) {
38040
+ return readVerified(root, now).find((r) => r.file === file && r.symbol === symbol && r.fingerprint === fingerprint2) ?? null;
37890
38041
  }
37891
38042
  function recordVerified(root, rec, now = Date.now()) {
37892
38043
  const others = readVerified(root, now).filter((r) => !(r.file === rec.file && r.symbol === rec.symbol));
@@ -38344,7 +38495,7 @@ async function runFormalPhase(input) {
38344
38495
  modelName = gen.name;
38345
38496
  const rel = `${AUTO_MODELS_DIR}/${gen.name}.lean`;
38346
38497
  writeFileInRootNoFollow(root, rel, gen.source);
38347
- const abs = resolveModelFile(root, rel) ?? (0, import_node_path54.join)(root, rel);
38498
+ const abs = resolveModelFile(root, rel) ?? (0, import_node_path55.join)(root, rel);
38348
38499
  progress.model = { name: gen.name, file: rel, rounds: round };
38349
38500
  progress.step = "check";
38350
38501
  report = await deps.check(root, abs);
@@ -38493,7 +38644,7 @@ async function runFormalPhase(input) {
38493
38644
  return done({
38494
38645
  status: "checked",
38495
38646
  target,
38496
- model: { name: modelName, file: (0, import_node_path54.relative)(root, (0, import_node_path54.join)(root, modelFile)), rounds },
38647
+ model: { name: modelName, file: (0, import_node_path55.relative)(root, (0, import_node_path55.join)(root, modelFile)), rounds },
38497
38648
  properties: report.properties,
38498
38649
  findings
38499
38650
  });
@@ -38570,7 +38721,7 @@ async function generateAndCheck(root, req, opts) {
38570
38721
  }
38571
38722
  const rel = `${dir}/${gen.name}.lean`;
38572
38723
  writeFileInRootNoFollow(root, rel, gen.source);
38573
- const abs = resolveModelFile(root, rel) ?? (0, import_node_path54.join)(root, rel);
38724
+ const abs = resolveModelFile(root, rel) ?? (0, import_node_path55.join)(root, rel);
38574
38725
  const report = await opts.deps.check(root, abs);
38575
38726
  const abstracts = report.header?.abstracts ?? [];
38576
38727
  const stray = abstracts.find((a) => !headerFiles.has(a.file)) ?? (abstracts[0] && abstracts[0].file !== req.file ? abstracts[0] : void 0);
@@ -38960,7 +39111,7 @@ var WRITERS_BUDGET_MS = 1500;
38960
39111
  var EXCERPT_MAX_LINES2 = 30;
38961
39112
  var VENDORED_READ_MAX_BYTES = 256 * 1024;
38962
39113
  var GUARD_BLOCK_CAP = 2;
38963
- var GUARD_ITER_FILE = (0, import_node_path55.join)(VERITY_DIR, ".guard-iteration");
39114
+ var GUARD_ITER_FILE = (0, import_node_path56.join)(VERITY_DIR, ".guard-iteration");
38964
39115
  function readPreToolUseStdin() {
38965
39116
  const empty = { command: "", cwd: null, sessionId: null };
38966
39117
  return new Promise((resolve9) => {
@@ -39130,11 +39281,11 @@ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedInte
39130
39281
  }
39131
39282
  };
39132
39283
  if (coverageTelemetry) requestBody2.coverage_telemetry = coverageTelemetry;
39133
- const docs2 = discoverGuardDocs(files);
39134
- if (docs2.length > 0 || statedIntent) {
39284
+ const docs = discoverGuardDocs(files);
39285
+ if (docs.length > 0 || statedIntent) {
39135
39286
  const intentContext = {};
39136
39287
  if (statedIntent) intentContext.user_prompt = statedIntent;
39137
- if (docs2.length > 0) intentContext.plans = docs2;
39288
+ if (docs.length > 0) intentContext.plans = docs;
39138
39289
  requestBody2.intent_context = intentContext;
39139
39290
  }
39140
39291
  return requestBody2;
@@ -39819,14 +39970,99 @@ function registerWaiveCommand(program2) {
39819
39970
  }
39820
39971
 
39821
39972
  // src/commands/init/index.ts
39822
- var import_node_fs74 = require("node:fs");
39973
+ var import_node_fs77 = require("node:fs");
39823
39974
  var import_promises19 = require("node:fs/promises");
39824
- var import_node_path60 = require("node:path");
39975
+ var import_node_path65 = require("node:path");
39825
39976
 
39826
- // src/lib/init-trace.ts
39977
+ // src/lib/init-changes.ts
39827
39978
  var import_node_crypto23 = require("node:crypto");
39828
39979
  var import_node_fs69 = require("node:fs");
39829
- var import_node_path56 = require("node:path");
39980
+ var import_node_os12 = require("node:os");
39981
+ var import_node_path57 = require("node:path");
39982
+ var REPO_FILES = [
39983
+ ".gitignore",
39984
+ "CLAUDE.md",
39985
+ ".claude/settings.json",
39986
+ ".claude/settings.local.json",
39987
+ STANDARD_FILE,
39988
+ VERITY_MD_FILE,
39989
+ CODACY_CONFIG_FILE,
39990
+ ".codacy/tools-configs/eslint.config.mjs"
39991
+ ];
39992
+ var SKILLS_DIR = ".claude/skills";
39993
+ var MAX_HASH_BYTES = 2 * 1024 * 1024;
39994
+ var MAX_TREE_ENTRIES = 2e3;
39995
+ function userSettingsDir(env) {
39996
+ return env.CLAUDE_CONFIG_DIR || (0, import_node_path57.join)(env.HOME || (0, import_node_os12.homedir)(), ".claude");
39997
+ }
39998
+ function fingerprint(root, rel) {
39999
+ const opened = openRegularInRoot(root, rel);
40000
+ if (!opened.ok) {
40001
+ if (opened.reason === "missing") return null;
40002
+ if (opened.reason === "symlink") {
40003
+ try {
40004
+ return `link:${(0, import_node_fs69.readlinkSync)((0, import_node_path57.join)(root, rel))}`;
40005
+ } catch {
40006
+ return "link";
40007
+ }
40008
+ }
40009
+ return `refused:${opened.reason}`;
40010
+ }
40011
+ if (opened.size > MAX_HASH_BYTES) {
40012
+ closeOpened(opened.fd);
40013
+ return `size:${opened.size}`;
40014
+ }
40015
+ try {
40016
+ return (0, import_node_crypto23.createHash)("sha256").update(readOpened(opened.fd, opened.size)).digest("hex");
40017
+ } catch {
40018
+ return "unreadable";
40019
+ }
40020
+ }
40021
+ function treeFingerprint(root, dirRel) {
40022
+ try {
40023
+ if (!(0, import_node_fs69.lstatSync)((0, import_node_path57.join)(root, dirRel)).isDirectory()) return fingerprint(root, dirRel);
40024
+ } catch {
40025
+ return null;
40026
+ }
40027
+ const parts2 = [];
40028
+ const walk2 = (rel) => {
40029
+ let names2;
40030
+ try {
40031
+ names2 = (0, import_node_fs69.readdirSync)((0, import_node_path57.join)(root, rel)).sort();
40032
+ } catch {
40033
+ return;
40034
+ }
40035
+ for (const name2 of names2) {
40036
+ if (parts2.length >= MAX_TREE_ENTRIES) return;
40037
+ const relPath = `${rel}/${name2}`;
40038
+ let st;
40039
+ try {
40040
+ st = (0, import_node_fs69.lstatSync)((0, import_node_path57.join)(root, relPath));
40041
+ } catch {
40042
+ continue;
40043
+ }
40044
+ if (st.isDirectory()) walk2(relPath);
40045
+ else parts2.push(`${relPath}\0${fingerprint(root, relPath)}`);
40046
+ }
40047
+ };
40048
+ walk2(dirRel);
40049
+ return (0, import_node_crypto23.createHash)("sha256").update(parts2.join("\n")).digest("hex");
40050
+ }
40051
+ function snapshotInitFiles(root = process.cwd(), env = process.env) {
40052
+ const snap = /* @__PURE__ */ new Map();
40053
+ for (const f of REPO_FILES) snap.set(f, fingerprint(root, f));
40054
+ snap.set(`${SKILLS_DIR}/`, treeFingerprint(root, SKILLS_DIR));
40055
+ snap.set("~/.claude/settings.json", fingerprint(userSettingsDir(env), "settings.json"));
40056
+ return snap;
40057
+ }
40058
+ function changedSince(before, after) {
40059
+ return [...after.keys()].filter((k) => before.get(k) !== after.get(k));
40060
+ }
40061
+
40062
+ // src/lib/init-trace.ts
40063
+ var import_node_crypto24 = require("node:crypto");
40064
+ var import_node_fs70 = require("node:fs");
40065
+ var import_node_path58 = require("node:path");
39830
40066
  var INIT_STEPS = [
39831
40067
  "started",
39832
40068
  "prerequisites",
@@ -39872,9 +40108,9 @@ function traceDisabled(env = process.env) {
39872
40108
  }
39873
40109
  var PENDING_NAME = "init-trace.json";
39874
40110
  var MAX_PENDING_BYTES = 64 * 1024;
39875
- var verityHome2 = () => (0, import_node_path56.join)(process.env.HOME ?? "", ".verity");
40111
+ var verityHome2 = () => (0, import_node_path58.join)(process.env.HOME ?? "", ".verity");
39876
40112
  function pendingTracePath() {
39877
- return (0, import_node_path56.join)(verityHome2(), PENDING_NAME);
40113
+ return (0, import_node_path58.join)(verityHome2(), PENDING_NAME);
39878
40114
  }
39879
40115
  var RUN_ID = /^[0-9a-f]{16}$/;
39880
40116
  var CLI_VERSION = /^[0-9A-Za-z.+-]{1,40}$/;
@@ -39932,10 +40168,10 @@ function writePending(runs) {
39932
40168
  try {
39933
40169
  const kept = runs.filter((r) => r.events.length > 0).slice(-MAX_PENDING_RUNS);
39934
40170
  if (kept.length === 0) {
39935
- (0, import_node_fs69.rmSync)(pendingTracePath(), { force: true });
40171
+ (0, import_node_fs70.rmSync)(pendingTracePath(), { force: true });
39936
40172
  return;
39937
40173
  }
39938
- (0, import_node_fs69.mkdirSync)(verityHome2(), { recursive: true });
40174
+ (0, import_node_fs70.mkdirSync)(verityHome2(), { recursive: true });
39939
40175
  if (!replaceInRoot(verityHome2(), PENDING_NAME, JSON.stringify(kept))) {
39940
40176
  logEvent("init_trace_save_refused", {});
39941
40177
  }
@@ -39978,7 +40214,7 @@ function startInitTrace(opts) {
39978
40214
  if (traceDisabled()) return NOOP;
39979
40215
  const startedAt = Date.now();
39980
40216
  const current = {
39981
- run_id: (0, import_node_crypto23.randomBytes)(8).toString("hex"),
40217
+ run_id: (0, import_node_crypto24.randomBytes)(8).toString("hex"),
39982
40218
  mode: opts.mode,
39983
40219
  interactive: opts.interactive,
39984
40220
  cli_version: cliVersion(),
@@ -40628,400 +40864,11 @@ var Rail = class {
40628
40864
  }
40629
40865
  };
40630
40866
 
40631
- // src/commands/migrate.ts
40632
- var import_node_fs70 = require("node:fs");
40633
- var import_node_path57 = require("node:path");
40634
- var import_node_child_process18 = require("node:child_process");
40635
-
40636
- // src/lib/telemetry.ts
40637
- var import_promises16 = require("node:fs/promises");
40638
- var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
40639
- var GITIGNORE_FILE = ".gitignore";
40640
- var GITIGNORE_ENTRY = SETTINGS_LOCAL_IGNORE_ENTRY;
40641
- var OTEL_HEADERS_HELPER_CMD = "verity telemetry headers";
40642
- var LEGACY_TELEMETRY_ENV_KEYS = ["OTEL_EXPORTER_OTLP_HEADERS"];
40643
- function deriveOtlpEndpoint(serviceUrl) {
40644
- return serviceUrl.replace(/\/+$/, "").replace(/\/v1$/, "") + "/v1/otlp";
40645
- }
40646
- function buildTelemetryEnv(serviceUrl) {
40647
- return {
40648
- CLAUDE_CODE_ENABLE_TELEMETRY: "1",
40649
- OTEL_METRICS_EXPORTER: "otlp",
40650
- OTEL_TRACES_EXPORTER: "otlp",
40651
- CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
40652
- OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
40653
- OTEL_EXPORTER_OTLP_ENDPOINT: deriveOtlpEndpoint(serviceUrl),
40654
- OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: "delta",
40655
- OTEL_LOG_USER_PROMPTS: "0",
40656
- OTEL_LOG_TOOL_DETAILS: "0",
40657
- OTEL_LOG_TOOL_CONTENT: "0",
40658
- OTEL_METRICS_INCLUDE_SESSION_ID: "true",
40659
- OTEL_METRICS_INCLUDE_ACCOUNT_UUID: "false",
40660
- OTEL_METRICS_INCLUDE_VERSION: "false",
40661
- OTEL_METRIC_EXPORT_INTERVAL: "60000"
40662
- };
40663
- }
40664
- var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv(""));
40665
- async function readSettingsLocal() {
40666
- try {
40667
- return JSON.parse(await (0, import_promises16.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
40668
- } catch {
40669
- return {};
40670
- }
40671
- }
40672
- async function writeSettingsLocal(settings) {
40673
- await writeJsonFilePreservingStyle(projectPath(SETTINGS_LOCAL_FILE2), settings);
40674
- }
40675
- async function ensureGitignore() {
40676
- const file = projectPath(GITIGNORE_FILE);
40677
- let content = "";
40678
- try {
40679
- content = await (0, import_promises16.readFile)(file, "utf-8");
40680
- } catch {
40681
- }
40682
- const lines = content.split("\n").map((l) => l.trim());
40683
- if (lines.includes(GITIGNORE_ENTRY) || lines.includes(".claude/") || lines.includes(".claude")) {
40684
- return;
40685
- }
40686
- const block = "# Verity telemetry \u2014 machine-local Claude Code settings\n" + GITIGNORE_ENTRY + "\n";
40687
- const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
40688
- await (0, import_promises16.writeFile)(file, next);
40689
- }
40690
- async function installTelemetry(serviceUrl) {
40691
- const env = buildTelemetryEnv(serviceUrl);
40692
- const settings = await readSettingsLocal();
40693
- settings.env = { ...settings.env ?? {}, ...env };
40694
- for (const key of LEGACY_TELEMETRY_ENV_KEYS) delete settings.env[key];
40695
- settings.otelHeadersHelper = OTEL_HEADERS_HELPER_CMD;
40696
- await writeSettingsLocal(settings);
40697
- await ensureGitignore();
40698
- return { ok: true, data: { endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT } };
40699
- }
40700
- async function checkTelemetry() {
40701
- const env = (await readSettingsLocal()).env ?? {};
40702
- const enabled = env.CLAUDE_CODE_ENABLE_TELEMETRY === "1" && !!env.OTEL_EXPORTER_OTLP_ENDPOINT;
40703
- return { enabled, endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? null, settingsPath: SETTINGS_LOCAL_FILE2 };
40704
- }
40705
- async function uninstallTelemetry() {
40706
- const settings = await readSettingsLocal();
40707
- const hadHelper = settings.otelHeadersHelper === OTEL_HEADERS_HELPER_CMD;
40708
- if (hadHelper) delete settings.otelHeadersHelper;
40709
- if (!settings.env) {
40710
- if (hadHelper) await writeSettingsLocal(settings);
40711
- return { ok: true, data: { removed: 0 } };
40712
- }
40713
- let removed = 0;
40714
- for (const key of VERITY_TELEMETRY_KEYS) {
40715
- if (key in settings.env) {
40716
- delete settings.env[key];
40717
- removed++;
40718
- }
40719
- }
40720
- for (const key of LEGACY_TELEMETRY_ENV_KEYS) delete settings.env[key];
40721
- if (Object.keys(settings.env).length === 0) delete settings.env;
40722
- await writeSettingsLocal(settings);
40723
- return { ok: true, data: { removed } };
40724
- }
40725
-
40726
- // src/commands/migrate.ts
40727
- var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
40728
- function defaultNpmRemover(pkg) {
40729
- (0, import_node_child_process18.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
40730
- }
40731
- function isGitTracked(cwd, relPath) {
40732
- try {
40733
- (0, import_node_child_process18.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
40734
- return true;
40735
- } catch {
40736
- return false;
40737
- }
40738
- }
40739
- function isGitRepo(cwd) {
40740
- try {
40741
- (0, import_node_child_process18.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
40742
- return true;
40743
- } catch {
40744
- return false;
40745
- }
40746
- }
40747
- async function runMigration(opts = {}) {
40748
- const root = opts.cwd ?? repoRoot();
40749
- const home = opts.home ?? process.env.HOME ?? "";
40750
- const npmRemover = opts.npmRemover ?? defaultNpmRemover;
40751
- const actions = [];
40752
- const movedProjectDir = migrateProjectDir(root, actions);
40753
- migrateGlobalCredentials(home, actions);
40754
- await migrateLegacyHooks(root, actions);
40755
- await migrateClaudeMd(root, actions);
40756
- migrateStandardFile(root, actions);
40757
- await migrateTelemetryHeaders(root, actions);
40758
- removeLegacyPackage(movedProjectDir, npmRemover, actions);
40759
- return { actions, migrated: actions.length > 0 };
40760
- }
40761
- function migrateProjectDir(root, actions) {
40762
- const gateDir = (0, import_node_path57.join)(root, ".gate");
40763
- const verityDir = (0, import_node_path57.join)(root, ".verity");
40764
- if ((0, import_node_fs70.existsSync)(gateDir) && !(0, import_node_fs70.existsSync)(verityDir)) {
40765
- return migrateProjectDirRename(root, gateDir, verityDir, actions);
40766
- }
40767
- if ((0, import_node_fs70.existsSync)(gateDir) && (0, import_node_fs70.existsSync)(verityDir)) {
40768
- return migrateProjectDirCarry(gateDir, verityDir, actions);
40769
- }
40770
- return false;
40771
- }
40772
- function migrateProjectDirRename(root, gateDir, verityDir, actions) {
40773
- let moved = false;
40774
- if (isGitRepo(root) && isGitTracked(root, ".gate")) {
40775
- if (hasStagedChanges(root)) {
40776
- printWarn(
40777
- "Migrating .gate \u2192 .verity with `git mv` while the index has staged changes \u2014 the rename will be staged alongside them. Review `git status` before committing."
40778
- );
40779
- }
40780
- try {
40781
- (0, import_node_child_process18.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
40782
- actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
40783
- moved = true;
40784
- } catch {
40785
- }
40786
- }
40787
- if (moved) {
40788
- if ((0, import_node_fs70.existsSync)(gateDir)) {
40789
- const carried = carryLegacyContents(gateDir, verityDir);
40790
- if (carried > 0) {
40791
- actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
40792
- }
40793
- try {
40794
- (0, import_node_fs70.rmSync)(gateDir, { recursive: true, force: true });
40795
- } catch {
40796
- }
40797
- }
40798
- return true;
40799
- }
40800
- moveDir(gateDir, verityDir);
40801
- actions.push("Moved .gate/ \u2192 .verity/");
40802
- return true;
40803
- }
40804
- function migrateProjectDirCarry(gateDir, verityDir, actions) {
40805
- const carried = carryLegacyContents(gateDir, verityDir);
40806
- if (carried > 0) {
40807
- actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
40808
- }
40809
- try {
40810
- (0, import_node_fs70.rmSync)(gateDir, { recursive: true, force: true });
40811
- } catch {
40812
- }
40813
- return carried > 0;
40814
- }
40815
- function migrateGlobalCredentials(home, actions) {
40816
- if (!home) return;
40817
- const gateCreds = (0, import_node_path57.join)(home, ".gate", "credentials");
40818
- const verityCreds = (0, import_node_path57.join)(home, ".verity", "credentials");
40819
- if (!(0, import_node_fs70.existsSync)(gateCreds)) return;
40820
- if (!(0, import_node_fs70.existsSync)(verityCreds)) {
40821
- (0, import_node_fs70.mkdirSync)((0, import_node_path57.join)(home, ".verity"), { recursive: true });
40822
- moveFile(gateCreds, verityCreds);
40823
- actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
40824
- return;
40825
- }
40826
- const added = mergeGlobalCredentials(gateCreds, verityCreds);
40827
- if (added > 0) {
40828
- actions.push(
40829
- `Merged ${added} legacy credential line(s) from ~/.gate/credentials into ~/.verity/credentials`
40830
- );
40831
- }
40832
- }
40833
- async function migrateLegacyHooks(root, actions) {
40834
- const settings = await readSettingsAt(root);
40835
- const settingsBefore = JSON.stringify(settings);
40836
- const updatedSettings = removeVerityHooks(settings);
40837
- if (settingsBefore !== JSON.stringify(updatedSettings)) {
40838
- await writeSettingsAt(root, updatedSettings);
40839
- actions.push("Stripped legacy gate hooks from .claude/settings.json");
40840
- }
40841
- }
40842
- async function migrateClaudeMd(root, actions) {
40843
- const claudeMd = (0, import_node_path57.join)(root, "CLAUDE.md");
40844
- const hadLegacyBlock = (0, import_node_fs70.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
40845
- if (!hadLegacyBlock) return;
40846
- try {
40847
- await ensureClaudeMdPointer(root);
40848
- actions.push("Rewrote legacy gate-memory block in CLAUDE.md");
40849
- } catch (err2) {
40850
- printWarn(`Could not rewrite CLAUDE.md block: ${err2.message}`);
40851
- }
40852
- }
40853
- function migrateStandardFile(root, actions) {
40854
- const gateMd = (0, import_node_path57.join)(root, "GATE.md");
40855
- const verityMd = (0, import_node_path57.join)(root, "VERITY.md");
40856
- if (!(0, import_node_fs70.existsSync)(gateMd) || (0, import_node_fs70.existsSync)(verityMd)) return;
40857
- let moved = false;
40858
- if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
40859
- try {
40860
- (0, import_node_child_process18.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
40861
- moved = true;
40862
- } catch {
40863
- }
40864
- }
40865
- if (!moved) moveFile(gateMd, verityMd);
40866
- const content = readFileSyncSafe(verityMd);
40867
- const refreshed = content.split("GATE.md").join("VERITY.md");
40868
- if (refreshed !== content) (0, import_node_fs70.writeFileSync)(verityMd, refreshed);
40869
- actions.push("Renamed GATE.md \u2192 VERITY.md");
40870
- }
40871
- async function migrateTelemetryHeaders(root, actions) {
40872
- const file = (0, import_node_path57.join)(root, ".claude", "settings.local.json");
40873
- if (!(0, import_node_fs70.existsSync)(file)) return;
40874
- let settings;
40875
- try {
40876
- settings = JSON.parse(readFileSyncSafe(file) || "{}");
40877
- } catch {
40878
- return;
40879
- }
40880
- const env = settings.env;
40881
- const hasInlineHeader = !!env && "OTEL_EXPORTER_OTLP_HEADERS" in env;
40882
- const telemetryEnabled = env?.CLAUDE_CODE_ENABLE_TELEMETRY === "1";
40883
- const needsHelper = telemetryEnabled && settings.otelHeadersHelper !== OTEL_HEADERS_HELPER_CMD;
40884
- if (!hasInlineHeader && !needsHelper) return;
40885
- if (hasInlineHeader && env) delete env["OTEL_EXPORTER_OTLP_HEADERS"];
40886
- if (telemetryEnabled) settings.otelHeadersHelper = OTEL_HEADERS_HELPER_CMD;
40887
- await writeJsonFilePreservingStyle(file, settings);
40888
- actions.push("Moved the telemetry token out of .claude/settings.local.json (now resolved at runtime)");
40889
- }
40890
- function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
40891
- if (!movedProjectDir) return;
40892
- try {
40893
- npmRemover(LEGACY_NPM_PACKAGE);
40894
- actions.push(`Removed legacy global package ${LEGACY_NPM_PACKAGE}`);
40895
- } catch (err2) {
40896
- printWarn(
40897
- `Could not remove ${LEGACY_NPM_PACKAGE} (${err2.message}). Remove it manually: npm rm -g ${LEGACY_NPM_PACKAGE}`
40898
- );
40899
- }
40900
- }
40901
- function credentialRemoteKey(line2) {
40902
- const trimmed = line2.trim();
40903
- if (trimmed === "") return null;
40904
- const idx = trimmed.indexOf(" token:");
40905
- return idx === -1 ? trimmed : trimmed.slice(0, idx).trim();
40906
- }
40907
- function mergeGlobalCredentials(gateCreds, verityCreds) {
40908
- const verityContent = readFileSyncSafe(verityCreds);
40909
- const existingKeys = new Set(
40910
- verityContent.split("\n").map(credentialRemoteKey).filter((k) => k !== null)
40911
- );
40912
- const toAppend = [];
40913
- for (const line2 of readFileSyncSafe(gateCreds).split("\n")) {
40914
- const key = credentialRemoteKey(line2);
40915
- if (key === null || existingKeys.has(key)) continue;
40916
- existingKeys.add(key);
40917
- toAppend.push(line2.replace(/\r$/, ""));
40918
- }
40919
- if (toAppend.length > 0) {
40920
- const sep7 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
40921
- (0, import_node_fs70.writeFileSync)(verityCreds, verityContent + sep7 + toAppend.join("\n") + "\n");
40922
- }
40923
- (0, import_node_fs70.rmSync)(gateCreds, { force: true });
40924
- return toAppend.length;
40925
- }
40926
- function readFileSyncSafe(path) {
40927
- try {
40928
- return (0, import_node_fs70.readFileSync)(path, "utf-8");
40929
- } catch {
40930
- return "";
40931
- }
40932
- }
40933
- function hasStagedChanges(root) {
40934
- try {
40935
- (0, import_node_child_process18.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
40936
- return false;
40937
- } catch {
40938
- return true;
40939
- }
40940
- }
40941
- function moveDir(from, to) {
40942
- try {
40943
- (0, import_node_fs70.renameSync)(from, to);
40944
- } catch (err2) {
40945
- if (err2.code !== "EXDEV") throw err2;
40946
- (0, import_node_fs70.cpSync)(from, to, { recursive: true });
40947
- (0, import_node_fs70.rmSync)(from, { recursive: true, force: true });
40948
- }
40949
- }
40950
- function moveFile(from, to) {
40951
- try {
40952
- (0, import_node_fs70.renameSync)(from, to);
40953
- } catch (err2) {
40954
- if (err2.code !== "EXDEV") throw err2;
40955
- (0, import_node_fs70.cpSync)(from, to);
40956
- (0, import_node_fs70.rmSync)(from, { force: true });
40957
- }
40958
- }
40959
- function carryLegacyContents(gateDir, verityDir) {
40960
- let copied = 0;
40961
- const walk2 = (relDir) => {
40962
- const srcDir = (0, import_node_path57.join)(gateDir, relDir);
40963
- for (const entry of (0, import_node_fs70.readdirSync)(srcDir)) {
40964
- const rel = relDir ? (0, import_node_path57.join)(relDir, entry) : entry;
40965
- const src = (0, import_node_path57.join)(gateDir, rel);
40966
- const dest = (0, import_node_path57.join)(verityDir, rel);
40967
- if ((0, import_node_fs70.statSync)(src).isDirectory()) {
40968
- walk2(rel);
40969
- } else if (!(0, import_node_fs70.existsSync)(dest)) {
40970
- (0, import_node_fs70.mkdirSync)((0, import_node_path57.dirname)(dest), { recursive: true });
40971
- (0, import_node_fs70.cpSync)(src, dest);
40972
- copied++;
40973
- }
40974
- }
40975
- };
40976
- walk2("");
40977
- return copied;
40978
- }
40979
- async function needsMigration(root = repoRoot()) {
40980
- const gateDir = (0, import_node_path57.join)(root, ".gate");
40981
- const verityDir = (0, import_node_path57.join)(root, ".verity");
40982
- if ((0, import_node_fs70.existsSync)(gateDir) && !(0, import_node_fs70.existsSync)(verityDir)) return true;
40983
- if ((0, import_node_fs70.existsSync)(gateDir) && (0, import_node_fs70.existsSync)(verityDir)) {
40984
- if ((0, import_node_fs70.existsSync)((0, import_node_path57.join)(gateDir, "credentials")) && !(0, import_node_fs70.existsSync)((0, import_node_path57.join)(verityDir, "credentials"))) {
40985
- return true;
40986
- }
40987
- if ((0, import_node_fs70.existsSync)((0, import_node_path57.join)(gateDir, "memory")) && !(0, import_node_fs70.existsSync)((0, import_node_path57.join)(verityDir, "memory"))) {
40988
- return true;
40989
- }
40990
- }
40991
- const claudeMd = (0, import_node_path57.join)(root, "CLAUDE.md");
40992
- if ((0, import_node_fs70.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
40993
- return true;
40994
- }
40995
- if ((0, import_node_fs70.existsSync)((0, import_node_path57.join)(root, "GATE.md")) && !(0, import_node_fs70.existsSync)((0, import_node_path57.join)(root, "VERITY.md"))) {
40996
- return true;
40997
- }
40998
- if (await hasLegacyHooksAt(root)) return true;
40999
- return false;
41000
- }
41001
- function registerMigrateCommand(program2) {
41002
- program2.command("migrate").description("Migrate a legacy GATE.md install to Verity (.gate \u2192 .verity, hooks, CLAUDE.md)").action(async () => {
41003
- let result;
41004
- try {
41005
- result = await runMigration();
41006
- } catch (err2) {
41007
- printError(`Migration failed: ${err2.message}`);
41008
- process.exitCode = 1;
41009
- return;
41010
- }
41011
- if (!result.migrated) {
41012
- printInfo("Nothing to migrate \u2014 this project is already on Verity.");
41013
- return;
41014
- }
41015
- printInfo("Migration complete:");
41016
- for (const a of result.actions) printInfo(` ${a} \u2713`);
41017
- });
41018
- }
41019
-
41020
40867
  // src/commands/init/ui.ts
41021
40868
  var import_node_fs71 = require("node:fs");
41022
- var import_node_path58 = require("node:path");
41023
- var import_node_os12 = require("node:os");
41024
- var import_node_child_process19 = require("node:child_process");
40869
+ var import_node_path59 = require("node:path");
40870
+ var import_node_os13 = require("node:os");
40871
+ var import_node_child_process18 = require("node:child_process");
41025
40872
  function createInitUi(opts) {
41026
40873
  const term = opts.term ?? detectTerm();
41027
40874
  const screen = new Screen(term);
@@ -41038,25 +40885,26 @@ function installInterrupt(ui) {
41038
40885
  process.once("SIGINT", stop2);
41039
40886
  }
41040
40887
  var SEEN_FILE = ".init-seen";
41041
- function firstInitOnMachine(home = process.env.HOME || (0, import_node_os12.homedir)()) {
41042
- return !(0, import_node_fs71.existsSync)((0, import_node_path58.join)(home, ".verity", SEEN_FILE));
40888
+ function firstInitOnMachine(home = process.env.HOME || (0, import_node_os13.homedir)()) {
40889
+ return !(0, import_node_fs71.existsSync)((0, import_node_path59.join)(home, ".verity", SEEN_FILE));
41043
40890
  }
41044
- function markInitSeen(home = process.env.HOME || (0, import_node_os12.homedir)()) {
41045
- replaceInRoot((0, import_node_path58.join)(home, ".verity"), SEEN_FILE, `${(/* @__PURE__ */ new Date()).toISOString()}
40891
+ function markInitSeen(home = process.env.HOME || (0, import_node_os13.homedir)()) {
40892
+ replaceInRoot((0, import_node_path59.join)(home, ".verity"), SEEN_FILE, `${(/* @__PURE__ */ new Date()).toISOString()}
41046
40893
  `);
41047
40894
  }
41048
40895
  function repoLabel() {
41049
40896
  try {
41050
- const url = (0, import_node_child_process19.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
40897
+ const url = (0, import_node_child_process18.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
41051
40898
  const parsed = parseRemote(url);
41052
40899
  if (parsed) return terminalText(`${parsed.owner}/${parsed.repo}`, 120);
41053
40900
  } catch {
41054
40901
  }
41055
- return terminalText((0, import_node_path58.basename)(repoRoot()), 120);
40902
+ return terminalText((0, import_node_path59.basename)(repoRoot()), 120);
41056
40903
  }
41057
40904
 
41058
40905
  // src/lib/prereqs.ts
41059
- var import_node_child_process20 = require("node:child_process");
40906
+ var import_node_child_process19 = require("node:child_process");
40907
+ var import_node_fs72 = require("node:fs");
41060
40908
  var MIN_NODE_MAJOR = 20;
41061
40909
  function which(bin) {
41062
40910
  return whichSync(bin, { real: false });
@@ -41077,7 +40925,7 @@ function checkNode() {
41077
40925
  function checkGit() {
41078
40926
  let detail = "";
41079
40927
  try {
41080
- detail = (0, import_node_child_process20.execSync)("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
40928
+ detail = (0, import_node_child_process19.execSync)("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
41081
40929
  } catch {
41082
40930
  return {
41083
40931
  id: "git",
@@ -41101,21 +40949,38 @@ function checkClaude() {
41101
40949
  required: false
41102
40950
  };
41103
40951
  }
41104
- function checkAnalysisCli() {
41105
- const path = which("codacy-analysis");
40952
+ var ANALYSIS_CLI_REMEDY = "Reinstall Verity, which includes it: npm install -g @codacy/verity-cli";
40953
+ function checkAnalysisCli(opts = {}) {
40954
+ const base = { id: "analysis-cli", label: "@codacy/analysis-cli", required: false };
40955
+ const analyzer = resolveAnalyzer(opts);
40956
+ const shown = (opts.onPathDisplay ?? (() => which("codacy-analysis")))();
40957
+ if (!analyzer) {
40958
+ return { ...base, status: "warn", detail: "not found", remedy: `${ANALYSIS_CLI_REMEDY} (static findings are unavailable until then)` };
40959
+ }
40960
+ if (analyzer.source === "path") {
40961
+ return { ...base, status: "ok", detail: `${shown ?? analyzer.path} (on PATH)`, source: "path" };
40962
+ }
40963
+ const unused = shown && !sameFile(shown, analyzer.path) ? shown : void 0;
41106
40964
  return {
41107
- id: "analysis-cli",
41108
- label: "@codacy/analysis-cli",
41109
- status: path ? "ok" : "warn",
41110
- detail: path ?? "not found",
41111
- remedy: path ? void 0 : "npm install -g @codacy/analysis-cli (static findings are unavailable until then)",
41112
- required: false
40965
+ ...base,
40966
+ status: "ok",
40967
+ detail: `${analyzer.version} (bundled with Verity)`,
40968
+ source: "bundled",
40969
+ version: analyzer.version,
40970
+ ...unused && { unused }
41113
40971
  };
41114
40972
  }
40973
+ function sameFile(a, b) {
40974
+ try {
40975
+ return (0, import_node_fs72.realpathSync)(a) === (0, import_node_fs72.realpathSync)(b);
40976
+ } catch {
40977
+ return false;
40978
+ }
40979
+ }
41115
40980
  var INSTALL_TIMEOUT_MS = 12e4;
41116
40981
  function run3(command, args2, opts = {}) {
41117
40982
  return new Promise((resolve9) => {
41118
- const child = (0, import_node_child_process20.spawn)(command, args2, {
40983
+ const child = (0, import_node_child_process19.spawn)(command, args2, {
41119
40984
  stdio: opts.inherit ? "inherit" : "pipe",
41120
40985
  timeout: INSTALL_TIMEOUT_MS
41121
40986
  });
@@ -41145,7 +41010,7 @@ async function installAnalysisCli(opts = {}) {
41145
41010
  label: "@codacy/analysis-cli",
41146
41011
  status: "warn",
41147
41012
  detail: "install failed",
41148
- remedy: "Install manually: npm install -g @codacy/analysis-cli",
41013
+ remedy: ANALYSIS_CLI_REMEDY,
41149
41014
  required: false
41150
41015
  };
41151
41016
  if (opts.sudo === false || !process.stdin.isTTY || !process.stdout.isTTY) return failed;
@@ -41166,6 +41031,305 @@ async function checkPrereqs(opts = {}) {
41166
41031
  return { checks, blocked: checks.some((c) => c.required && c.status !== "ok") };
41167
41032
  }
41168
41033
 
41034
+ // src/lib/telemetry-local.ts
41035
+ var import_node_path61 = require("node:path");
41036
+
41037
+ // src/lib/telemetry-settings.ts
41038
+ var import_promises16 = require("node:fs/promises");
41039
+ var import_node_os14 = require("node:os");
41040
+ var import_node_path60 = require("node:path");
41041
+ var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
41042
+ var OTEL_HEADERS_HELPER_CMD = "verity telemetry headers";
41043
+ var LEGACY_HEADER_KEY = "OTEL_EXPORTER_OTLP_HEADERS";
41044
+ var OTLP_ENDPOINT_KEYS = [
41045
+ "OTEL_EXPORTER_OTLP_ENDPOINT",
41046
+ "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
41047
+ "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
41048
+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"
41049
+ ];
41050
+ function claudeConfigDir2() {
41051
+ return process.env.CLAUDE_CONFIG_DIR || (0, import_node_path60.join)((0, import_node_os14.homedir)(), ".claude");
41052
+ }
41053
+ function userSettingsPath() {
41054
+ return (0, import_node_path60.join)(claudeConfigDir2(), "settings.json");
41055
+ }
41056
+ function deriveOtlpEndpoint(serviceUrl) {
41057
+ return serviceUrl.replace(/\/+$/, "").replace(/\/v1$/, "") + "/v1/otlp";
41058
+ }
41059
+ function isVerityOtlpEndpoint(value, also) {
41060
+ if (typeof value !== "string" || value.trim() === "") return false;
41061
+ if (also && value.replace(/\/+$/, "") === also.replace(/\/+$/, "")) return true;
41062
+ try {
41063
+ return /\/functions\/v1\/otlp\/?$/.test(new URL(value).pathname);
41064
+ } catch {
41065
+ return false;
41066
+ }
41067
+ }
41068
+ function sameUrl(a, b) {
41069
+ const norm3 = (raw) => {
41070
+ const trimmed = raw.trim().replace(/\/+$/, "");
41071
+ try {
41072
+ const u = new URL(trimmed);
41073
+ return `${u.protocol}//${u.host.toLowerCase()}${u.pathname.replace(/\/+$/, "")}`;
41074
+ } catch {
41075
+ return trimmed.toLowerCase();
41076
+ }
41077
+ };
41078
+ return norm3(a) === norm3(b);
41079
+ }
41080
+ function envOf(settings) {
41081
+ const env = settings.env;
41082
+ return env && typeof env === "object" && !Array.isArray(env) ? env : {};
41083
+ }
41084
+ function displayPath(path) {
41085
+ const home = (0, import_node_os14.homedir)();
41086
+ return home && path.startsWith(home + "/") ? "~" + path.slice(home.length) : path;
41087
+ }
41088
+ async function readSettingsFile(path) {
41089
+ let raw;
41090
+ try {
41091
+ raw = await (0, import_promises16.readFile)(path, "utf-8");
41092
+ } catch (err2) {
41093
+ if (err2.code === "ENOENT") return { ok: true, settings: {}, exists: false };
41094
+ return { ok: false, error: `Could not read ${displayPath(path)}: ${err2.message}` };
41095
+ }
41096
+ try {
41097
+ const parsed = raw.trim() === "" ? {} : JSON.parse(raw);
41098
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
41099
+ return { ok: false, error: `${displayPath(path)} is not a JSON object. Fix it, then run the command again. Nothing was changed.` };
41100
+ }
41101
+ return { ok: true, settings: parsed, exists: true };
41102
+ } catch {
41103
+ return { ok: false, error: `${displayPath(path)} is not valid JSON. Fix it, then run the command again. Nothing was changed.` };
41104
+ }
41105
+ }
41106
+
41107
+ // src/lib/telemetry-local.ts
41108
+ var MAX_LOCAL_SETTINGS_BYTES = 1024 * 1024;
41109
+ function nextLocalBytes(raw, value) {
41110
+ try {
41111
+ if (jsonSemanticEqual(JSON.parse(raw), value)) return null;
41112
+ } catch {
41113
+ }
41114
+ const next = JSON.stringify(value, null, detectJsonIndent(raw)) + "\n";
41115
+ return next === raw ? null : next;
41116
+ }
41117
+ var OLD_LOCAL_WRITER_VALUES = {
41118
+ CLAUDE_CODE_ENABLE_TELEMETRY: "1",
41119
+ OTEL_METRICS_EXPORTER: "otlp",
41120
+ OTEL_TRACES_EXPORTER: "otlp",
41121
+ CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
41122
+ OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
41123
+ OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: "delta",
41124
+ OTEL_LOG_USER_PROMPTS: "0",
41125
+ OTEL_LOG_TOOL_DETAILS: "0",
41126
+ OTEL_LOG_TOOL_CONTENT: "0",
41127
+ OTEL_METRICS_INCLUDE_SESSION_ID: "true",
41128
+ OTEL_METRICS_INCLUDE_ACCOUNT_UUID: "false",
41129
+ OTEL_METRICS_INCLUDE_VERSION: "false",
41130
+ OTEL_METRIC_EXPORT_INTERVAL: "60000"
41131
+ };
41132
+ var LEGACY_VERITY_HEADER = /^Authorization=Bearer (?:verity|gate)_/;
41133
+ function planLocalHeal(settings) {
41134
+ const env = envOf(settings);
41135
+ const envKeys = [];
41136
+ if (typeof env[LEGACY_HEADER_KEY] === "string" && LEGACY_VERITY_HEADER.test(env[LEGACY_HEADER_KEY])) {
41137
+ envKeys.push(LEGACY_HEADER_KEY);
41138
+ }
41139
+ if (isVerityOtlpEndpoint(env.OTEL_EXPORTER_OTLP_ENDPOINT)) {
41140
+ envKeys.push("OTEL_EXPORTER_OTLP_ENDPOINT");
41141
+ for (const [key, value] of Object.entries(OLD_LOCAL_WRITER_VALUES)) {
41142
+ if (env[key] === value) envKeys.push(key);
41143
+ }
41144
+ }
41145
+ return { envKeys, helper: settings.otelHeadersHelper === OTEL_HEADERS_HELPER_CMD };
41146
+ }
41147
+ function readLocalSettings(root) {
41148
+ const read2 = readRegularFileInRoot(root, SETTINGS_LOCAL_FILE2, MAX_LOCAL_SETTINGS_BYTES);
41149
+ if (!read2.ok) return null;
41150
+ try {
41151
+ const parsed = JSON.parse(read2.content);
41152
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
41153
+ return { raw: read2.content, settings: parsed };
41154
+ } catch {
41155
+ return null;
41156
+ }
41157
+ }
41158
+ function localTelemetryLeftovers(root = repoRoot()) {
41159
+ const local = readLocalSettings(root);
41160
+ if (!local) return [];
41161
+ const plan = planLocalHeal(local.settings);
41162
+ return [...plan.envKeys, ...plan.helper ? ["otelHeadersHelper"] : []];
41163
+ }
41164
+ async function healLocalTelemetry(root = repoRoot()) {
41165
+ const local = readLocalSettings(root);
41166
+ if (!local) return { removed: [] };
41167
+ const settings = local.settings;
41168
+ const plan = planLocalHeal(settings);
41169
+ if (plan.envKeys.length === 0 && !plan.helper) return { removed: [] };
41170
+ const env = envOf(settings);
41171
+ for (const key of plan.envKeys) delete env[key];
41172
+ if (settings.env && Object.keys(env).length === 0) delete settings.env;
41173
+ if (plan.helper) delete settings.otelHeadersHelper;
41174
+ const next = nextLocalBytes(local.raw, settings);
41175
+ if (next !== null && !replaceInRoot((0, import_node_path61.join)(root, ".claude"), "settings.local.json", next)) {
41176
+ return { removed: [] };
41177
+ }
41178
+ return { removed: [...plan.envKeys, ...plan.helper ? ["otelHeadersHelper"] : []] };
41179
+ }
41180
+
41181
+ // src/lib/telemetry-gate.ts
41182
+ var import_node_child_process20 = require("node:child_process");
41183
+ var import_node_fs73 = require("node:fs");
41184
+ var import_node_path62 = require("node:path");
41185
+ var PROJECT_MARKERS = ["setup.json", "config.json"];
41186
+ function writtenOnThisMachine(root, rel) {
41187
+ const r = (0, import_node_child_process20.spawnSync)("git", hardenedGitArgv(["ls-files", "--error-unmatch", "--", rel]), {
41188
+ cwd: root,
41189
+ env: hardenedGitEnv(),
41190
+ encoding: "utf-8",
41191
+ stdio: ["ignore", "ignore", "pipe"],
41192
+ timeout: 5e3
41193
+ });
41194
+ if (r.status === 1) return true;
41195
+ if (r.status === 128 && /not a git repository/i.test(r.stderr ?? "")) return true;
41196
+ return false;
41197
+ }
41198
+ function isTelemetryProject() {
41199
+ for (const root of [repoRoot(), mainWorktreeRoot()]) {
41200
+ if (!root) continue;
41201
+ const dir = (0, import_node_path62.join)(root, VERITY_DIR);
41202
+ if (isMachineStore(dir)) continue;
41203
+ for (const marker of PROJECT_MARKERS) {
41204
+ if ((0, import_node_fs73.existsSync)((0, import_node_path62.join)(dir, marker)) && writtenOnThisMachine(root, `${VERITY_DIR}/${marker}`)) return true;
41205
+ }
41206
+ }
41207
+ return false;
41208
+ }
41209
+ async function telemetryPinRefusal(token, endpoint) {
41210
+ const serviceUrl = endpoint.trim().replace(/\/+$/, "").replace(/\/otlp$/, "");
41211
+ const pin = await checkTokenPin(token, serviceUrl);
41212
+ if (pin.attach) return null;
41213
+ if (pin.reason === "mismatch" && sameUrl(deriveOtlpEndpoint(pin.mintedFor), endpoint)) return null;
41214
+ return pinRefusalMessage(pin, serviceUrl);
41215
+ }
41216
+ async function sessionExportEndpoints() {
41217
+ const pick = (env) => OTLP_ENDPOINT_KEYS.map((key) => env[key]).filter((v) => typeof v === "string" && v.trim() !== "");
41218
+ const fromEnv = pick(process.env);
41219
+ if (fromEnv.length > 0) return fromEnv;
41220
+ const read2 = await readSettingsFile(userSettingsPath());
41221
+ return read2.ok ? pick(envOf(read2.settings)) : [];
41222
+ }
41223
+
41224
+ // src/lib/telemetry.ts
41225
+ function buildTelemetryEnv(serviceUrl) {
41226
+ return {
41227
+ CLAUDE_CODE_ENABLE_TELEMETRY: "1",
41228
+ OTEL_METRICS_EXPORTER: "otlp",
41229
+ OTEL_TRACES_EXPORTER: "otlp",
41230
+ CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
41231
+ OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
41232
+ OTEL_EXPORTER_OTLP_ENDPOINT: deriveOtlpEndpoint(serviceUrl),
41233
+ OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: "delta",
41234
+ OTEL_LOG_USER_PROMPTS: "0",
41235
+ OTEL_LOG_TOOL_DETAILS: "0",
41236
+ OTEL_LOG_TOOL_CONTENT: "0",
41237
+ OTEL_METRICS_INCLUDE_SESSION_ID: "true",
41238
+ OTEL_METRICS_INCLUDE_ACCOUNT_UUID: "false",
41239
+ OTEL_METRICS_INCLUDE_VERSION: "false",
41240
+ OTEL_METRICS_INCLUDE_REPOSITORY: "true",
41241
+ OTEL_METRIC_EXPORT_INTERVAL: "60000"
41242
+ };
41243
+ }
41244
+ var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv(""));
41245
+ var EXPORTER_KEYS = ["OTEL_METRICS_EXPORTER", "OTEL_TRACES_EXPORTER"];
41246
+ function foreignExportKey(settings, endpoint) {
41247
+ const helper = settings.otelHeadersHelper;
41248
+ if (typeof helper === "string" && helper.trim() !== "" && helper !== OTEL_HEADERS_HELPER_CMD) {
41249
+ return "otelHeadersHelper";
41250
+ }
41251
+ const env = envOf(settings);
41252
+ for (const key of OTLP_ENDPOINT_KEYS) {
41253
+ const value = env[key];
41254
+ if (value !== void 0 && value !== "" && !isVerityOtlpEndpoint(value, endpoint)) return key;
41255
+ }
41256
+ if (env[LEGACY_HEADER_KEY] !== void 0 && env[LEGACY_HEADER_KEY] !== "") return LEGACY_HEADER_KEY;
41257
+ for (const key of EXPORTER_KEYS) {
41258
+ const value = env[key];
41259
+ if (value !== void 0 && value !== "" && value !== "otlp") return key;
41260
+ }
41261
+ return null;
41262
+ }
41263
+ async function installTelemetry(serviceUrl, opts = {}) {
41264
+ const path = userSettingsPath();
41265
+ const read2 = await readSettingsFile(path);
41266
+ if (!read2.ok) return { ok: false, error: read2.error };
41267
+ const settings = read2.settings;
41268
+ const env = buildTelemetryEnv(serviceUrl);
41269
+ const foreign = foreignExportKey(settings, env.OTEL_EXPORTER_OTLP_ENDPOINT);
41270
+ if (foreign) {
41271
+ return {
41272
+ ok: false,
41273
+ error: `Telemetry not installed: ${displayPath(path)} already sets ${foreign}, so you export somewhere else already. Nothing was changed.`
41274
+ };
41275
+ }
41276
+ let token = opts.token;
41277
+ if (!token) {
41278
+ const resolved = await resolveToken();
41279
+ if (!resolved.ok) {
41280
+ return { ok: false, error: 'Telemetry not installed: no Verity token on this machine. Run "verity login". Nothing was changed.' };
41281
+ }
41282
+ token = resolved.data.token;
41283
+ }
41284
+ const refusal = await telemetryPinRefusal(token, env.OTEL_EXPORTER_OTLP_ENDPOINT);
41285
+ if (refusal) {
41286
+ return { ok: false, error: `Telemetry not installed: ${refusal} Nothing was changed.` };
41287
+ }
41288
+ settings.env = { ...envOf(settings), ...env };
41289
+ settings.otelHeadersHelper = OTEL_HEADERS_HELPER_CMD;
41290
+ await writeJsonFilePreservingStyle(path, settings);
41291
+ const healed = await healLocalTelemetry();
41292
+ return {
41293
+ ok: true,
41294
+ data: { endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT, settingsPath: path, healedLocalKeys: healed.removed }
41295
+ };
41296
+ }
41297
+ async function checkTelemetry() {
41298
+ const path = userSettingsPath();
41299
+ const read2 = await readSettingsFile(path);
41300
+ const settings = read2.ok ? read2.settings : {};
41301
+ const env = envOf(settings);
41302
+ const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT || null;
41303
+ const enabled = env.CLAUDE_CODE_ENABLE_TELEMETRY === "1" && !!endpoint && settings.otelHeadersHelper === OTEL_HEADERS_HELPER_CMD;
41304
+ return { enabled, endpoint, settingsPath: path, staleLocalKeys: localTelemetryLeftovers() };
41305
+ }
41306
+ async function uninstallTelemetry() {
41307
+ const path = userSettingsPath();
41308
+ const read2 = await readSettingsFile(path);
41309
+ if (!read2.ok) return { ok: false, error: read2.error };
41310
+ const settings = read2.settings;
41311
+ const env = envOf(settings);
41312
+ let removed = 0;
41313
+ const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;
41314
+ const foreignEndpoint = endpoint !== void 0 && endpoint !== "" && !isVerityOtlpEndpoint(endpoint);
41315
+ if (isVerityOtlpEndpoint(endpoint)) {
41316
+ const verityValues = buildTelemetryEnv("");
41317
+ for (const key of VERITY_TELEMETRY_KEYS) {
41318
+ if (key === "OTEL_EXPORTER_OTLP_ENDPOINT" || env[key] === verityValues[key]) {
41319
+ if (key in env) {
41320
+ delete env[key];
41321
+ removed++;
41322
+ }
41323
+ }
41324
+ }
41325
+ if (settings.otelHeadersHelper === OTEL_HEADERS_HELPER_CMD) delete settings.otelHeadersHelper;
41326
+ if (settings.env && Object.keys(env).length === 0) delete settings.env;
41327
+ await writeJsonFilePreservingStyle(path, settings);
41328
+ }
41329
+ const healed = await healLocalTelemetry();
41330
+ return { ok: true, data: { removed, settingsPath: path, foreignEndpoint, healedLocalKeys: healed.removed } };
41331
+ }
41332
+
41169
41333
  // src/commands/init/steps.ts
41170
41334
  function languageShares(d) {
41171
41335
  const total = d.languages.reduce((n2, l) => n2 + (d.languageCounts[l] ?? 0), 0);
@@ -41234,19 +41398,19 @@ async function prerequisitesStep(ui) {
41234
41398
  const analysis = report.checks.find((c) => c.id === "analysis-cli");
41235
41399
  if (analysis && analysis.status !== "ok") {
41236
41400
  const s = ui.screen.s;
41237
- const installed2 = await ui.screen.spin(`${s.gray}\u2502${s.reset} `, () => "Installing the Codacy analysis CLI (it runs the Trivy security scan)", installAnalysisCli({ quiet: true, sudo: false }));
41401
+ const installed2 = await ui.screen.spin(`${s.gray}\u2502${s.reset} `, () => "Verity's copy of the Codacy analysis CLI is missing. Installing it (it runs the Trivy security scan)", installAnalysisCli({ quiet: true, sudo: false }));
41238
41402
  report.checks[report.checks.indexOf(analysis)] = installed2;
41239
41403
  if (installed2.status === "ok") ui.rail.ticked("Installed the Codacy analysis CLI");
41240
41404
  }
41241
41405
  for (const c of report.checks) {
41242
41406
  if (c.status === "ok") continue;
41243
- ui.rail.warn(`${c.label}: ${c.detail}`);
41244
- if (c.remedy) ui.rail.line(c.remedy);
41407
+ ui.rail.warn(terminalText(`${c.label}: ${c.detail}`, 300));
41408
+ if (c.remedy) ui.rail.line(terminalText(c.remedy, 300));
41245
41409
  }
41246
41410
  ui.rail.settle();
41247
41411
  return !report.checks.some((c) => c.required && c.status !== "ok");
41248
41412
  }
41249
- async function telemetryStep(ui, answer, resumeDeferred, globals) {
41413
+ async function telemetryStep(ui, answer, resumeDeferred, globals, previous) {
41250
41414
  if (answer === "already-on") return "enabled";
41251
41415
  if (answer === "no") return "declined";
41252
41416
  if (answer === "turn-off") {
@@ -41256,7 +41420,16 @@ async function telemetryStep(ui, answer, resumeDeferred, globals) {
41256
41420
  ui.rail.settle();
41257
41421
  return "declined";
41258
41422
  }
41259
- if (answer !== "yes" && !resumeDeferred) return void 0;
41423
+ if (answer === "not-asked" && !resumeDeferred) {
41424
+ if (!ui.asking || previous === "declined") return void 0;
41425
+ if (!(await resolveToken(globals.token)).ok) return void 0;
41426
+ if ((await checkTelemetry()).enabled) return "enabled";
41427
+ if (!await askTelemetry(ui)) {
41428
+ ui.rail.line("Telemetry stays off. Turn it on later: verity telemetry install", "muted");
41429
+ ui.rail.settle();
41430
+ return "declined";
41431
+ }
41432
+ }
41260
41433
  const token = await resolveToken(globals.token);
41261
41434
  const url = await resolveServiceUrl(globals.serviceUrl);
41262
41435
  if (!token.ok || !url.ok) {
@@ -41273,7 +41446,18 @@ async function telemetryStep(ui, answer, resumeDeferred, globals) {
41273
41446
  }
41274
41447
  ui.rail.gap();
41275
41448
  ui.rail.settle();
41276
- return installed2.ok ? "enabled" : "deferred";
41449
+ return installed2.ok ? "enabled" : void 0;
41450
+ }
41451
+ async function askTelemetry(ui) {
41452
+ const { rail, screen } = ui;
41453
+ rail.active("Send Claude Code cost and usage from this machine to Verity?");
41454
+ rail.line("Token counts, cost, models, tool names and the repository name, from every Claude Code session on this machine. No prompts, no code, no file contents.");
41455
+ rail.line("See it on the /usage page. Turn it off any time: verity telemetry uninstall");
41456
+ rail.gap();
41457
+ rail.keys([["Enter", "yes"], ["n", "no"]]);
41458
+ const k = await screen.key(["enter", "n"], "enter", { freshMs: 300 });
41459
+ rail.collapse();
41460
+ return k !== "n";
41277
41461
  }
41278
41462
  function signInDetail(outcome) {
41279
41463
  if (outcome === "already" || outcome === "new") return ["ok", outcome];
@@ -41324,6 +41508,55 @@ function backgroundStep(ui, opts) {
41324
41508
 
41325
41509
  // src/commands/init/scaffold.ts
41326
41510
  var import_promises17 = require("node:fs/promises");
41511
+
41512
+ // src/lib/codacy-legacy.ts
41513
+ var import_node_child_process21 = require("node:child_process");
41514
+ var VERITY_ESLINT_CONFIG_FILE = ".codacy/tools-configs/eslint.config.mjs";
41515
+ var VERITY_ESLINT_LOCAL_PATH = "./.codacy/tools-configs/eslint.config.mjs";
41516
+ var VERITY_ESLINT_FIRST_LINE = "// Verity-owned ESLint config for the quality gate.";
41517
+ var MAX_BYTES = 256 * 1024;
41518
+ function classifyCodacyConfig(root = repoRoot()) {
41519
+ const cfg = readRegularFileInRoot(root, CODACY_CONFIG_FILE, MAX_BYTES);
41520
+ if (!cfg.ok) return cfg.reason === "missing" ? "none" : "foreign";
41521
+ let parsed;
41522
+ try {
41523
+ parsed = JSON.parse(cfg.content);
41524
+ } catch {
41525
+ return "foreign";
41526
+ }
41527
+ const tools = typeof parsed === "object" && parsed !== null ? parsed.tools : void 0;
41528
+ const pointsAtVerity2 = Array.isArray(tools) && tools.some((t) => typeof t === "object" && t !== null && t.localConfigurationFile === VERITY_ESLINT_LOCAL_PATH);
41529
+ if (!pointsAtVerity2) return "foreign";
41530
+ const eslint = readRegularFileInRoot(root, VERITY_ESLINT_CONFIG_FILE, MAX_BYTES);
41531
+ return eslint.ok && eslint.content.startsWith(VERITY_ESLINT_FIRST_LINE) ? "verity" : "foreign";
41532
+ }
41533
+ function isTracked(root, path) {
41534
+ try {
41535
+ (0, import_node_child_process21.execFileSync)("git", ["ls-files", "--error-unmatch", "--", path], { cwd: root, stdio: "pipe" });
41536
+ return true;
41537
+ } catch {
41538
+ return false;
41539
+ }
41540
+ }
41541
+ function removeVerityCodacyConfig(root = repoRoot()) {
41542
+ const removed = [];
41543
+ const untracked = [];
41544
+ try {
41545
+ for (const f of [CODACY_CONFIG_FILE, VERITY_ESLINT_CONFIG_FILE]) {
41546
+ if (!isTracked(root, f)) {
41547
+ untracked.push(f);
41548
+ continue;
41549
+ }
41550
+ (0, import_node_child_process21.execFileSync)("git", ["rm", "--quiet", "--", f], { cwd: root, stdio: "pipe" });
41551
+ removed.push(f);
41552
+ }
41553
+ } catch (err2) {
41554
+ return { ok: false, removed, error: err2.message };
41555
+ }
41556
+ return { ok: true, removed, untracked };
41557
+ }
41558
+
41559
+ // src/commands/init/scaffold.ts
41327
41560
  async function scaffoldStep(ui, given = {}) {
41328
41561
  const { rail } = ui;
41329
41562
  await (0, import_promises17.mkdir)(VERITY_DIR, { recursive: true });
@@ -41351,15 +41584,16 @@ async function scaffoldStep(ui, given = {}) {
41351
41584
  }
41352
41585
  const tracked = committedVerityState();
41353
41586
  if (tracked.length > 0) {
41354
- if (given.untrackState) {
41587
+ if (given.untrackState || ui.asking && await askUntrackState(ui, tracked)) {
41355
41588
  const result = untrackVerityState();
41356
41589
  if (result === "untracked") rail.line("Untracked the Verity state files (staged). Commit to finish.");
41357
41590
  else if (result === "failed") rail.line("Could not untrack them. Run: git rm -r --cached .verity && git add .verity/standard.yaml", "warn");
41358
41591
  } else {
41359
41592
  rail.warn(`${tracked.length} Verity state file(s) are tracked in git`);
41360
- rail.line(`For example ${tracked[0]}. Fix: git rm -r --cached .verity && git add .verity/standard.yaml`);
41593
+ rail.line(`For example ${terminalText(tracked[0], 200)}. Fix: git rm -r --cached .verity && git add .verity/standard.yaml`);
41361
41594
  }
41362
41595
  }
41596
+ retireCodacyConfig(ui);
41363
41597
  try {
41364
41598
  await ensureClaudeMdPointer();
41365
41599
  } catch (err2) {
@@ -41369,11 +41603,44 @@ async function scaffoldStep(ui, given = {}) {
41369
41603
  if (cfg.formal_refused) rail.line(`Formal verification: off \u2014 ${formalRefusalMessage(cfg.formal_refused)}`, "warn");
41370
41604
  rail.settle();
41371
41605
  }
41606
+ async function askUntrackState(ui, tracked) {
41607
+ const { rail, screen } = ui;
41608
+ rail.active(`${tracked.length} Verity state file(s) are tracked in git`);
41609
+ rail.line(`For example ${terminalText(tracked[0], 200)}. They can hold copies of secrets the gate flagged, and each push shares them.`);
41610
+ rail.line("Stop tracking them? The files stay on disk, and the removal is staged for you to commit.");
41611
+ rail.line("Copies already pushed stay in the git history: rotate any secret the gate flagged.");
41612
+ rail.gap();
41613
+ rail.keys([["Enter", "stop tracking them"], ["n", "keep them tracked"]]);
41614
+ const k = await screen.key(["enter", "n"], "enter", { freshMs: 300 });
41615
+ rail.collapse();
41616
+ return k !== "n";
41617
+ }
41618
+ function retireCodacyConfig(ui) {
41619
+ const kind2 = classifyCodacyConfig();
41620
+ if (kind2 === "none") return;
41621
+ if (kind2 === "foreign") {
41622
+ ui.rail.line("Verity no longer reads .codacy/. Remove it if nothing else uses it.", "muted");
41623
+ return;
41624
+ }
41625
+ const result = removeVerityCodacyConfig();
41626
+ if (result.ok) {
41627
+ if (result.removed.length > 0) {
41628
+ ui.rail.done("Removed the .codacy/ config that an older Verity wrote (staged)");
41629
+ ui.rail.line("Verity runs Trivy from .verity/static-analysis.json now. To keep the files: git restore --staged --worktree .codacy");
41630
+ }
41631
+ if (result.untracked.length > 0) {
41632
+ ui.rail.line(`An older Verity wrote ${result.untracked.join(" and ")}, and Verity no longer uses them. Remove them by hand: rm ${result.untracked.join(" ")}`, "warn");
41633
+ }
41634
+ } else {
41635
+ ui.rail.warn("Could not remove the .codacy/ config that an older Verity wrote");
41636
+ ui.rail.line(terminalText(result.error, 200));
41637
+ }
41638
+ }
41372
41639
 
41373
41640
  // src/commands/init/wiring.ts
41374
- var import_node_fs72 = require("node:fs");
41641
+ var import_node_fs74 = require("node:fs");
41375
41642
  var import_promises18 = require("node:fs/promises");
41376
- var import_node_path59 = require("node:path");
41643
+ var import_node_path63 = require("node:path");
41377
41644
  var DEFAULT_MOMENTS = ["stop", "pre-commit", "pre-push"];
41378
41645
  async function momentsFromInstalledHooks() {
41379
41646
  const status = await checkAllVerityHooks();
@@ -41446,12 +41713,12 @@ var SKILLS = [
41446
41713
  ];
41447
41714
  function resolveDataDir2() {
41448
41715
  const candidates2 = [
41449
- (0, import_node_path59.join)(__dirname, "..", "data"),
41450
- (0, import_node_path59.join)(__dirname, "..", "..", "data"),
41451
- (0, import_node_path59.join)(process.cwd(), "cli", "data")
41716
+ (0, import_node_path63.join)(__dirname, "..", "data"),
41717
+ (0, import_node_path63.join)(__dirname, "..", "..", "data"),
41718
+ (0, import_node_path63.join)(process.cwd(), "cli", "data")
41452
41719
  ];
41453
41720
  for (const candidate of candidates2) {
41454
- if ((0, import_node_fs72.existsSync)((0, import_node_path59.join)(candidate, "skills"))) return candidate;
41721
+ if ((0, import_node_fs74.existsSync)((0, import_node_path63.join)(candidate, "skills"))) return candidate;
41455
41722
  }
41456
41723
  throw new Error("Could not find Verity skill data. Reinstall: npm install -g @codacy/verity-cli");
41457
41724
  }
@@ -41459,9 +41726,9 @@ async function skillIsCurrent(src, dest) {
41459
41726
  const list2 = (dir) => {
41460
41727
  const out2 = [];
41461
41728
  const walk2 = (d, prefix) => {
41462
- for (const e of (0, import_node_fs72.readdirSync)(d, { withFileTypes: true })) {
41729
+ for (const e of (0, import_node_fs74.readdirSync)(d, { withFileTypes: true })) {
41463
41730
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
41464
- if (e.isDirectory()) walk2((0, import_node_path59.join)(d, e.name), rel);
41731
+ if (e.isDirectory()) walk2((0, import_node_path63.join)(d, e.name), rel);
41465
41732
  else if (e.isFile()) out2.push(rel);
41466
41733
  }
41467
41734
  };
@@ -41472,7 +41739,7 @@ async function skillIsCurrent(src, dest) {
41472
41739
  const shipped = list2(src);
41473
41740
  if (JSON.stringify(shipped) !== JSON.stringify(list2(dest))) return false;
41474
41741
  for (const rel of shipped) {
41475
- if (await (0, import_promises18.readFile)((0, import_node_path59.join)(src, rel), "utf-8") !== await (0, import_promises18.readFile)((0, import_node_path59.join)(dest, rel), "utf-8")) return false;
41742
+ if (await (0, import_promises18.readFile)((0, import_node_path63.join)(src, rel), "utf-8") !== await (0, import_promises18.readFile)((0, import_node_path63.join)(dest, rel), "utf-8")) return false;
41476
41743
  }
41477
41744
  return true;
41478
41745
  } catch {
@@ -41480,16 +41747,16 @@ async function skillIsCurrent(src, dest) {
41480
41747
  }
41481
41748
  }
41482
41749
  async function installSkills(force) {
41483
- const source2 = (0, import_node_path59.join)(resolveDataDir2(), "skills");
41750
+ const source2 = (0, import_node_path63.join)(resolveDataDir2(), "skills");
41484
41751
  const missing = [];
41485
41752
  for (const skill of SKILLS) {
41486
- const src = (0, import_node_path59.join)(source2, skill);
41487
- const dest = (0, import_node_path59.join)(".claude/skills", skill);
41488
- if (!(0, import_node_fs72.existsSync)(src)) {
41753
+ const src = (0, import_node_path63.join)(source2, skill);
41754
+ const dest = (0, import_node_path63.join)(".claude/skills", skill);
41755
+ if (!(0, import_node_fs74.existsSync)(src)) {
41489
41756
  missing.push(skill);
41490
41757
  continue;
41491
41758
  }
41492
- if ((0, import_node_fs72.existsSync)(dest) && !force && await skillIsCurrent(src, dest)) continue;
41759
+ if ((0, import_node_fs74.existsSync)(dest) && !force && await skillIsCurrent(src, dest)) continue;
41493
41760
  await (0, import_promises18.mkdir)(dest, { recursive: true });
41494
41761
  await (0, import_promises18.cp)(src, dest, { recursive: true, force: true });
41495
41762
  }
@@ -41528,8 +41795,651 @@ async function hooksStep(ui, ctx) {
41528
41795
  rail.settle();
41529
41796
  }
41530
41797
 
41798
+ // src/commands/init/standard.ts
41799
+ var import_node_fs75 = require("node:fs");
41800
+
41801
+ // src/commands/init/rules.ts
41802
+ async function projectLabel(token, serviceUrl, verbose) {
41803
+ try {
41804
+ const who = await whoami(token, serviceUrl, verbose);
41805
+ return who.ok ? who.data.project_id : null;
41806
+ } catch {
41807
+ return null;
41808
+ }
41809
+ }
41810
+ function profilePreview(profile) {
41811
+ return JSON.stringify(profile, null, 2).split("\n").slice(0, 12).map((l) => terminalText(l, 400)).concat(["\u2026", `The whole profile: ${PROFILE_FILE}`]);
41812
+ }
41813
+ async function rulesOffered(ui, globals) {
41814
+ const token = await resolveToken(globals.token);
41815
+ const url = await resolveServiceUrl(globals.serviceUrl);
41816
+ if (!token.ok || !url.ok) return false;
41817
+ const status = await generationStatus({ serviceUrl: url.data, token: token.data.token, verbose: globals.verbose });
41818
+ if (status === "off") ui.rail.line("Project rules are not switched on for this project yet. The built-in rules apply.");
41819
+ if (status === "unsupported") {
41820
+ ui.rail.line(`This Verity service (${terminalText(url.data, 200)}) does not write project rules yet: it is older than this CLI. The built-in rules apply.`);
41821
+ }
41822
+ return status === "on" || status === "unknown";
41823
+ }
41824
+ async function askToSend(ui, profile) {
41825
+ const { rail, screen } = ui;
41826
+ const lines = consentLines(profile);
41827
+ rail.active("Write review rules for this repository?");
41828
+ rail.box(lines.slice(1, -1));
41829
+ rail.line(lines[lines.length - 1]);
41830
+ rail.gap();
41831
+ rail.keys([["Enter", "send"], ["v", "view the profile"], ["n", "skip"]]);
41832
+ let allowed = ["enter", "v", "n"];
41833
+ for (; ; ) {
41834
+ const k = await screen.key(allowed, "n", { freshMs: 300 });
41835
+ if (k !== "v") {
41836
+ rail.collapse();
41837
+ return k === "enter";
41838
+ }
41839
+ rail.gap();
41840
+ for (const l of profilePreview(profile)) rail.line(l);
41841
+ rail.gap();
41842
+ rail.keys([["Enter", "send"], ["n", "skip"]]);
41843
+ allowed = ["enter", "n"];
41844
+ }
41845
+ }
41846
+ async function generate(ui, globals, profile, replaces) {
41847
+ const { rail, screen } = ui;
41848
+ const s = screen.s;
41849
+ const token = await resolveToken(globals.token);
41850
+ const url = await resolveServiceUrl(globals.serviceUrl);
41851
+ if (!token.ok || !url.ok) return { status: "fallback" };
41852
+ const commits = profile.history.commits.length;
41853
+ const fixes = profile.history.commits.filter((c) => c.fix).length;
41854
+ const reverts = profile.history.commits.filter((c) => /^revert\b/i.test(c.subject)).length;
41855
+ rail.active("Writing review rules");
41856
+ await rail.counted(([c, f, r]) => `${s.green}\u2713${s.reset} Read ${c} commits ${s.gray}\xB7${s.reset} ${f} fixes ${s.gray}\xB7${s.reset} ${r} reverts`, [commits, fixes, reverts]);
41857
+ if (profile.docs.length) rail.ticked(`Read ${profile.docs.map((d) => terminalText(d.name, 120)).join(" and ")}`);
41858
+ if (ui.term.screen && !ui.term.motion) rail.line("Writing rules for this repository\u2026");
41859
+ const started = Date.now();
41860
+ const base = buildStandard({ detected: detectProject() }, loadCatalog());
41861
+ const outcome = await screen.spin(`${s.gray}\u2502${s.reset} `, () => "Writing rules for this repository", generateStandard({
41862
+ serviceUrl: url.data,
41863
+ token: token.data.token,
41864
+ profile,
41865
+ base,
41866
+ projectId: await projectLabel(token.data.token, url.data, globals.verbose),
41867
+ verbose: globals.verbose,
41868
+ replaces
41869
+ }));
41870
+ rail.collapse();
41871
+ if (outcome.status === "exists") return { status: "exists" };
41872
+ if (outcome.status === "disabled") {
41873
+ rail.line("Project rules are not switched on for this project yet. The built-in rules apply.");
41874
+ rail.gap();
41875
+ return { status: "fallback" };
41876
+ }
41877
+ if (outcome.status === "unsupported") {
41878
+ rail.line(`This Verity service (${terminalText(url.data, 200)}) does not write project rules yet: it is older than this CLI. The built-in rules apply.`);
41879
+ rail.gap();
41880
+ return { status: "fallback" };
41881
+ }
41882
+ if (outcome.status === "failed") {
41883
+ rail.warn("The rules could not be written this time");
41884
+ rail.line(terminalText(outcome.reason, 300));
41885
+ rail.line("The built-in rules apply. Try again later: verity standard generate");
41886
+ rail.gap();
41887
+ return { status: "fallback" };
41888
+ }
41889
+ const secs2 = Math.max(1, Math.round((Date.now() - started) / 1e3));
41890
+ const docs = profile.docs.length;
41891
+ if (outcome.rules.length === 0) {
41892
+ rail.warn("No project rules this time");
41893
+ rail.line("The proposed rules were too generic, or not safe to use. The built-in rules apply.");
41894
+ } else {
41895
+ const n2 = outcome.rules.length;
41896
+ const from = [`${commits} ${commits === 1 ? "commit" : "commits"}`, ...docs ? [`${docs} ${docs === 1 ? "doc" : "docs"}`] : []].join(" and ");
41897
+ rail.done(`${n2} ${n2 === 1 ? "rule" : "rules"} for this repository ${s.gray}\xB7 from ${from} \xB7 ${secs2}s${s.reset}`);
41898
+ await rail.rules(outcome.rules);
41899
+ if (outcome.rejected > 0) rail.line(`${outcome.rejected} proposed ${outcome.rejected === 1 ? "rule was" : "rules were"} left out: too generic, or not safe to use.`);
41900
+ }
41901
+ rail.gap();
41902
+ rail.done(`Standard v${outcome.version} saved`);
41903
+ rail.line(`Your team gets the same rules. Edit them in ${STANDARD_FILE}`);
41904
+ if (outcome.warning) rail.line(terminalText(outcome.warning, 600), "warn");
41905
+ return { status: "created", version: outcome.version };
41906
+ }
41907
+
41908
+ // src/commands/init/team-standard.ts
41909
+ async function lookUpTeamStandard(opts) {
41910
+ if (!opts.adopt) return { status: "skipped", why: "no-adopt" };
41911
+ const token = await resolveToken(opts.globals.token);
41912
+ const url = await resolveServiceUrl(opts.globals.serviceUrl);
41913
+ if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
41914
+ const found = await fetchRemoteSetup({ token: token.data.token, serviceUrl: url.data, verbose: opts.globals.verbose });
41915
+ if (found.status === "unavailable") return { status: "unavailable", reason: terminalText(found.reason, 300) };
41916
+ if (found.status === "none") return { status: "none" };
41917
+ if (opts.previous?.declinedStandardVersion === found.standard.version) {
41918
+ return { status: "declined-before", version: found.standard.version };
41919
+ }
41920
+ return { status: "found", found, version: found.standard.version };
41921
+ }
41922
+ async function writeTeamStandard(team, opts) {
41923
+ const token = await resolveToken(opts.globals.token);
41924
+ const url = await resolveServiceUrl(opts.globals.serviceUrl);
41925
+ if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
41926
+ const found = team.found;
41927
+ try {
41928
+ const outcome = await adoptRemoteSetup(found, {
41929
+ serviceUrl: url.data,
41930
+ projectId: await projectLabel(token.data.token, url.data, opts.globals.verbose)
41931
+ });
41932
+ if (opts.previous?.declinedStandardVersion != null) {
41933
+ await writeSetupState({ declinedStandardVersion: void 0 }).catch(() => {
41934
+ });
41935
+ }
41936
+ return { status: "adopted", version: found.standard.version, summary: describeRemote(found), notes: outcome.notes };
41937
+ } catch (err2) {
41938
+ return { status: "write-failed", reason: terminalText(err2.message, 300) };
41939
+ }
41940
+ }
41941
+ async function adoptTeamStandard(opts) {
41942
+ const team = await lookUpTeamStandard(opts);
41943
+ return team.status === "found" ? writeTeamStandard(team, opts) : team;
41944
+ }
41945
+ async function askTeamChoice(ui, team) {
41946
+ const { rail, screen } = ui;
41947
+ rail.active("Your team already has a Standard for this repository");
41948
+ rail.line(describeRemote(team.found));
41949
+ rail.line(`Use it to review with the same rules as everyone else. A new one becomes v${team.version + 1} for the whole team.`);
41950
+ rail.gap();
41951
+ rail.keys([["Enter", "use it"], ["n", "create a new one"]]);
41952
+ const k = await screen.key(["enter", "n"], "enter", { freshMs: 300 });
41953
+ rail.collapse();
41954
+ return k === "n" ? "new" : "use";
41955
+ }
41956
+ function sayAdopted(ui, team) {
41957
+ ui.rail.done(`Using your team's Standard v${team.version}`);
41958
+ ui.rail.line(terminalText(team.summary, 300));
41959
+ ui.rail.line(`Edit it in ${STANDARD_FILE}, then share the change: verity standard push`);
41960
+ for (const note of team.notes) ui.rail.line(terminalText(note, 300), "warn");
41961
+ }
41962
+
41963
+ // src/commands/init/standard.ts
41964
+ async function deriveFromCatalogue(ui, globals) {
41965
+ const url = await resolveServiceUrl(globals.serviceUrl);
41966
+ try {
41967
+ const result = await runSynthesis({ serviceUrl: url.ok ? url.data : void 0 });
41968
+ if ("refused" in result) {
41969
+ ui.rail.warn("No Standard was written");
41970
+ ui.rail.line(terminalText(result.refused, 300));
41971
+ return { ok: false, version: null };
41972
+ }
41973
+ const pushed = await pushStandard(globals);
41974
+ if (pushed.standardVersion !== null) {
41975
+ await correctVerityMdVersion({
41976
+ standard: result.standard,
41977
+ version: pushed.standardVersion,
41978
+ tools: result.tools,
41979
+ serviceUrl: url.ok ? url.data : void 0
41980
+ });
41981
+ ui.rail.done(`Standard v${pushed.standardVersion} saved`);
41982
+ ui.rail.line(`Your team gets the same rules. Edit them in ${STANDARD_FILE}`);
41983
+ } else {
41984
+ ui.rail.done("Standard saved on this machine");
41985
+ }
41986
+ for (const line2 of pushed.lines) {
41987
+ if (line2.startsWith("Standard pushed")) continue;
41988
+ if (line2.startsWith("Not signed in")) ui.rail.line(`Sign in to share it with your team: verity login, then verity standard push. Edit it in ${STANDARD_FILE}`);
41989
+ else ui.rail.line(terminalText(line2, 600), "warn");
41990
+ }
41991
+ for (const note of result.notes) ui.rail.line(terminalText(note, 300), "warn");
41992
+ return { ok: true, version: pushed.standardVersion };
41993
+ } catch (err2) {
41994
+ ui.rail.warn("No Standard was written");
41995
+ ui.rail.line(terminalText(err2.message, 300));
41996
+ return { ok: false, version: null };
41997
+ }
41998
+ }
41999
+ async function standardStep(ui, ctx) {
42000
+ const { rail } = ui;
42001
+ const s = ui.screen.s;
42002
+ const notAsked = { outcome: "skipped", detail: "none" };
42003
+ const end = (r) => {
42004
+ rail.gap();
42005
+ rail.settle();
42006
+ return r;
42007
+ };
42008
+ const derived = async (profile2) => {
42009
+ const d = await deriveFromCatalogue(ui, ctx.globals);
42010
+ return end({ source: d.ok ? "derived" : "none", version: d.version, profile: profile2 });
42011
+ };
42012
+ if ((0, import_node_fs75.existsSync)(projectPath(STANDARD_FILE))) {
42013
+ const sync = await pushLocalStandardIfMissing(ctx.globals);
42014
+ rail.done("Kept your Standard");
42015
+ rail.line(`${STANDARD_FILE} is already here, so this run leaves it as it is. Edit it there, then share the change: verity standard push`);
42016
+ if (sync.status === "pushed") {
42017
+ rail.line(sync.version !== null ? `It was only on this machine. It is shared now as v${sync.version}.` : "It is only on this machine:", sync.version !== null ? "muted" : "warn");
42018
+ for (const line2 of sync.lines) if (!line2.startsWith("Standard pushed")) rail.line(terminalText(line2, 600), "warn");
42019
+ }
42020
+ return end({ source: "kept", version: sync.status === "pushed" ? sync.version : null, profile: notAsked });
42021
+ }
42022
+ const team = await lookUpTeamStandard(ctx);
42023
+ let replaces;
42024
+ if (team.status === "found") {
42025
+ const choice = ctx.standard ?? (ui.asking ? await askTeamChoice(ui, team) : "use");
42026
+ if (choice === "use") {
42027
+ const adopted = await writeTeamStandard(team, ctx);
42028
+ if (adopted.status === "adopted") {
42029
+ sayAdopted(ui, adopted);
42030
+ return end({ source: "adopted", version: adopted.version, profile: notAsked });
42031
+ }
42032
+ rail.warn("Could not write the team's Standard here");
42033
+ if (adopted.status === "write-failed") rail.line(terminalText(adopted.reason, 300));
42034
+ return derived(notAsked);
42035
+ }
42036
+ await writeSetupState({ declinedStandardVersion: team.version }).catch(() => {
42037
+ });
42038
+ rail.done(`Creating a new Standard ${s.gray}\xB7 it becomes v${team.version + 1} for the team${ctx.standard ? " (--standard new)" : ""}${s.reset}`);
42039
+ replaces = team.version;
42040
+ } else if (team.status === "declined-before") {
42041
+ rail.line(`This project turned down the team's Standard v${team.version} before, so this run builds its own.`);
42042
+ return derived(notAsked);
42043
+ }
42044
+ if (team.status === "unavailable") {
42045
+ rail.warn("Could not ask the Verity service for this repository's Standard");
42046
+ rail.line(terminalText(team.reason, 300));
42047
+ return derived(notAsked);
42048
+ }
42049
+ if (team.status === "skipped") return derived(notAsked);
42050
+ if (!await rulesOffered(ui, ctx.globals)) return derived(notAsked);
42051
+ const profile = buildRepoProfile();
42052
+ try {
42053
+ writeRepoFile(PROFILE_FILE, JSON.stringify(profile, null, 2) + "\n");
42054
+ } catch (err2) {
42055
+ rail.warn(`Could not write ${PROFILE_FILE} for you to read, so nothing was sent`);
42056
+ rail.line(terminalText(err2.message, 200));
42057
+ return derived({ outcome: "failed", detail: "error" });
42058
+ }
42059
+ let detail;
42060
+ let send;
42061
+ if (ctx.profile) {
42062
+ send = ctx.profile === "send";
42063
+ detail = "given";
42064
+ } else if (ui.asking) {
42065
+ send = await askToSend(ui, profile);
42066
+ detail = send ? "asked" : "declined";
42067
+ } else {
42068
+ send = false;
42069
+ detail = "unattended";
42070
+ }
42071
+ if (!send) {
42072
+ rail.warn("Profile not sent");
42073
+ rail.line(detail === "unattended" ? "Nobody was asked, so nothing was sent. Your Standard has the built-in rules only." : "Your Standard has the built-in rules only.");
42074
+ rail.line(`Add project rules later: verity standard generate (read ${PROFILE_FILE} first)`);
42075
+ rail.gap();
42076
+ return derived({ outcome: "skipped", detail });
42077
+ }
42078
+ if (detail === "given") rail.done(`Profile sent ${s.gray}\xB7 no source code (--profile send)${s.reset}`);
42079
+ else rail.done(`Profile sent ${s.gray}\xB7 no source code${s.reset}`);
42080
+ rail.gap();
42081
+ rail.settle();
42082
+ const generated = await generate(ui, ctx.globals, profile, replaces);
42083
+ if (generated.status === "created") {
42084
+ return end({ source: "generated", version: generated.version, profile: { outcome: "ok", detail } });
42085
+ }
42086
+ if (generated.status === "exists") {
42087
+ const again = await adoptTeamStandard(ctx);
42088
+ if (again.status === "adopted") {
42089
+ sayAdopted(ui, again);
42090
+ return end({ source: "adopted", version: again.version, profile: { outcome: "ok", detail } });
42091
+ }
42092
+ }
42093
+ return derived({ outcome: "ok", detail });
42094
+ }
42095
+
42096
+ // src/commands/init/finish.ts
42097
+ async function finishRun(ui, ctx) {
42098
+ const { rail } = ui;
42099
+ const s = ui.screen.s;
42100
+ const secs2 = Math.max(1, Math.round((Date.now() - ctx.started) / 1e3));
42101
+ if (!ctx.haveStandard) {
42102
+ sayChanged(ui, ctx.before);
42103
+ rail.end(`${s.yellow}Not finished:${s.reset} no Standard was written, so the gate cannot review.`, "Fix the error above, then run verity init again.");
42104
+ return;
42105
+ }
42106
+ if (ctx.pluginMode) {
42107
+ const version = activePluginVersion();
42108
+ if (version) await writeSetupState({ pluginSetup: { version, completed_at: (/* @__PURE__ */ new Date()).toISOString() }, wiring: void 0 });
42109
+ } else if (ctx.noPlugin) {
42110
+ await writeSetupState({ wiring: "project" });
42111
+ }
42112
+ sayChanged(ui, ctx.before);
42113
+ const next = ctx.momentIds.length === 0 ? "Verity reviews nothing until you turn a moment on: verity config moments turn,commit,push" : ctx.pluginMode ? ctx.signedIn ? "Verity reviews from your next turn." : "The security scan runs from your next turn. Sign in for AI review: verity login" : ctx.signedIn ? "Start a new Claude Code session. Verity reviews from its first turn." : "Start a new Claude Code session. The security scan runs from the first turn.";
42114
+ rail.end(`${s.green}${s.bold}Ready${s.reset} ${s.gray}in ${secs2}s.${s.reset}`, next);
42115
+ }
42116
+ function sayChanged(ui, before) {
42117
+ const changed = changedSince(before, snapshotInitFiles());
42118
+ if (changed.length === 0) return;
42119
+ ui.rail.line(`Changed: ${changed.join(", ")}. Details: verity doctor`, "muted");
42120
+ }
42121
+
42122
+ // src/commands/migrate.ts
42123
+ var import_node_fs76 = require("node:fs");
42124
+ var import_node_path64 = require("node:path");
42125
+ var import_node_child_process22 = require("node:child_process");
42126
+ var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
42127
+ function defaultNpmRemover(pkg) {
42128
+ (0, import_node_child_process22.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
42129
+ }
42130
+ function git2(cwd, args2) {
42131
+ (0, import_node_child_process22.execFileSync)("git", hardenedGitArgv(args2), { cwd, stdio: "pipe", env: hardenedGitEnv() });
42132
+ }
42133
+ function isGitTracked(cwd, relPath) {
42134
+ try {
42135
+ git2(cwd, ["ls-files", "--error-unmatch", "--", relPath]);
42136
+ return true;
42137
+ } catch {
42138
+ return false;
42139
+ }
42140
+ }
42141
+ function isGitRepo(cwd) {
42142
+ try {
42143
+ git2(cwd, ["rev-parse", "--is-inside-work-tree"]);
42144
+ return true;
42145
+ } catch {
42146
+ return false;
42147
+ }
42148
+ }
42149
+ async function runMigration(opts = {}) {
42150
+ const root = opts.cwd ?? repoRoot();
42151
+ const home = opts.home ?? process.env.HOME ?? "";
42152
+ const npmRemover = opts.npmRemover ?? defaultNpmRemover;
42153
+ const actions = [];
42154
+ const movedProjectDir = migrateProjectDir(root, actions);
42155
+ migrateGlobalCredentials(home, actions);
42156
+ await migrateLegacyHooks(root, actions);
42157
+ await migrateClaudeMd(root, actions);
42158
+ migrateStandardFile(root, actions);
42159
+ await migrateLocalTelemetry(root, actions);
42160
+ removeLegacyPackage(movedProjectDir, npmRemover, actions);
42161
+ return { actions, migrated: actions.length > 0 };
42162
+ }
42163
+ function migrateProjectDir(root, actions) {
42164
+ const gateDir = (0, import_node_path64.join)(root, ".gate");
42165
+ const verityDir = (0, import_node_path64.join)(root, ".verity");
42166
+ if ((0, import_node_fs76.existsSync)(gateDir) && !(0, import_node_fs76.existsSync)(verityDir)) {
42167
+ return migrateProjectDirRename(root, gateDir, verityDir, actions);
42168
+ }
42169
+ if ((0, import_node_fs76.existsSync)(gateDir) && (0, import_node_fs76.existsSync)(verityDir)) {
42170
+ return migrateProjectDirCarry(gateDir, verityDir, actions);
42171
+ }
42172
+ return false;
42173
+ }
42174
+ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
42175
+ let moved = false;
42176
+ if (isGitRepo(root) && isGitTracked(root, ".gate")) {
42177
+ if (hasStagedChanges(root)) {
42178
+ printWarn(
42179
+ "Migrating .gate \u2192 .verity with `git mv` while the index has staged changes \u2014 the rename will be staged alongside them. Review `git status` before committing."
42180
+ );
42181
+ }
42182
+ try {
42183
+ git2(root, ["mv", "--", ".gate", ".verity"]);
42184
+ actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
42185
+ moved = true;
42186
+ } catch {
42187
+ }
42188
+ }
42189
+ if (moved) {
42190
+ if ((0, import_node_fs76.existsSync)(gateDir)) {
42191
+ const carried = carryLegacyContents(gateDir, verityDir);
42192
+ if (carried > 0) {
42193
+ actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
42194
+ }
42195
+ try {
42196
+ (0, import_node_fs76.rmSync)(gateDir, { recursive: true, force: true });
42197
+ } catch {
42198
+ }
42199
+ }
42200
+ return true;
42201
+ }
42202
+ moveDir(gateDir, verityDir);
42203
+ actions.push("Moved .gate/ \u2192 .verity/");
42204
+ return true;
42205
+ }
42206
+ function migrateProjectDirCarry(gateDir, verityDir, actions) {
42207
+ const carried = carryLegacyContents(gateDir, verityDir);
42208
+ if (carried > 0) {
42209
+ actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
42210
+ }
42211
+ try {
42212
+ (0, import_node_fs76.rmSync)(gateDir, { recursive: true, force: true });
42213
+ } catch {
42214
+ }
42215
+ return carried > 0;
42216
+ }
42217
+ function migrateGlobalCredentials(home, actions) {
42218
+ if (!home) return;
42219
+ const gateCreds = (0, import_node_path64.join)(home, ".gate", "credentials");
42220
+ const verityCreds = (0, import_node_path64.join)(home, ".verity", "credentials");
42221
+ if (!(0, import_node_fs76.existsSync)(gateCreds)) return;
42222
+ if (!(0, import_node_fs76.existsSync)(verityCreds)) {
42223
+ (0, import_node_fs76.mkdirSync)((0, import_node_path64.join)(home, ".verity"), { recursive: true });
42224
+ moveFile(gateCreds, verityCreds);
42225
+ actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
42226
+ return;
42227
+ }
42228
+ const added = mergeGlobalCredentials(gateCreds, verityCreds);
42229
+ if (added > 0) {
42230
+ actions.push(
42231
+ `Merged ${added} legacy credential line(s) from ~/.gate/credentials into ~/.verity/credentials`
42232
+ );
42233
+ }
42234
+ }
42235
+ async function migrateLegacyHooks(root, actions) {
42236
+ const settings = await readSettingsAt(root);
42237
+ const settingsBefore = JSON.stringify(settings);
42238
+ const updatedSettings = removeVerityHooks(settings);
42239
+ if (settingsBefore !== JSON.stringify(updatedSettings)) {
42240
+ await writeSettingsAt(root, updatedSettings);
42241
+ actions.push("Stripped legacy gate hooks from .claude/settings.json");
42242
+ }
42243
+ }
42244
+ async function migrateClaudeMd(root, actions) {
42245
+ const claudeMd = (0, import_node_path64.join)(root, "CLAUDE.md");
42246
+ const hadLegacyBlock = (0, import_node_fs76.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
42247
+ if (!hadLegacyBlock) return;
42248
+ try {
42249
+ await ensureClaudeMdPointer(root);
42250
+ actions.push("Rewrote legacy gate-memory block in CLAUDE.md");
42251
+ } catch (err2) {
42252
+ printWarn(`Could not rewrite CLAUDE.md block: ${err2.message}`);
42253
+ }
42254
+ }
42255
+ function migrateStandardFile(root, actions) {
42256
+ const gateMd = (0, import_node_path64.join)(root, "GATE.md");
42257
+ const verityMd = (0, import_node_path64.join)(root, "VERITY.md");
42258
+ if (!(0, import_node_fs76.existsSync)(gateMd) || (0, import_node_fs76.existsSync)(verityMd)) return;
42259
+ let moved = false;
42260
+ if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
42261
+ try {
42262
+ git2(root, ["mv", "--", "GATE.md", "VERITY.md"]);
42263
+ moved = true;
42264
+ } catch {
42265
+ }
42266
+ }
42267
+ if (!moved) moveFile(gateMd, verityMd);
42268
+ const content = readFileSyncSafe(verityMd);
42269
+ const refreshed = content.split("GATE.md").join("VERITY.md");
42270
+ if (refreshed !== content) (0, import_node_fs76.writeFileSync)(verityMd, refreshed);
42271
+ actions.push("Renamed GATE.md \u2192 VERITY.md");
42272
+ }
42273
+ async function migrateLocalTelemetry(root, actions) {
42274
+ const { removed } = await healLocalTelemetry(root);
42275
+ if (removed.length === 0) return;
42276
+ actions.push(
42277
+ `Removed ${removed.length} old Verity telemetry key(s) from ${SETTINGS_LOCAL_FILE2} (Claude Code ignores them there). Run "verity telemetry install" to export from this machine.`
42278
+ );
42279
+ }
42280
+ function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
42281
+ if (!movedProjectDir) return;
42282
+ try {
42283
+ npmRemover(LEGACY_NPM_PACKAGE);
42284
+ actions.push(`Removed legacy global package ${LEGACY_NPM_PACKAGE}`);
42285
+ } catch (err2) {
42286
+ printWarn(
42287
+ `Could not remove ${LEGACY_NPM_PACKAGE} (${err2.message}). Remove it manually: npm rm -g ${LEGACY_NPM_PACKAGE}`
42288
+ );
42289
+ }
42290
+ }
42291
+ function credentialRemoteKey(line2) {
42292
+ const trimmed = line2.trim();
42293
+ if (trimmed === "") return null;
42294
+ const idx = trimmed.indexOf(" token:");
42295
+ return idx === -1 ? trimmed : trimmed.slice(0, idx).trim();
42296
+ }
42297
+ function mergeGlobalCredentials(gateCreds, verityCreds) {
42298
+ const verityContent = readFileSyncSafe(verityCreds);
42299
+ const existingKeys = new Set(
42300
+ verityContent.split("\n").map(credentialRemoteKey).filter((k) => k !== null)
42301
+ );
42302
+ const toAppend = [];
42303
+ for (const line2 of readFileSyncSafe(gateCreds).split("\n")) {
42304
+ const key = credentialRemoteKey(line2);
42305
+ if (key === null || existingKeys.has(key)) continue;
42306
+ existingKeys.add(key);
42307
+ toAppend.push(line2.replace(/\r$/, ""));
42308
+ }
42309
+ if (toAppend.length > 0) {
42310
+ const sep7 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
42311
+ (0, import_node_fs76.writeFileSync)(verityCreds, verityContent + sep7 + toAppend.join("\n") + "\n");
42312
+ }
42313
+ (0, import_node_fs76.rmSync)(gateCreds, { force: true });
42314
+ return toAppend.length;
42315
+ }
42316
+ function readFileSyncSafe(path) {
42317
+ try {
42318
+ return (0, import_node_fs76.readFileSync)(path, "utf-8");
42319
+ } catch {
42320
+ return "";
42321
+ }
42322
+ }
42323
+ function hasStagedChanges(root) {
42324
+ try {
42325
+ git2(root, ["diff", "--cached", "--quiet", ...DIFF_READ_FLAGS]);
42326
+ return false;
42327
+ } catch {
42328
+ return true;
42329
+ }
42330
+ }
42331
+ function moveDir(from, to) {
42332
+ try {
42333
+ (0, import_node_fs76.renameSync)(from, to);
42334
+ } catch (err2) {
42335
+ if (err2.code !== "EXDEV") throw err2;
42336
+ (0, import_node_fs76.cpSync)(from, to, { recursive: true });
42337
+ (0, import_node_fs76.rmSync)(from, { recursive: true, force: true });
42338
+ }
42339
+ }
42340
+ function moveFile(from, to) {
42341
+ try {
42342
+ (0, import_node_fs76.renameSync)(from, to);
42343
+ } catch (err2) {
42344
+ if (err2.code !== "EXDEV") throw err2;
42345
+ (0, import_node_fs76.cpSync)(from, to);
42346
+ (0, import_node_fs76.rmSync)(from, { force: true });
42347
+ }
42348
+ }
42349
+ function carryLegacyContents(gateDir, verityDir) {
42350
+ if (!isRealDir(gateDir) || !isRealDir(verityDir)) return 0;
42351
+ let copied = 0;
42352
+ const walk2 = (relDir) => {
42353
+ let entries;
42354
+ try {
42355
+ entries = (0, import_node_fs76.readdirSync)((0, import_node_path64.join)(gateDir, relDir), { withFileTypes: true });
42356
+ } catch {
42357
+ return;
42358
+ }
42359
+ for (const entry of entries) {
42360
+ const rel = relDir ? (0, import_node_path64.join)(relDir, entry.name) : entry.name;
42361
+ if (entry.isSymbolicLink()) continue;
42362
+ if (entry.isDirectory()) {
42363
+ walk2(rel);
42364
+ continue;
42365
+ }
42366
+ if (!entry.isFile() || !ensureRealDirs(verityDir, relDir)) continue;
42367
+ const opened = openRegularInRoot(gateDir, rel);
42368
+ if (!opened.ok) continue;
42369
+ try {
42370
+ (0, import_node_fs76.writeFileSync)((0, import_node_path64.join)(verityDir, rel), readOpened(opened.fd, opened.size), { flag: "wx", mode: 384 });
42371
+ copied++;
42372
+ } catch {
42373
+ }
42374
+ }
42375
+ };
42376
+ walk2("");
42377
+ return copied;
42378
+ }
42379
+ function isRealDir(path) {
42380
+ try {
42381
+ const st = (0, import_node_fs76.lstatSync)(path);
42382
+ return st.isDirectory() && !st.isSymbolicLink();
42383
+ } catch {
42384
+ return false;
42385
+ }
42386
+ }
42387
+ function ensureRealDirs(root, relDir) {
42388
+ let current = root;
42389
+ for (const segment of relDir.split(/[\\/]/).filter(Boolean)) {
42390
+ current = (0, import_node_path64.join)(current, segment);
42391
+ try {
42392
+ (0, import_node_fs76.mkdirSync)(current);
42393
+ } catch (e) {
42394
+ if (e.code !== "EEXIST") return false;
42395
+ }
42396
+ if (!isRealDir(current)) return false;
42397
+ }
42398
+ return true;
42399
+ }
42400
+ async function needsMigration(root = repoRoot()) {
42401
+ const gateDir = (0, import_node_path64.join)(root, ".gate");
42402
+ const verityDir = (0, import_node_path64.join)(root, ".verity");
42403
+ if ((0, import_node_fs76.existsSync)(gateDir) && !(0, import_node_fs76.existsSync)(verityDir)) return true;
42404
+ if ((0, import_node_fs76.existsSync)(gateDir) && (0, import_node_fs76.existsSync)(verityDir)) {
42405
+ if ((0, import_node_fs76.existsSync)((0, import_node_path64.join)(gateDir, "credentials")) && !(0, import_node_fs76.existsSync)((0, import_node_path64.join)(verityDir, "credentials"))) {
42406
+ return true;
42407
+ }
42408
+ if ((0, import_node_fs76.existsSync)((0, import_node_path64.join)(gateDir, "memory")) && !(0, import_node_fs76.existsSync)((0, import_node_path64.join)(verityDir, "memory"))) {
42409
+ return true;
42410
+ }
42411
+ }
42412
+ const claudeMd = (0, import_node_path64.join)(root, "CLAUDE.md");
42413
+ if ((0, import_node_fs76.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
42414
+ return true;
42415
+ }
42416
+ if ((0, import_node_fs76.existsSync)((0, import_node_path64.join)(root, "GATE.md")) && !(0, import_node_fs76.existsSync)((0, import_node_path64.join)(root, "VERITY.md"))) {
42417
+ return true;
42418
+ }
42419
+ if (await hasLegacyHooksAt(root)) return true;
42420
+ return false;
42421
+ }
42422
+ function registerMigrateCommand(program2) {
42423
+ program2.command("migrate").description("Migrate a legacy GATE.md install to Verity (.gate \u2192 .verity, hooks, CLAUDE.md)").action(async () => {
42424
+ let result;
42425
+ try {
42426
+ result = await runMigration();
42427
+ } catch (err2) {
42428
+ printError(`Migration failed: ${err2.message}`);
42429
+ process.exitCode = 1;
42430
+ return;
42431
+ }
42432
+ if (!result.migrated) {
42433
+ printInfo("Nothing to migrate \u2014 this project is already on Verity.");
42434
+ return;
42435
+ }
42436
+ printInfo("Migration complete:");
42437
+ for (const a of result.actions) printInfo(` ${a} \u2713`);
42438
+ });
42439
+ }
42440
+
41531
42441
  // src/commands/init/sign-in.ts
41532
- var import_node_child_process21 = require("node:child_process");
42442
+ var import_node_child_process23 = require("node:child_process");
41533
42443
  async function classifyExistingLogin(serviceUrl, remote, opts) {
41534
42444
  const existing = await resolveToken(opts.token);
41535
42445
  if (!existing.ok) return { kind: "none" };
@@ -41554,7 +42464,7 @@ async function classifyExistingLogin(serviceUrl, remote, opts) {
41554
42464
  }
41555
42465
  function gitRemote() {
41556
42466
  try {
41557
- return (0, import_node_child_process21.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
42467
+ return (0, import_node_child_process23.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
41558
42468
  } catch {
41559
42469
  return "";
41560
42470
  }
@@ -41685,299 +42595,54 @@ async function signInStep(ui, resolution, opts) {
41685
42595
  return end("new");
41686
42596
  }
41687
42597
 
41688
- // src/commands/init/standard.ts
41689
- var import_node_fs73 = require("node:fs");
41690
-
41691
- // src/commands/init/rules.ts
41692
- async function projectLabel(token, serviceUrl, verbose) {
42598
+ // src/commands/init/phases.ts
42599
+ async function moveGateInstall(ui) {
42600
+ if (!await needsMigration()) return;
41693
42601
  try {
41694
- const who = await whoami(token, serviceUrl, verbose);
41695
- return who.ok ? who.data.project_id : null;
41696
- } catch {
41697
- return null;
41698
- }
41699
- }
41700
- function profilePreview(profile) {
41701
- return JSON.stringify(profile, null, 2).split("\n").slice(0, 12).map((l) => terminalText(l, 400)).concat(["\u2026", `The whole profile: ${PROFILE_FILE}`]);
41702
- }
41703
- async function askToSend(ui, profile) {
41704
- const { rail, screen } = ui;
41705
- const lines = consentLines(profile);
41706
- rail.active("Write review rules for this repository?");
41707
- rail.box(lines.slice(1, -1));
41708
- rail.line(lines[lines.length - 1]);
41709
- rail.gap();
41710
- rail.keys([["Enter", "send"], ["v", "view the profile"], ["n", "skip"]]);
41711
- let allowed = ["enter", "v", "n"];
41712
- for (; ; ) {
41713
- const k = await screen.key(allowed, "n", { freshMs: 300 });
41714
- if (k !== "v") {
41715
- rail.collapse();
41716
- return k === "enter";
41717
- }
41718
- rail.gap();
41719
- for (const l of profilePreview(profile)) rail.line(l);
41720
- rail.gap();
41721
- rail.keys([["Enter", "send"], ["n", "skip"]]);
41722
- allowed = ["enter", "n"];
41723
- }
41724
- }
41725
- async function generate(ui, globals, profile, replaces) {
41726
- const { rail, screen } = ui;
41727
- const s = screen.s;
41728
- const token = await resolveToken(globals.token);
41729
- const url = await resolveServiceUrl(globals.serviceUrl);
41730
- if (!token.ok || !url.ok) return { status: "fallback" };
41731
- const commits = profile.history.commits.length;
41732
- const fixes = profile.history.commits.filter((c) => c.fix).length;
41733
- const reverts = profile.history.commits.filter((c) => /^revert\b/i.test(c.subject)).length;
41734
- rail.active("Writing review rules");
41735
- await rail.counted(([c, f, r]) => `${s.green}\u2713${s.reset} Read ${c} commits ${s.gray}\xB7${s.reset} ${f} fixes ${s.gray}\xB7${s.reset} ${r} reverts`, [commits, fixes, reverts]);
41736
- if (profile.docs.length) rail.ticked(`Read ${profile.docs.map((d) => terminalText(d.name, 120)).join(" and ")}`);
41737
- if (ui.term.screen && !ui.term.motion) rail.line("Writing rules for this repository\u2026");
41738
- const started = Date.now();
41739
- const base = buildStandard({ detected: detectProject() }, loadCatalog());
41740
- const outcome = await screen.spin(`${s.gray}\u2502${s.reset} `, () => "Writing rules for this repository", generateStandard({
41741
- serviceUrl: url.data,
41742
- token: token.data.token,
41743
- profile,
41744
- base,
41745
- projectId: await projectLabel(token.data.token, url.data, globals.verbose),
41746
- verbose: globals.verbose,
41747
- replaces
41748
- }));
41749
- rail.collapse();
41750
- if (outcome.status === "exists") return { status: "exists" };
41751
- if (outcome.status === "disabled") {
41752
- rail.line("Project rules are not switched on for this project yet. The built-in rules apply.");
41753
- rail.gap();
41754
- return { status: "fallback" };
41755
- }
41756
- if (outcome.status === "unsupported") {
41757
- rail.line(`This Verity service (${terminalText(url.data, 200)}) does not write project rules yet: it is older than this CLI. The built-in rules apply.`);
41758
- rail.gap();
41759
- return { status: "fallback" };
41760
- }
41761
- if (outcome.status === "failed") {
41762
- rail.warn("The rules could not be written this time");
41763
- rail.line(terminalText(outcome.reason, 300));
41764
- rail.line("The built-in rules apply. Try again later: verity standard generate");
41765
- rail.gap();
41766
- return { status: "fallback" };
41767
- }
41768
- const secs2 = Math.max(1, Math.round((Date.now() - started) / 1e3));
41769
- const docs2 = profile.docs.length;
41770
- if (outcome.rules.length === 0) {
41771
- rail.warn("No project rules this time");
41772
- rail.line("The proposed rules were too generic, or not safe to use. The built-in rules apply.");
41773
- } else {
41774
- const n2 = outcome.rules.length;
41775
- const from = [`${commits} ${commits === 1 ? "commit" : "commits"}`, ...docs2 ? [`${docs2} ${docs2 === 1 ? "doc" : "docs"}`] : []].join(" and ");
41776
- rail.done(`${n2} ${n2 === 1 ? "rule" : "rules"} for this repository ${s.gray}\xB7 from ${from} \xB7 ${secs2}s${s.reset}`);
41777
- await rail.rules(outcome.rules);
41778
- if (outcome.rejected > 0) rail.line(`${outcome.rejected} proposed ${outcome.rejected === 1 ? "rule was" : "rules were"} left out: too generic, or not safe to use.`);
41779
- }
41780
- rail.gap();
41781
- rail.done(`Standard v${outcome.version} saved`);
41782
- rail.line(`Your team gets the same rules. Edit them in ${STANDARD_FILE}`);
41783
- if (outcome.warning) rail.line(terminalText(outcome.warning, 600), "warn");
41784
- return { status: "created", version: outcome.version };
41785
- }
41786
-
41787
- // src/commands/init/standard.ts
41788
- async function lookUpTeamStandard(opts) {
41789
- if (!opts.adopt) return { status: "skipped", why: "no-adopt" };
41790
- const token = await resolveToken(opts.globals.token);
41791
- const url = await resolveServiceUrl(opts.globals.serviceUrl);
41792
- if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
41793
- const found = await fetchRemoteSetup({ token: token.data.token, serviceUrl: url.data, verbose: opts.globals.verbose });
41794
- if (found.status === "unavailable") return { status: "unavailable", reason: terminalText(found.reason, 300) };
41795
- if (found.status === "none") return { status: "none" };
41796
- if (opts.previous?.declinedStandardVersion === found.standard.version) {
41797
- return { status: "declined-before", version: found.standard.version };
42602
+ const result = await runMigration();
42603
+ ui.rail.done("Moved a GATE.md install to Verity");
42604
+ for (const a of result.actions) ui.rail.line(a);
42605
+ } catch (err2) {
42606
+ ui.rail.warn(`The GATE.md migration failed: ${terminalText(err2.message, 200)}`);
41798
42607
  }
41799
- return { status: "found", found, version: found.standard.version };
42608
+ ui.rail.settle();
41800
42609
  }
41801
- async function writeTeamStandard(team, opts) {
41802
- const token = await resolveToken(opts.globals.token);
41803
- const url = await resolveServiceUrl(opts.globals.serviceUrl);
41804
- if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
41805
- const found = team.found;
42610
+ async function signInSafely(ui, globals) {
41806
42611
  try {
41807
- const outcome = await adoptRemoteSetup(found, {
41808
- serviceUrl: url.data,
41809
- projectId: await projectLabel(token.data.token, url.data, opts.globals.verbose)
41810
- });
41811
- if (opts.previous?.declinedStandardVersion != null) {
41812
- await writeSetupState({ declinedStandardVersion: void 0 }).catch(() => {
41813
- });
41814
- }
41815
- return { status: "adopted", version: found.standard.version, summary: describeRemote(found), notes: outcome.notes };
42612
+ return await signInStep(ui, await resolveServiceUrlForAuth(globals.serviceUrl), { token: globals.token, verbose: globals.verbose, repo: repoLabel() });
41816
42613
  } catch (err2) {
41817
- return { status: "write-failed", reason: terminalText(err2.message, 300) };
42614
+ ui.rail.collapse();
42615
+ ui.rail.warn(`Sign-in did not complete: ${terminalText(err2.message, 200)}`);
42616
+ ui.rail.gap();
42617
+ ui.rail.settle();
42618
+ return "failed";
41818
42619
  }
41819
42620
  }
41820
- async function adoptTeamStandard(opts) {
41821
- const team = await lookUpTeamStandard(opts);
41822
- return team.status === "found" ? writeTeamStandard(team, opts) : team;
42621
+ function traceTelemetry(trace, telemetry, answer) {
42622
+ if (telemetry === "enabled") trace.mark("telemetry", "ok", answer === "already-on" ? "already" : "new");
42623
+ else if (telemetry === "declined") trace.mark("telemetry", "skipped", "declined");
42624
+ else if (telemetry === "deferred") trace.mark("telemetry", "failed", "none");
42625
+ else trace.mark("telemetry", "skipped", "unattended");
41823
42626
  }
41824
- async function askTeamChoice(ui, team) {
41825
- const { rail, screen } = ui;
41826
- rail.active("Your team already has a Standard for this repository");
41827
- rail.line(describeRemote(team.found));
41828
- rail.line(`Use it to review with the same rules as everyone else. A new one becomes v${team.version + 1} for the whole team.`);
41829
- rail.gap();
41830
- rail.keys([["Enter", "use it"], ["n", "create a new one"]]);
41831
- const k = await screen.key(["enter", "n"], "enter", { freshMs: 300 });
41832
- rail.collapse();
41833
- return k === "n" ? "new" : "use";
41834
- }
41835
- function sayAdopted(ui, team) {
41836
- ui.rail.done(`Using your team's Standard v${team.version}`);
41837
- ui.rail.line(terminalText(team.summary, 300));
41838
- for (const note of team.notes) ui.rail.line(terminalText(note, 300), "warn");
41839
- }
41840
- async function deriveFromCatalogue(ui, globals) {
41841
- const url = await resolveServiceUrl(globals.serviceUrl);
42627
+ async function recordAnswers(ui, momentIds, telemetry) {
41842
42628
  try {
41843
- const result = await runSynthesis({ serviceUrl: url.ok ? url.data : void 0 });
41844
- if ("refused" in result) {
41845
- ui.rail.warn("No Standard was written");
41846
- ui.rail.line(result.refused);
41847
- return { ok: false, version: null };
41848
- }
41849
- const pushed = await pushStandard(globals);
41850
- if (pushed.standardVersion !== null) {
41851
- await correctVerityMdVersion({
41852
- standard: result.standard,
41853
- version: pushed.standardVersion,
41854
- tools: result.tools,
41855
- serviceUrl: url.ok ? url.data : void 0
41856
- });
41857
- ui.rail.done(`Standard v${pushed.standardVersion} saved`);
41858
- ui.rail.line(`Your team gets the same rules. Edit them in ${STANDARD_FILE}`);
41859
- } else {
41860
- ui.rail.done("Standard saved on this machine");
41861
- }
41862
- for (const line2 of pushed.lines) {
41863
- if (line2.startsWith("Standard pushed")) continue;
41864
- if (line2.startsWith("Not signed in")) ui.rail.line(`Sign in to share it with your team: verity login, then verity standard push. Edit it in ${STANDARD_FILE}`);
41865
- else ui.rail.line(line2, "warn");
41866
- }
41867
- for (const note of result.notes) ui.rail.line(note, "warn");
41868
- return { ok: true, version: pushed.standardVersion };
41869
- } catch (err2) {
41870
- ui.rail.warn("No Standard was written");
41871
- ui.rail.line(terminalText(err2.message, 300));
41872
- return { ok: false, version: null };
41873
- }
41874
- }
41875
- async function standardStep(ui, ctx) {
41876
- const { rail } = ui;
41877
- const s = ui.screen.s;
41878
- const notAsked = { outcome: "skipped", detail: "none" };
41879
- const end = (r) => {
41880
- rail.gap();
41881
- rail.settle();
41882
- return r;
41883
- };
41884
- const derived = async (profile2) => {
41885
- const d = await deriveFromCatalogue(ui, ctx.globals);
41886
- return end({ source: d.ok ? "derived" : "none", version: d.version, profile: profile2 });
41887
- };
41888
- if ((0, import_node_fs73.existsSync)(projectPath(STANDARD_FILE))) {
41889
- const sync = await pushLocalStandardIfMissing(ctx.globals);
41890
- rail.done("Kept your Standard");
41891
- rail.line(`${STANDARD_FILE} is already here, so this run leaves it as it is.`);
41892
- if (sync.status === "pushed") {
41893
- rail.line(sync.version !== null ? `It was only on this machine. It is shared now as v${sync.version}.` : "It is only on this machine:", sync.version !== null ? "muted" : "warn");
41894
- for (const line2 of sync.lines) if (!line2.startsWith("Standard pushed")) rail.line(line2, "warn");
41895
- }
41896
- return end({ source: "kept", version: sync.status === "pushed" ? sync.version : null, profile: notAsked });
41897
- }
41898
- const team = await lookUpTeamStandard(ctx);
41899
- let replaces;
41900
- if (team.status === "found") {
41901
- const choice = ctx.standard ?? (ui.asking ? await askTeamChoice(ui, team) : "use");
41902
- if (choice === "use") {
41903
- const adopted = await writeTeamStandard(team, ctx);
41904
- if (adopted.status === "adopted") {
41905
- sayAdopted(ui, adopted);
41906
- return end({ source: "adopted", version: adopted.version, profile: notAsked });
41907
- }
41908
- rail.warn("Could not write the team's Standard here");
41909
- if (adopted.status === "write-failed") rail.line(adopted.reason);
41910
- return derived(notAsked);
41911
- }
41912
- await writeSetupState({ declinedStandardVersion: team.version }).catch(() => {
42629
+ await writeSetupState({
42630
+ moments: momentIds,
42631
+ ...telemetry ? { telemetry } : {},
42632
+ init: { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.35.0-experimental.3a13a5e" : "dev" }
41913
42633
  });
41914
- rail.done(`Creating a new Standard ${s.gray}\xB7 it becomes v${team.version + 1} for the team${ctx.standard ? " (--standard new)" : ""}${s.reset}`);
41915
- replaces = team.version;
41916
- } else if (team.status === "declined-before") {
41917
- rail.line(`This project turned down the team's Standard v${team.version} before, so this run builds its own.`);
41918
- return derived(notAsked);
41919
- }
41920
- if (team.status === "unavailable") {
41921
- rail.warn("Could not ask the Verity service for this repository's Standard");
41922
- rail.line(team.reason);
41923
- return derived(notAsked);
41924
- }
41925
- if (team.status === "skipped") return derived(notAsked);
41926
- const profile = buildRepoProfile();
41927
- try {
41928
- writeRepoFile(PROFILE_FILE, JSON.stringify(profile, null, 2) + "\n");
41929
42634
  } catch (err2) {
41930
- rail.warn(`Could not write ${PROFILE_FILE} for you to read, so nothing was sent`);
41931
- rail.line(terminalText(err2.message, 200));
41932
- return derived({ outcome: "failed", detail: "error" });
42635
+ ui.rail.line(`Could not record the setup answers: ${terminalText(err2.message, 200)}`, "warn");
41933
42636
  }
41934
- let detail;
41935
- let send;
41936
- if (ctx.profile) {
41937
- send = ctx.profile === "send";
41938
- detail = "given";
41939
- } else if (ui.asking) {
41940
- send = await askToSend(ui, profile);
41941
- detail = send ? "asked" : "declined";
41942
- } else {
41943
- send = false;
41944
- detail = "unattended";
41945
- }
41946
- if (!send) {
41947
- rail.warn("Profile not sent");
41948
- rail.line(detail === "unattended" ? "Nobody was asked, so nothing was sent. Your Standard has the built-in rules only." : "Your Standard has the built-in rules only.");
41949
- rail.line(`Add project rules later: verity standard generate (read ${PROFILE_FILE} first)`);
41950
- rail.gap();
41951
- return derived({ outcome: "skipped", detail });
41952
- }
41953
- if (detail === "given") rail.done(`Profile sent ${s.gray}\xB7 no source code (--profile send)${s.reset}`);
41954
- else rail.done(`Profile sent ${s.gray}\xB7 no source code${s.reset}`);
41955
- rail.gap();
41956
- rail.settle();
41957
- const generated = await generate(ui, ctx.globals, profile, replaces);
41958
- if (generated.status === "created") {
41959
- return end({ source: "generated", version: generated.version, profile: { outcome: "ok", detail } });
41960
- }
41961
- if (generated.status === "exists") {
41962
- const again = await adoptTeamStandard(ctx);
41963
- if (again.status === "adopted") {
41964
- sayAdopted(ui, again);
41965
- return end({ source: "adopted", version: again.version, profile: { outcome: "ok", detail } });
41966
- }
41967
- }
41968
- return derived({ outcome: "ok", detail });
41969
42637
  }
41970
42638
 
41971
42639
  // src/commands/init/index.ts
41972
- async function runInit(program2, opts) {
41973
- const started = Date.now();
41974
- const globals = program2.opts();
41975
- const defaultsOnly = (opts.yes ?? false) || !interactive();
42640
+ function parseInitFlags(opts) {
41976
42641
  let givenMoments;
41977
42642
  if (opts.moments != null) {
41978
42643
  const parsed = parseMomentSelection(String(opts.moments));
41979
42644
  if ("error" in parsed) {
41980
- printError(parsed.error);
42645
+ printError(terminalText(parsed.error, 300));
41981
42646
  process.exit(1);
41982
42647
  }
41983
42648
  givenMoments = parsed.moments;
@@ -41986,7 +42651,7 @@ async function runInit(program2, opts) {
41986
42651
  if (opts.telemetry != null) {
41987
42652
  const parsed = parseTelemetryAnswer(String(opts.telemetry));
41988
42653
  if (parsed === null) {
41989
- printError(`--telemetry: unrecognised "${opts.telemetry}". Valid: on, off.`);
42654
+ printError(`--telemetry: unrecognised "${terminalText(String(opts.telemetry), 60)}". Valid: on, off.`);
41990
42655
  process.exit(1);
41991
42656
  }
41992
42657
  givenTelemetry = parsed;
@@ -41995,7 +42660,7 @@ async function runInit(program2, opts) {
41995
42660
  if (opts.profile != null) {
41996
42661
  const v = String(opts.profile).trim().toLowerCase();
41997
42662
  if (v !== "send" && v !== "skip") {
41998
- printError(`--profile: unrecognised "${opts.profile}". Valid: send, skip.`);
42663
+ printError(`--profile: unrecognised "${terminalText(String(opts.profile), 60)}". Valid: send, skip.`);
41999
42664
  process.exit(1);
42000
42665
  }
42001
42666
  profileAnswer = v;
@@ -42004,16 +42669,24 @@ async function runInit(program2, opts) {
42004
42669
  if (opts.standard != null) {
42005
42670
  const v = String(opts.standard).trim().toLowerCase();
42006
42671
  if (v !== "use" && v !== "new") {
42007
- printError(`--standard: unrecognised "${opts.standard}". Valid: use, new.`);
42672
+ printError(`--standard: unrecognised "${terminalText(String(opts.standard), 60)}". Valid: use, new.`);
42008
42673
  process.exit(1);
42009
42674
  }
42010
42675
  standardAnswer = v;
42011
42676
  }
42677
+ return { givenMoments, givenTelemetry, profileAnswer, standardAnswer };
42678
+ }
42679
+ async function runInit(program2, opts) {
42680
+ const started = Date.now();
42681
+ const globals = program2.opts();
42682
+ const defaultsOnly = (opts.yes ?? false) || !interactive();
42683
+ const { givenMoments, givenTelemetry, profileAnswer, standardAnswer } = parseInitFlags(opts);
42012
42684
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
42013
- if (!projectMarkers.some((m) => (0, import_node_fs74.existsSync)(m))) {
42685
+ if (!projectMarkers.some((m) => (0, import_node_fs77.existsSync)(m))) {
42014
42686
  printError('No project detected in the current directory. Run "verity init" from your project root.');
42015
42687
  process.exit(1);
42016
42688
  }
42689
+ const before = snapshotInitFiles();
42017
42690
  const stale = clearStalePluginMarker();
42018
42691
  const pluginMode = opts.plugin === false ? false : opts.pluginMode ?? pluginActiveHere();
42019
42692
  const install = pluginMode ? activePluginInstall() : null;
@@ -42026,16 +42699,7 @@ async function runInit(program2, opts) {
42026
42699
  await wordmark(ui.screen, { reveal: firstInitOnMachine() });
42027
42700
  rail.open(repoLabel());
42028
42701
  if (opts.intensity != null) rail.line("--intensity is ignored: static analysis is Trivy only.", "warn");
42029
- if (await needsMigration()) {
42030
- try {
42031
- const result = await runMigration();
42032
- rail.done("Moved a GATE.md install to Verity");
42033
- for (const a of result.actions) rail.line(a);
42034
- } catch (err2) {
42035
- rail.warn(`The GATE.md migration failed: ${terminalText(err2.message, 200)}`);
42036
- }
42037
- rail.settle();
42038
- }
42702
+ await moveGateInstall(ui);
42039
42703
  if (!await prerequisitesStep(ui)) {
42040
42704
  rail.end("Stopped: a required tool is missing.", "Install it, then run verity init again.");
42041
42705
  trace.mark("prerequisites", "failed", "blocked");
@@ -42046,10 +42710,10 @@ async function runInit(program2, opts) {
42046
42710
  trace.mark("prerequisites", "ok");
42047
42711
  await scaffoldStep(ui, { untrackMemory: opts.untrackMemory === true, untrackState: opts.untrackState === true });
42048
42712
  trace.mark("scaffold", "ok");
42049
- await (0, import_promises19.mkdir)((0, import_node_path60.join)(process.env.HOME ?? "", ".verity"), { recursive: true });
42713
+ await (0, import_promises19.mkdir)((0, import_node_path65.join)(process.env.HOME ?? "", ".verity"), { recursive: true });
42050
42714
  await repoStep(ui, detectProject());
42051
42715
  const previous = await readSetupState();
42052
- const setUpBefore = (0, import_node_fs74.existsSync)(projectPath(STANDARD_FILE));
42716
+ const setUpBefore = (0, import_node_fs77.existsSync)(projectPath(STANDARD_FILE));
42053
42717
  const answers = await askSetupQuestions(defaultsOnly, previous, { moments: givenMoments, telemetry: givenTelemetry });
42054
42718
  trace.mark("questions", "ok", givenMoments || givenTelemetry !== void 0 || profileAnswer || standardAnswer ? "asked" : "defaults");
42055
42719
  const plan = planMoments(answers.moments, { given: givenMoments !== void 0, defaultsOnly });
@@ -42065,66 +42729,40 @@ async function runInit(program2, opts) {
42065
42729
  force: opts.force ?? false
42066
42730
  });
42067
42731
  trace.mark("hooks", "ok", pluginMode ? "plugin" : "project");
42068
- let signIn;
42069
- try {
42070
- signIn = await signInStep(ui, await resolveServiceUrlForAuth(globals.serviceUrl), { token: globals.token, verbose: globals.verbose, repo: repoLabel() });
42071
- } catch (err2) {
42072
- rail.collapse();
42073
- rail.warn(`Sign-in did not complete: ${terminalText(err2.message, 200)}`);
42074
- rail.gap();
42075
- rail.settle();
42076
- signIn = "failed";
42077
- }
42732
+ const signIn = await signInSafely(ui, globals);
42078
42733
  trace.mark("sign_in", ...signInDetail(signIn));
42079
42734
  const signedIn = signIn === "already" || signIn === "new" || signIn === "unreachable";
42080
- const telemetry = await telemetryStep(ui, answers.telemetry, answers.telemetry === "not-asked" && previous?.telemetry === "deferred", globals);
42081
- if (telemetry === "enabled") trace.mark("telemetry", "ok", answers.telemetry === "already-on" ? "already" : "new");
42082
- else if (telemetry === "declined") trace.mark("telemetry", "skipped", "declined");
42083
- else if (telemetry === "deferred") trace.mark("telemetry", "failed", "none");
42084
- else trace.mark("telemetry", "skipped", "unattended");
42735
+ const telemetry = await telemetryStep(ui, answers.telemetry, answers.telemetry === "not-asked" && previous?.telemetry === "deferred", globals, previous?.telemetry);
42736
+ traceTelemetry(trace, telemetry, answers.telemetry);
42085
42737
  const standard = await standardStep(ui, { globals, previous, adopt: opts.adopt !== false, profile: profileAnswer, standard: standardAnswer });
42086
42738
  trace.mark("profile", standard.profile.outcome, standard.profile.detail);
42087
42739
  const haveStandard = standard.source !== "none";
42088
42740
  trace.mark("standard", haveStandard ? "ok" : "failed", standard.source);
42089
42741
  const formalNew = formalDefault(previous, { setUpBefore });
42090
42742
  backgroundStep(ui, { cli: process.argv[1], formalNew });
42091
- try {
42092
- await writeSetupState({
42093
- // The effective answer: the next run starts from this record.
42094
- moments: plan.momentIds,
42095
- ...telemetry ? { telemetry } : {},
42096
- init: { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.35.0-experimental.3211f79" : "dev" }
42097
- });
42098
- } catch (err2) {
42099
- rail.line(`Could not record the setup answers: ${terminalText(err2.message, 200)}`, "warn");
42100
- }
42743
+ await recordAnswers(ui, plan.momentIds, telemetry);
42101
42744
  trace.mark("completed", haveStandard ? "ok" : "failed", haveStandard ? void 0 : "none");
42102
42745
  await trace.settle();
42103
- const secs2 = Math.max(1, Math.round((Date.now() - started) / 1e3));
42104
- const s = ui.screen.s;
42105
- if (!haveStandard) {
42106
- rail.end(`${s.yellow}Not finished:${s.reset} this project has no Standard, so the gate cannot review.`, "Run verity init again, or /verity-setup in Claude Code.");
42107
- } else {
42108
- if (pluginMode) {
42109
- const version = activePluginVersion();
42110
- if (version) await writeSetupState({ pluginSetup: { version, completed_at: (/* @__PURE__ */ new Date()).toISOString() }, wiring: void 0 });
42111
- } else if (opts.plugin === false) {
42112
- await writeSetupState({ wiring: "project" });
42113
- }
42114
- const next = plan.momentIds.length === 0 ? "Verity reviews nothing until you turn a moment on: verity config moments turn,commit,push" : pluginMode ? signedIn ? "Verity reviews from your next turn." : "The security scan runs from your next turn. Sign in for AI review: verity login" : signedIn ? "Start a new Claude Code session. Verity reviews from its first turn." : "Start a new Claude Code session. The security scan runs from the first turn.";
42115
- rail.end(`${s.green}${s.bold}Ready${s.reset} ${s.gray}in ${secs2}s.${s.reset}`, next);
42116
- }
42746
+ await finishRun(ui, {
42747
+ started,
42748
+ before,
42749
+ haveStandard,
42750
+ pluginMode,
42751
+ noPlugin: opts.plugin === false,
42752
+ signedIn,
42753
+ momentIds: plan.momentIds
42754
+ });
42117
42755
  markInitSeen();
42118
42756
  } finally {
42119
42757
  ui.screen.release();
42120
42758
  }
42121
42759
  }
42122
42760
  function registerInitCommand(program2) {
42123
- program2.command("init").alias("setup").description("Set up Verity in this project: two questions (sign in, and send a repo profile); every other choice is a stated default").option("--force", "Reinstall the skills even when they are up to date (hooks are always reconciled)").option("-y, --yes", "Ask nothing: skip sign-in unless already signed in, and send the profile only with --profile send").option("--profile <send|skip>", "Answer the repo-profile question without asking (the plugin asks in the session)").option("--standard <use|new>", "When the team already has a Standard: use it, or create a new one (the plugin asks in the session; unattended runs use it)").option("--no-setup", "Ignored: init no longer hands off to /verity-setup (kept for old scripts)").option("--plugin-mode", "The Claude Code plugin owns the skills and hooks: install neither, and remove any this project already has").option("--no-plugin", "Ignore any Claude Code plugin here and wire this project's own skills and hooks").option("--no-adopt", "Don't use this repository's Standard from the service; build a new one").option("--moments <list>", 'Set when Verity reviews without the default: stop,pre-commit,pre-push \u2014 or "none"').option("--intensity <level>", "Ignored: static analysis is Trivy only (kept so older plugin skills keep working)").option("--telemetry <on|off>", "Turn cost & usage telemetry on or off (init does not ask)").option("--untrack-memory", "Stop tracking the knowledge graph in git (init does not ask)").option("--untrack-state", "Untrack committed Verity state (init does not ask)").action((opts) => runInit(program2, opts));
42761
+ program2.command("init").alias("setup").description("Set up Verity in this project: sign in, telemetry, and the Standard; every other choice is a stated default").option("--force", "Reinstall the skills even when they are up to date (hooks are always reconciled)").option("-y, --yes", "Ask nothing: skip sign-in unless already signed in, and send the profile only with --profile send").option("--profile <send|skip>", "Answer the repo-profile question without asking (the plugin asks in the session)").option("--standard <use|new>", "When the team already has a Standard: use it, or create a new one (the plugin asks in the session; unattended runs use it)").option("--no-setup", "Ignored: init no longer hands off to /verity-setup (kept for old scripts)").option("--plugin-mode", "The Claude Code plugin owns the skills and hooks: install neither, and remove any this project already has").option("--no-plugin", "Ignore any Claude Code plugin here and wire this project's own skills and hooks").option("--no-adopt", "Don't use this repository's Standard from the service; build a new one").option("--moments <list>", 'Set when Verity reviews without the default: stop,pre-commit,pre-push \u2014 or "none"').option("--intensity <level>", "Ignored: static analysis is Trivy only (kept so older plugin skills keep working)").option("--telemetry <on|off>", "Answer the telemetry question without asking: send cost & usage from this machine, or not").option("--untrack-memory", "Stop tracking the knowledge graph in git (init does not ask)").option("--untrack-state", "Untrack committed Verity state without asking (a terminal run asks when it finds some)").action((opts) => runInit(program2, opts));
42124
42762
  }
42125
42763
 
42126
42764
  // src/commands/doctor.ts
42127
- var import_node_fs75 = require("node:fs");
42765
+ var import_node_fs78 = require("node:fs");
42128
42766
  async function buildReport() {
42129
42767
  const prereqs = await checkPrereqs({ install: false });
42130
42768
  const state = await readSetupState();
@@ -42133,8 +42771,8 @@ async function buildReport() {
42133
42771
  const provenance = cliProvenance();
42134
42772
  const telemetry = await checkTelemetry();
42135
42773
  const artifacts = {
42136
- standard: (0, import_node_fs75.existsSync)(projectPath(STANDARD_FILE)),
42137
- verityMd: (0, import_node_fs75.existsSync)(projectPath(VERITY_MD_FILE))
42774
+ standard: (0, import_node_fs78.existsSync)(projectPath(STANDARD_FILE)),
42775
+ verityMd: (0, import_node_fs78.existsSync)(projectPath(VERITY_MD_FILE))
42138
42776
  };
42139
42777
  const next = [];
42140
42778
  for (const c of prereqs.checks) {
@@ -42173,6 +42811,11 @@ async function buildReport() {
42173
42811
  if (state?.telemetry === "deferred") {
42174
42812
  next.push('Telemetry was requested but needs a token \u2014 run "verity login", then "verity telemetry install".');
42175
42813
  }
42814
+ if (telemetry.staleLocalKeys.length > 0) {
42815
+ next.push(
42816
+ `.claude/settings.local.json still holds ${telemetry.staleLocalKeys.length} old Verity telemetry key(s). Claude Code 2.1.282 and later ignores them there, so nothing is exported from them. Run "verity telemetry install" to export from this machine instead (it removes them), or "verity telemetry uninstall" to turn telemetry off and remove them.`
42817
+ );
42818
+ }
42176
42819
  const memory2 = {
42177
42820
  posture: memoryPosture(),
42178
42821
  trackedFiles: committedMemoryFiles().length,
@@ -42220,7 +42863,7 @@ async function buildReport() {
42220
42863
  noAnalysisMoment,
42221
42864
  versions
42222
42865
  },
42223
- telemetry: { enabled: telemetry.enabled, endpoint: telemetry.endpoint },
42866
+ telemetry: { enabled: telemetry.enabled, endpoint: telemetry.endpoint, staleLocalKeys: telemetry.staleLocalKeys },
42224
42867
  artifacts,
42225
42868
  memory: memory2,
42226
42869
  trackedState,
@@ -42243,6 +42886,7 @@ function registerDoctorCommand(program2) {
42243
42886
  for (const c of report.prerequisites) {
42244
42887
  if (c.status === "ok") printInfo(` ${c.label} ${c.detail} \u2713`);
42245
42888
  else printWarn(` ${c.label}: ${c.detail}${c.remedy ? ` \u2014 ${c.remedy}` : ""}`);
42889
+ if (c.unused) printInfo(` ${c.unused}: not used (Verity runs its bundled copy)`);
42246
42890
  }
42247
42891
  printInfo("Setup:");
42248
42892
  printInfo(` verity init: ${report.phases.init.done ? `done ${report.phases.init.completed_at ?? ""}`.trim() : "NOT run here"}`);
@@ -42264,7 +42908,9 @@ function registerDoctorCommand(program2) {
42264
42908
  if (cfg.formal_refused) printWarn(` ${formalRefusalMessage(cfg.formal_refused)}`);
42265
42909
  }
42266
42910
  printInfo(` Infra (intent/baseline/compact/session-end): ${[report.hooks.intent, report.hooks.baseline, report.hooks.compact, report.hooks.sessionEnd].filter(Boolean).length}/4`);
42267
- printInfo(`Telemetry: ${report.telemetry.enabled ? `enabled \u2192 ${report.telemetry.endpoint}` : "disabled"}`);
42911
+ printInfo(
42912
+ `Telemetry: ${report.telemetry.enabled ? `enabled \u2192 ${report.telemetry.endpoint} (every Claude Code session on this machine)` : "disabled"}`
42913
+ );
42268
42914
  if (process.platform === "win32") printWarn(WINDOWS_STATIC_NOTE);
42269
42915
  else printInfo("Static analysis: Trivy (dependencies, malicious packages, secrets)");
42270
42916
  printInfo("Artifacts:");
@@ -42285,8 +42931,8 @@ function registerDoctorCommand(program2) {
42285
42931
  }
42286
42932
 
42287
42933
  // src/commands/uninstall.ts
42288
- var import_node_fs76 = require("node:fs");
42289
- var import_node_path61 = require("node:path");
42934
+ var import_node_fs79 = require("node:fs");
42935
+ var import_node_path66 = require("node:path");
42290
42936
  function registerUninstallCommand(program2) {
42291
42937
  program2.command("uninstall").description("Remove Verity from this project (skills, hooks, .verity/, VERITY.md)").option("--dry-run", "Show what would be removed without doing it").option("--purge-global", "Also remove ~/.verity/ (deletes saved tokens \u2014 reconnect requires re-registration)").option("--keep-verity-md", "Keep the project root VERITY.md file").action(async (opts) => {
42292
42938
  const dryRun = opts.dryRun ?? false;
@@ -42295,11 +42941,11 @@ function registerUninstallCommand(program2) {
42295
42941
  const actions = [];
42296
42942
  const skillsRoot = projectPath(".claude/skills");
42297
42943
  for (const name2 of PROJECT_SKILL_NAMES) {
42298
- const dir = (0, import_node_path61.join)(skillsRoot, name2);
42299
- if ((0, import_node_fs76.existsSync)(dir)) {
42944
+ const dir = (0, import_node_path66.join)(skillsRoot, name2);
42945
+ if ((0, import_node_fs79.existsSync)(dir)) {
42300
42946
  actions.push({
42301
42947
  label: `Remove .claude/skills/${name2}/`,
42302
- apply: () => (0, import_node_fs76.rmSync)(dir, { recursive: true, force: true })
42948
+ apply: () => (0, import_node_fs79.rmSync)(dir, { recursive: true, force: true })
42303
42949
  });
42304
42950
  }
42305
42951
  }
@@ -42313,24 +42959,24 @@ function registerUninstallCommand(program2) {
42313
42959
  });
42314
42960
  }
42315
42961
  const verityDir = projectPath(VERITY_DIR);
42316
- if ((0, import_node_fs76.existsSync)(verityDir)) {
42962
+ if ((0, import_node_fs79.existsSync)(verityDir)) {
42317
42963
  actions.push({
42318
42964
  label: `Remove ${VERITY_DIR}/`,
42319
- apply: () => (0, import_node_fs76.rmSync)(verityDir, { recursive: true, force: true })
42965
+ apply: () => (0, import_node_fs79.rmSync)(verityDir, { recursive: true, force: true })
42320
42966
  });
42321
42967
  }
42322
42968
  if (!keepVerityMd) {
42323
42969
  const verityMd = projectPath(VERITY_MD_FILE);
42324
- if ((0, import_node_fs76.existsSync)(verityMd)) {
42970
+ if ((0, import_node_fs79.existsSync)(verityMd)) {
42325
42971
  actions.push({
42326
42972
  label: `Remove ${VERITY_MD_FILE}`,
42327
- apply: () => (0, import_node_fs76.rmSync)(verityMd, { force: true })
42973
+ apply: () => (0, import_node_fs79.rmSync)(verityMd, { force: true })
42328
42974
  });
42329
42975
  }
42330
42976
  }
42331
42977
  const cleanupEmptyDir = (path) => {
42332
- if ((0, import_node_fs76.existsSync)(path) && (0, import_node_fs76.statSync)(path).isDirectory() && (0, import_node_fs76.readdirSync)(path).length === 0) {
42333
- (0, import_node_fs76.rmdirSync)(path);
42978
+ if ((0, import_node_fs79.existsSync)(path) && (0, import_node_fs79.statSync)(path).isDirectory() && (0, import_node_fs79.readdirSync)(path).length === 0) {
42979
+ (0, import_node_fs79.rmdirSync)(path);
42334
42980
  }
42335
42981
  };
42336
42982
  actions.push({
@@ -42341,11 +42987,11 @@ function registerUninstallCommand(program2) {
42341
42987
  }
42342
42988
  });
42343
42989
  const home = process.env.HOME ?? "";
42344
- const globalVerityDir = (0, import_node_path61.join)(home, ".verity");
42345
- if (purgeGlobal && (0, import_node_fs76.existsSync)(globalVerityDir)) {
42990
+ const globalVerityDir = (0, import_node_path66.join)(home, ".verity");
42991
+ if (purgeGlobal && (0, import_node_fs79.existsSync)(globalVerityDir)) {
42346
42992
  actions.push({
42347
42993
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
42348
- apply: () => (0, import_node_fs76.rmSync)(globalVerityDir, { recursive: true, force: true })
42994
+ apply: () => (0, import_node_fs79.rmSync)(globalVerityDir, { recursive: true, force: true })
42349
42995
  });
42350
42996
  }
42351
42997
  if (actions.length === 0) {
@@ -42564,8 +43210,8 @@ function registerTaskCommands(program2) {
42564
43210
  }
42565
43211
 
42566
43212
  // src/commands/reset.ts
42567
- var import_node_fs77 = require("node:fs");
42568
- var import_node_path62 = require("node:path");
43213
+ var import_node_fs80 = require("node:fs");
43214
+ var import_node_path67 = require("node:path");
42569
43215
  function registerResetCommand(program2) {
42570
43216
  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) => {
42571
43217
  const globals = program2.opts();
@@ -42605,18 +43251,18 @@ function registerResetCommand(program2) {
42605
43251
  } catch {
42606
43252
  }
42607
43253
  }
42608
- const purged = purgeVerityDir((0, import_node_path62.basename)(CACHE_DIR), (name2) => name2.startsWith("pending-"));
43254
+ const purged = purgeVerityDir((0, import_node_path67.basename)(CACHE_DIR), (name2) => name2.startsWith("pending-"));
42609
43255
  if (purged === null) printError(`Did not purge ${CACHE_DIR}: it is not a directory inside ${VERITY_DIR}.`);
42610
43256
  const stateDir2 = projectPath(VERITY_DIR);
42611
43257
  const filesToClear = [
42612
- (0, import_node_path62.basename)(INTENT_FILE),
42613
- (0, import_node_path62.basename)(HASH_FILE),
42614
- (0, import_node_path62.basename)(BASELINE_SHA_FILE),
43258
+ (0, import_node_path67.basename)(INTENT_FILE),
43259
+ (0, import_node_path67.basename)(HASH_FILE),
43260
+ (0, import_node_path67.basename)(BASELINE_SHA_FILE),
42615
43261
  ".iteration-count",
42616
43262
  ".last-analysis"
42617
43263
  ];
42618
43264
  for (const name2 of filesToClear) {
42619
- if (lstatOrNull((0, import_node_path62.join)(stateDir2, name2)) && !replaceInRoot(stateDir2, name2, "")) {
43265
+ if (lstatOrNull((0, import_node_path67.join)(stateDir2, name2)) && !replaceInRoot(stateDir2, name2, "")) {
42620
43266
  printError(`Did not clear ${VERITY_DIR}/${name2}: it is not a regular file inside ${VERITY_DIR}.`);
42621
43267
  }
42622
43268
  }
@@ -42631,29 +43277,29 @@ function registerResetCommand(program2) {
42631
43277
  }
42632
43278
  function lstatOrNull(path) {
42633
43279
  try {
42634
- return (0, import_node_fs77.lstatSync)(path);
43280
+ return (0, import_node_fs80.lstatSync)(path);
42635
43281
  } catch {
42636
43282
  return null;
42637
43283
  }
42638
43284
  }
42639
43285
  function purgeVerityDir(child, select) {
42640
43286
  const verityDir = projectPath(VERITY_DIR);
42641
- const dir = (0, import_node_path62.join)(verityDir, child);
43287
+ const dir = (0, import_node_path67.join)(verityDir, child);
42642
43288
  const parent = lstatOrNull(verityDir);
42643
43289
  const st = lstatOrNull(dir);
42644
43290
  if (!st) return 0;
42645
43291
  if (!parent || parent.isSymbolicLink() || !parent.isDirectory()) return null;
42646
43292
  if (st.isSymbolicLink() || !st.isDirectory()) return null;
42647
43293
  try {
42648
- if ((0, import_node_fs77.realpathSync)(dir) !== (0, import_node_path62.join)((0, import_node_fs77.realpathSync)(verityDir), child)) return null;
43294
+ if ((0, import_node_fs80.realpathSync)(dir) !== (0, import_node_path67.join)((0, import_node_fs80.realpathSync)(verityDir), child)) return null;
42649
43295
  } catch {
42650
43296
  return null;
42651
43297
  }
42652
43298
  let removed = 0;
42653
- for (const entry of (0, import_node_fs77.readdirSync)(dir, { withFileTypes: true })) {
43299
+ for (const entry of (0, import_node_fs80.readdirSync)(dir, { withFileTypes: true })) {
42654
43300
  if (entry.isDirectory() || !select(entry.name)) continue;
42655
43301
  try {
42656
- (0, import_node_fs77.unlinkSync)((0, import_node_path62.join)(dir, entry.name));
43302
+ (0, import_node_fs80.unlinkSync)((0, import_node_path67.join)(dir, entry.name));
42657
43303
  removed++;
42658
43304
  } catch {
42659
43305
  }
@@ -42662,7 +43308,7 @@ function purgeVerityDir(child, select) {
42662
43308
  }
42663
43309
 
42664
43310
  // src/commands/reflect.ts
42665
- var import_node_fs78 = require("node:fs");
43311
+ var import_node_fs81 = require("node:fs");
42666
43312
 
42667
43313
  // src/lib/reflection-globs.ts
42668
43314
  var MAX_GLOBS = 6;
@@ -42700,7 +43346,7 @@ async function writeNodeToDisk(args2) {
42700
43346
  { treePaths: listTrackedFiles(), recordBaseline: false }
42701
43347
  );
42702
43348
  if (written === 0) return false;
42703
- return (0, import_node_fs78.existsSync)(projectPath(`${VERITY_DIR}/memory/${args2.filePath}`));
43349
+ return (0, import_node_fs81.existsSync)(projectPath(`${VERITY_DIR}/memory/${args2.filePath}`));
42704
43350
  } catch {
42705
43351
  return false;
42706
43352
  }
@@ -43137,9 +43783,15 @@ function registerRunCommand(program2) {
43137
43783
  }
43138
43784
 
43139
43785
  // src/commands/telemetry.ts
43140
- var import_node_fs79 = require("node:fs");
43786
+ var import_node_fs82 = require("node:fs");
43787
+ var SCOPE = "every Claude Code session on this machine";
43788
+ function declineHeaders(why) {
43789
+ process.stderr.write(`Verity telemetry is off here: ${why} Nothing is exported from this session.
43790
+ `);
43791
+ process.exitCode = 1;
43792
+ }
43141
43793
  async function recordTelemetryChoice(choice) {
43142
- if (!(0, import_node_fs79.existsSync)(projectPath(SETUP_STATE_FILE))) return;
43794
+ if (!(0, import_node_fs82.existsSync)(projectPath(SETUP_STATE_FILE))) return;
43143
43795
  try {
43144
43796
  if ((await readSetupState())?.telemetry === choice) return;
43145
43797
  await writeSetupState({ telemetry: choice });
@@ -43147,8 +43799,8 @@ async function recordTelemetryChoice(choice) {
43147
43799
  }
43148
43800
  }
43149
43801
  function registerTelemetryCommands(program2) {
43150
- const telemetry = program2.command("telemetry").description("Manage Claude Code OpenTelemetry export to Verity (cost & usage)");
43151
- telemetry.command("install").description("Enable Claude Code telemetry export to Verity (writes .claude/settings.local.json)").action(async () => {
43802
+ const telemetry = program2.command("telemetry").description(`Manage Claude Code OpenTelemetry export to Verity (cost & usage) for ${SCOPE}`);
43803
+ telemetry.command("install").description(`Export cost & usage to Verity from ${SCOPE} (writes your Claude Code user settings)`).action(async () => {
43152
43804
  const globals = program2.opts();
43153
43805
  const tokenResult = await resolveToken(globals.token);
43154
43806
  if (!tokenResult.ok) {
@@ -43160,35 +43812,51 @@ function registerTelemetryCommands(program2) {
43160
43812
  printError(urlResult.error);
43161
43813
  process.exit(1);
43162
43814
  }
43163
- const pin = await checkTokenPin(tokenResult.data.token, urlResult.data);
43164
- if (!pin.attach && pin.reason === "unpinnable") {
43165
- printWarn("Your Verity credential does not record which service issued it, so this endpoint");
43166
- printWarn(" cannot be verified \u2014 a repository could point your telemetry elsewhere.");
43167
- printWarn(' Run "verity login" to re-issue the credential; Verity can check it after that.');
43168
- }
43169
- const result = await installTelemetry(urlResult.data);
43815
+ const result = await installTelemetry(urlResult.data, { token: tokenResult.data.token });
43170
43816
  if (!result.ok) {
43171
43817
  printError(result.error);
43172
43818
  process.exit(1);
43173
43819
  }
43174
43820
  await recordTelemetryChoice("enabled");
43175
43821
  printInfo(`Telemetry enabled \u2192 ${result.data.endpoint}`);
43176
- printInfo(` wrote ${SETTINGS_LOCAL_FILE2} (no token stored \u2014 resolved at runtime via otelHeadersHelper)`);
43822
+ printInfo(` scope: ${SCOPE}`);
43823
+ printInfo(` wrote ${displayPath(result.data.settingsPath)} (no token stored \u2014 resolved at runtime via otelHeadersHelper)`);
43824
+ if (result.data.healedLocalKeys.length > 0) {
43825
+ printInfo(` removed ${result.data.healedLocalKeys.length} old telemetry key(s) from ${SETTINGS_LOCAL_FILE2}`);
43826
+ }
43827
+ printInfo(" only Verity projects send data. In other repositories Claude Code shows a startup");
43828
+ printInfo(' warning that "verity telemetry headers" exited 1, and it exports nothing there.');
43177
43829
  printInfo(" takes effect on your NEXT Claude Code session; first metrics appear within ~60s of activity");
43178
43830
  printInfo(" view cost & usage at /usage");
43179
43831
  });
43180
- telemetry.command("headers").description("Print the OTLP Authorization header as JSON (used by Claude Code otelHeadersHelper)").action(async () => {
43832
+ telemetry.command("headers").description("Print the OTLP headers as JSON for Claude Code otelHeadersHelper (exits 1 outside a Verity project)").action(async () => {
43833
+ if (!isTelemetryProject()) {
43834
+ declineHeaders("this directory is not in a Verity project.");
43835
+ return;
43836
+ }
43181
43837
  const globals = program2.opts();
43182
43838
  const tokenResult = await resolveToken(globals.token);
43183
- if (tokenResult.ok) {
43184
- const remote = requestRemote();
43185
- printJsonCompact({
43186
- Authorization: `Bearer ${tokenResult.data.token}`,
43187
- ...remote ? { "X-Verity-Remote": remote } : {}
43188
- });
43189
- } else {
43190
- printJsonCompact({});
43839
+ if (!tokenResult.ok) {
43840
+ declineHeaders('this Verity project has no token on this machine. Run "verity login".');
43841
+ return;
43842
+ }
43843
+ const endpoints = await sessionExportEndpoints();
43844
+ if (endpoints.length === 0) {
43845
+ declineHeaders("no OTLP endpoint is configured for this session.");
43846
+ return;
43847
+ }
43848
+ for (const endpoint of endpoints) {
43849
+ const refusal = await telemetryPinRefusal(tokenResult.data.token, endpoint);
43850
+ if (refusal) {
43851
+ declineHeaders(refusal);
43852
+ return;
43853
+ }
43191
43854
  }
43855
+ const remote = requestRemote();
43856
+ printJsonCompact({
43857
+ Authorization: `Bearer ${tokenResult.data.token}`,
43858
+ ...remote ? { "X-Verity-Remote": remote } : {}
43859
+ });
43192
43860
  });
43193
43861
  telemetry.command("check").description("Show whether Claude Code telemetry export to Verity is enabled").option("--json", "Output status as JSON").action(async (opts) => {
43194
43862
  const status = await checkTelemetry();
@@ -43197,21 +43865,36 @@ function registerTelemetryCommands(program2) {
43197
43865
  return;
43198
43866
  }
43199
43867
  if (status.enabled) {
43200
- printInfo(`Telemetry: enabled \u2192 ${status.endpoint}`);
43868
+ printInfo(`Telemetry: enabled \u2192 ${status.endpoint} (${SCOPE})`);
43201
43869
  } else {
43202
43870
  printWarn('Telemetry: disabled. Run "verity telemetry install" to enable cost & usage tracking.');
43203
43871
  }
43872
+ if (status.staleLocalKeys.length > 0) {
43873
+ printWarn(
43874
+ ` ${SETTINGS_LOCAL_FILE2} still holds ${status.staleLocalKeys.length} old Verity telemetry key(s), which Claude Code ignores there. "verity telemetry install" removes them.`
43875
+ );
43876
+ }
43204
43877
  });
43205
- telemetry.command("uninstall").description("Disable Claude Code telemetry export to Verity").action(async () => {
43878
+ telemetry.command("uninstall").description(`Stop the telemetry export to Verity from ${SCOPE}`).action(async () => {
43206
43879
  const result = await uninstallTelemetry();
43207
43880
  if (!result.ok) {
43208
43881
  printError(result.error);
43209
43882
  process.exit(1);
43210
43883
  }
43211
43884
  await recordTelemetryChoice("declined");
43212
- printInfo(
43213
- result.data.removed > 0 ? `Telemetry disabled (${result.data.removed} keys removed from ${SETTINGS_LOCAL_FILE2})` : "Telemetry was not enabled \u2014 nothing to remove."
43214
- );
43885
+ const { removed, settingsPath, foreignEndpoint, healedLocalKeys } = result.data;
43886
+ if (removed > 0) {
43887
+ printInfo(`Telemetry disabled (${removed} keys removed from ${displayPath(settingsPath)})`);
43888
+ } else if (foreignEndpoint) {
43889
+ printInfo(
43890
+ `${displayPath(settingsPath)} exports to another destination, so Verity left it unchanged.`
43891
+ );
43892
+ } else {
43893
+ printInfo("Telemetry was not enabled \u2014 nothing to remove.");
43894
+ }
43895
+ if (healedLocalKeys.length > 0) {
43896
+ printInfo(` removed ${healedLocalKeys.length} old telemetry key(s) from ${SETTINGS_LOCAL_FILE2}`);
43897
+ }
43215
43898
  });
43216
43899
  }
43217
43900
 
@@ -43253,8 +43936,8 @@ function renderSeatUsage(d, now = /* @__PURE__ */ new Date()) {
43253
43936
  lines.push(
43254
43937
  `Session ${utcHm(d.session.start)}\u2013${utcHm(d.session.resets_at)} (resets in ${inText(now, new Date(d.session.resets_at))}) \xB7 week from Mon ${utcDay2(d.week.start)} (resets Mon ${utcDay2(d.week.resets_at)}, 00:00 UTC)`
43255
43938
  );
43256
- if (!d.plan) lines.push("No plan is set for this org: counts only. Plan limits apply from Nov 2.");
43257
- else if (!d.enforced) lines.push("Limits are shown for information. They are not enforced before Nov 2.");
43939
+ if (!d.plan) lines.push("No plan is set for this org: counts only.");
43940
+ else if (!d.enforced) lines.push("Limits are shown for information. They are not enforced yet.");
43258
43941
  const me = d.members.find((m) => m.me);
43259
43942
  if (me) {
43260
43943
  const w = me.weeks[0];
@@ -43358,8 +44041,8 @@ function registerUsageCommand(program2) {
43358
44041
  }
43359
44042
 
43360
44043
  // src/commands/prove.ts
43361
- var import_node_fs80 = require("node:fs");
43362
- var import_node_path63 = require("node:path");
44044
+ var import_node_fs83 = require("node:fs");
44045
+ var import_node_path68 = require("node:path");
43363
44046
 
43364
44047
  // src/lib/prove/hint-pool.ts
43365
44048
  var STOP2 = /* @__PURE__ */ new Set([
@@ -43501,7 +44184,7 @@ function registerProveCommand(program2) {
43501
44184
  process.exit(2);
43502
44185
  }
43503
44186
  const limit = Math.max(1, Math.min(50, Number(opts.limit) || 10));
43504
- let signals = collectRiskSignals(root, { since: opts.since, limit, paths: paths.map((p) => (0, import_node_path63.relative)(root, (0, import_node_path63.resolve)(p)) || ".") });
44187
+ let signals = collectRiskSignals(root, { since: opts.since, limit, paths: paths.map((p) => (0, import_node_path68.relative)(root, (0, import_node_path68.resolve)(p)) || ".") });
43505
44188
  if (opts.hint && paths.length === 0) {
43506
44189
  const hit = hintMatches(root, opts.hint);
43507
44190
  if (hit.files.length > 0) {
@@ -43555,7 +44238,7 @@ function registerProveCommand(program2) {
43555
44238
  });
43556
44239
  prove.command("check [model]").description("Elaborate a model (or every model) with the local Lean toolchain and report each property").option("--timeout <ms>", "Per-model timeout in ms", "120000").option("--json", "Machine-readable output").action((model, opts) => {
43557
44240
  const root = repoRoot();
43558
- if (!(0, import_node_fs80.existsSync)((0, import_node_path63.join)(root, PROOFS_DIR, "lakefile.toml"))) {
44241
+ if (!(0, import_node_fs83.existsSync)((0, import_node_path68.join)(root, PROOFS_DIR, "lakefile.toml"))) {
43559
44242
  printError(`No ${PROOFS_DIR}/ project here. Run \`verity prove init\` first.`);
43560
44243
  process.exit(2);
43561
44244
  }
@@ -43579,7 +44262,7 @@ ${build.stderr.split("\n").filter((l) => !l.startsWith("trace:")).slice(0, 20).j
43579
44262
  const reports = [];
43580
44263
  for (const f of files) {
43581
44264
  const report = checkModel(tc, root, f, { timeoutMs: Math.max(1e3, Number(opts.timeout) || 12e4) });
43582
- report.file = (0, import_node_path63.relative)(root, f);
44265
+ report.file = (0, import_node_path68.relative)(root, f);
43583
44266
  reports.push(report);
43584
44267
  try {
43585
44268
  writeResult(root, report);
@@ -43617,7 +44300,7 @@ ${build.stderr.split("\n").filter((l) => !l.startsWith("trace:")).slice(0, 20).j
43617
44300
  printError("Not logged in \u2014 replays run on the Verity service. Run `verity login` first.");
43618
44301
  process.exit(2);
43619
44302
  }
43620
- if (!(0, import_node_fs80.existsSync)((0, import_node_path63.join)(root, PROOFS_DIR, "lakefile.toml"))) {
44303
+ if (!(0, import_node_fs83.existsSync)((0, import_node_path68.join)(root, PROOFS_DIR, "lakefile.toml"))) {
43621
44304
  printError(`No ${PROOFS_DIR}/ project here. Run \`verity prove init\` first.`);
43622
44305
  process.exit(2);
43623
44306
  }
@@ -43648,7 +44331,7 @@ ${report.error ?? ""}`);
43648
44331
  printInfo(opts.property ? `${model} ${opts.property} has no counterexample to replay` : `${model} has no counterexample to replay`);
43649
44332
  return;
43650
44333
  }
43651
- const modelSource = readRegularFileInRoot(root, (0, import_node_path63.relative)(root, file), 512 * 1024);
44334
+ const modelSource = readRegularFileInRoot(root, (0, import_node_path68.relative)(root, file), 512 * 1024);
43652
44335
  if (!opts.json) printInfo(`replaying ${targets.length} counterexample(s) of ${model} in network-less containers (${Math.min(targets.length, REPLAY_CONCURRENCY)} at a time)\u2026`);
43653
44336
  const outcomes = await mapPool(targets, REPLAY_CONCURRENCY, async (p) => {
43654
44337
  const brief = buildReplayBrief(root, report, p, modelSource.ok ? modelSource.content : "");
@@ -43668,7 +44351,7 @@ ${report.error ?? ""}`);
43668
44351
  });
43669
44352
  prove.command("auto [files...]").description("The formal phase the pre-push guard runs: select one risky symbol among changed files, generate a model, check it, replay counterexamples (default files: the commits ahead of upstream)").option("--deadline <ms>", "Budget for the whole phase", "170000").option("--json", "Machine-readable output").action(async (paths, opts) => {
43670
44353
  const root = repoRoot();
43671
- let files = paths.map((p) => (0, import_node_path63.relative)(root, (0, import_node_path63.resolve)(p)));
44354
+ let files = paths.map((p) => (0, import_node_path68.relative)(root, (0, import_node_path68.resolve)(p)));
43672
44355
  if (files.length === 0) {
43673
44356
  const ahead = runGitSync(root, ["diff", "--name-only", "@{upstream}...HEAD"]);
43674
44357
  const fallback = ahead.ok ? ahead : runGitSync(root, ["diff", "--name-only", "HEAD~1..HEAD"]);
@@ -43742,7 +44425,7 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
43742
44425
  let revise;
43743
44426
  if (opts.revise) {
43744
44427
  const existing = resolveModelFile(root, opts.revise);
43745
- const src = existing ? readRegularFileInRoot(root, (0, import_node_path63.relative)(root, existing), 64 * 1024) : null;
44428
+ const src = existing ? readRegularFileInRoot(root, (0, import_node_path68.relative)(root, existing), 64 * 1024) : null;
43746
44429
  if (!src || !src.ok || !opts.note) {
43747
44430
  printError("--revise needs an existing model under Models/ and --note - with what the replay showed on stdin");
43748
44431
  process.exit(2);
@@ -43782,8 +44465,8 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
43782
44465
  });
43783
44466
  prove.command("waive <file>").description("Dismiss the remembered formal finding(s) on a file for its current bytes \u2014 the accepted-risk path for a formal block").requiredOption("--reason <text>", "The human disposition this records (reviewer finding, ADR, \u2026)").action((file, opts) => {
43784
44467
  const root = repoRoot();
43785
- const rel0 = (0, import_node_path63.relative)(root, (0, import_node_path63.isAbsolute)(file) ? file : (0, import_node_path63.join)(root, file)).replace(/\\/g, "/");
43786
- const rel = rel0.startsWith("..") || (0, import_node_path63.isAbsolute)(rel0) ? null : rel0;
44468
+ const rel0 = (0, import_node_path68.relative)(root, (0, import_node_path68.isAbsolute)(file) ? file : (0, import_node_path68.join)(root, file)).replace(/\\/g, "/");
44469
+ const rel = rel0.startsWith("..") || (0, import_node_path68.isAbsolute)(rel0) ? null : rel0;
43787
44470
  if (!rel) {
43788
44471
  printError(`${file} is not inside this repository`);
43789
44472
  process.exit(2);
@@ -43799,8 +44482,8 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
43799
44482
  });
43800
44483
  prove.command("hash <file>").description("Print the sha256 of a file, for a model header (Verity.model \u2026 sha256). Path is REPO-relative, as in the header").action((file) => {
43801
44484
  const root = repoRoot();
43802
- const rel = (0, import_node_path63.relative)(root, (0, import_node_path63.isAbsolute)(file) ? file : (0, import_node_path63.join)(root, file));
43803
- const digest = rel.startsWith("..") || (0, import_node_path63.isAbsolute)(rel) ? null : sha256InRoot(root, rel);
44485
+ const rel = (0, import_node_path68.relative)(root, (0, import_node_path68.isAbsolute)(file) ? file : (0, import_node_path68.join)(root, file));
44486
+ const digest = rel.startsWith("..") || (0, import_node_path68.isAbsolute)(rel) ? null : sha256InRoot(root, rel);
43804
44487
  if (!digest) {
43805
44488
  printError(`Cannot hash ${file}: it must be a regular file inside this repository (not a symlink)`);
43806
44489
  process.exit(2);
@@ -43834,10 +44517,10 @@ function refreshScaffold(root) {
43834
44517
  }
43835
44518
 
43836
44519
  // src/commands/lean.ts
43837
- var import_node_fs81 = require("node:fs");
43838
- var import_node_child_process22 = require("node:child_process");
43839
- var import_node_os13 = require("node:os");
43840
- var import_node_path64 = require("node:path");
44520
+ var import_node_fs84 = require("node:fs");
44521
+ var import_node_child_process24 = require("node:child_process");
44522
+ var import_node_os15 = require("node:os");
44523
+ var import_node_path69 = require("node:path");
43841
44524
  function registerLeanCommand(program2) {
43842
44525
  const lean = program2.command("lean").description("The Lean 4 toolchain formal verification needs (managed download, pinned by this CLI)");
43843
44526
  lean.command("status").option("--json", "Machine-readable output").description("Which toolchain would run, and whether it matches the pinned version").action((opts) => {
@@ -43845,7 +44528,7 @@ function registerLeanCommand(program2) {
43845
44528
  let version = null;
43846
44529
  if (tc) {
43847
44530
  try {
43848
- version = (0, import_node_child_process22.execFileSync)(tc.lake.replace(/lake(\.exe)?$/, "lean$1"), ["--version"], { env: tc.env, encoding: "utf-8", timeout: 1e4 }).trim();
44531
+ version = (0, import_node_child_process24.execFileSync)(tc.lake.replace(/lake(\.exe)?$/, "lean$1"), ["--version"], { env: tc.env, encoding: "utf-8", timeout: 1e4 }).trim();
43849
44532
  } catch {
43850
44533
  version = null;
43851
44534
  }
@@ -43856,7 +44539,7 @@ function registerLeanCommand(program2) {
43856
44539
  const bg = readInstallState();
43857
44540
  const background = bg ? { ...bg, running: bg.state === "running" && installRunning() } : null;
43858
44541
  if (opts.json) {
43859
- printJson({ pinned: LEAN_TOOLCHAIN, found: tc?.lake ?? null, version, matches_pin: matches, managed_dir: managed, managed_installed: (0, import_node_fs81.existsSync)((0, import_node_path64.join)(managed, "bin")), asset: leanAssetName(), background });
44542
+ printJson({ pinned: LEAN_TOOLCHAIN, found: tc?.lake ?? null, version, matches_pin: matches, managed_dir: managed, managed_installed: (0, import_node_fs84.existsSync)((0, import_node_path69.join)(managed, "bin")), asset: leanAssetName(), background });
43860
44543
  return;
43861
44544
  }
43862
44545
  printInfo(`pinned: ${LEAN_TOOLCHAIN}`);
@@ -43873,11 +44556,11 @@ function registerLeanCommand(program2) {
43873
44556
  if (opts.background) process.exit(await runBackgroundInstall());
43874
44557
  const asset = leanAssetName();
43875
44558
  if (!asset) {
43876
- printError(`no Lean release for ${(0, import_node_os13.platform)()}/${(0, import_node_os13.arch)()}`);
44559
+ printError(`no Lean release for ${(0, import_node_os15.platform)()}/${(0, import_node_os15.arch)()}`);
43877
44560
  process.exit(2);
43878
44561
  }
43879
44562
  const dir = pinnedVersionDir();
43880
- if ((0, import_node_fs81.existsSync)((0, import_node_path64.join)(dir, "bin", "lake")) && !opts.force) {
44563
+ if ((0, import_node_fs84.existsSync)((0, import_node_path69.join)(dir, "bin", "lake")) && !opts.force) {
43881
44564
  printInfo(`already installed at ${dir}`);
43882
44565
  return;
43883
44566
  }
@@ -43913,18 +44596,21 @@ function registerLeanCommand(program2) {
43913
44596
  });
43914
44597
  lean.command("remove").description("Delete the Verity-managed toolchain for the pinned version").action(() => {
43915
44598
  const dir = pinnedVersionDir();
43916
- if (!(0, import_node_fs81.existsSync)(dir)) {
44599
+ if (!(0, import_node_fs84.existsSync)(dir)) {
43917
44600
  printInfo("nothing to remove");
43918
44601
  return;
43919
44602
  }
43920
- (0, import_node_fs81.rmSync)(dir, { recursive: true, force: true });
44603
+ (0, import_node_fs84.rmSync)(dir, { recursive: true, force: true });
43921
44604
  printInfo(`removed ${dir}`);
43922
44605
  });
43923
44606
  }
43924
44607
 
43925
44608
  // src/cli.ts
43926
- program.name("verity").description("CLI for Verity quality gate service").version("0.35.0-experimental.3211f79").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
43927
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.35.0-experimental.3211f79");
44609
+ program.name("verity").description("CLI for Verity quality gate service").version("0.35.0-experimental.3a13a5e").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
44610
+ const isTelemetryHelper = actionCommand.name() === "headers" && actionCommand.parent?.name() === "telemetry";
44611
+ if (!isTelemetryHelper) {
44612
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.35.0-experimental.3a13a5e");
44613
+ }
43928
44614
  setUserNamedServiceUrl(program.opts().serviceUrl);
43929
44615
  try {
43930
44616
  await foldLegacyLocalCredential();