@codacy/verity-cli 0.35.3-experimental.f980d82 → 0.35.4

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
@@ -14732,8 +14732,10 @@ var SECURITY_PATTERNS = [
14732
14732
  /(^|\/)pom\.xml$/,
14733
14733
  /(^|\/)gradle\.lockfile$/
14734
14734
  ];
14735
- var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
14736
- var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
14735
+ var PROD_SERVICE_ORIGIN = "https://server.verity.md";
14736
+ var LEGACY_PROD_SERVICE_ORIGIN = "https://ofcamwrjwrkazqvdchko.supabase.co";
14737
+ var PROD_SERVICE_URL = `${PROD_SERVICE_ORIGIN}/functions/v1`;
14738
+ var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
14737
14739
  var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
14738
14740
  var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
14739
14741
  var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
@@ -14823,6 +14825,36 @@ function rotateIfNeeded(file) {
14823
14825
  var import_promises = require("node:fs/promises");
14824
14826
  var import_node_path3 = require("node:path");
14825
14827
  var import_node_child_process2 = require("node:child_process");
14828
+
14829
+ // src/lib/service-origin.ts
14830
+ var LEGACY = new URL(LEGACY_PROD_SERVICE_ORIGIN);
14831
+ function canonicalServiceUrl(raw) {
14832
+ const trimmed = raw.trim();
14833
+ let u;
14834
+ try {
14835
+ u = new URL(trimmed);
14836
+ } catch {
14837
+ return raw;
14838
+ }
14839
+ if (u.protocol !== LEGACY.protocol || u.host.toLowerCase() !== LEGACY.host || u.username !== "" || u.password !== "") {
14840
+ return raw;
14841
+ }
14842
+ return trimmed.replace(/^https:\/\/[^/?#]+/i, PROD_SERVICE_ORIGIN);
14843
+ }
14844
+ function normalizeServiceUrl(raw) {
14845
+ const trimmed = canonicalServiceUrl(raw).trim().replace(/\/+$/, "");
14846
+ try {
14847
+ const u = new URL(trimmed);
14848
+ return `${u.protocol}//${u.host.toLowerCase()}${u.pathname.replace(/\/+$/, "")}`;
14849
+ } catch {
14850
+ return trimmed.toLowerCase();
14851
+ }
14852
+ }
14853
+ function sameServiceUrl(a, b) {
14854
+ return normalizeServiceUrl(a) === normalizeServiceUrl(b);
14855
+ }
14856
+
14857
+ // src/lib/credentials.ts
14826
14858
  function globalCredentialsPath() {
14827
14859
  return `${process.env.HOME}/.verity/credentials`;
14828
14860
  }
@@ -14843,7 +14875,8 @@ function parseCredentialLine(line2) {
14843
14875
  const rest = idx === -1 ? line2 : line2.slice(idx);
14844
14876
  const tokenMatch = rest.match(TOKEN_RE);
14845
14877
  if (!tokenMatch) return null;
14846
- const serviceUrl = rest.match(/service_url:\s*(https?:\/\/\S+)/)?.[1];
14878
+ const serviceUrlRaw = rest.match(/service_url:\s*(https?:\/\/\S+)/)?.[1];
14879
+ const serviceUrl = serviceUrlRaw != null ? canonicalServiceUrl(serviceUrlRaw) : void 0;
14847
14880
  const userIdRaw = rest.match(/user_id:\s*(\d+)/)?.[1];
14848
14881
  const email = rest.match(/email:\s*(\S+)/)?.[1];
14849
14882
  return {
@@ -14865,7 +14898,7 @@ function encodeRemoteKey(remote) {
14865
14898
  function serializeCredential(remote, rec) {
14866
14899
  const key = encodeRemoteKey(remote);
14867
14900
  const out2 = [key ? `${key} token: ${rec.token}` : `token: ${rec.token}`];
14868
- if (rec.serviceUrl) out2.push(`service_url: ${rec.serviceUrl}`);
14901
+ if (rec.serviceUrl) out2.push(`service_url: ${canonicalServiceUrl(rec.serviceUrl)}`);
14869
14902
  if (rec.userId != null) out2.push(`user_id: ${rec.userId}`);
14870
14903
  if (rec.email) out2.push(`email: ${rec.email}`);
14871
14904
  return out2.join(" ");
@@ -14938,7 +14971,7 @@ async function removeSupersededUserCredentials(loginServiceUrl, loginUserId) {
14938
14971
  let removed = 0;
14939
14972
  for (const line2 of content.split("\n")) {
14940
14973
  const parsed = parseCredentialLine(line2);
14941
- const superseded = parsed !== null && parsed.remote !== "" && parsed.rec.userId === loginUserId && (!parsed.rec.serviceUrl || parsed.rec.serviceUrl === loginServiceUrl);
14974
+ const superseded = parsed !== null && parsed.remote !== "" && parsed.rec.userId === loginUserId && (!parsed.rec.serviceUrl || sameServiceUrl(parsed.rec.serviceUrl, loginServiceUrl));
14942
14975
  if (superseded) {
14943
14976
  removed++;
14944
14977
  continue;
@@ -14990,9 +15023,10 @@ function parseLocalCredentialFile(content) {
14990
15023
  const tokenMatch = content.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
14991
15024
  if (!tokenMatch) return null;
14992
15025
  const userIdMatch = content.match(/^user_id:\s*(\d+)/m);
15026
+ const serviceUrl = content.match(/service_url:\s*(https?:\/\/\S+)/)?.[1];
14993
15027
  return {
14994
15028
  token: tokenMatch[1],
14995
- serviceUrl: content.match(/service_url:\s*(https?:\/\/\S+)/)?.[1],
15029
+ serviceUrl: serviceUrl != null ? canonicalServiceUrl(serviceUrl) : void 0,
14996
15030
  userId: userIdMatch ? Number(userIdMatch[1]) : void 0,
14997
15031
  email: content.match(/^email:\s*(\S+)/m)?.[1]
14998
15032
  };
@@ -15306,15 +15340,7 @@ async function checkTokenPin(token, targetUrl) {
15306
15340
  if (!stored.serviceUrl) return { attach: false, reason: "unpinnable" };
15307
15341
  return normalizeUrl(stored.serviceUrl) === normalizeUrl(targetUrl) ? { attach: true } : { attach: false, reason: "mismatch", mintedFor: stored.serviceUrl };
15308
15342
  }
15309
- function normalizeUrl(raw) {
15310
- const trimmed = raw.trim().replace(/\/+$/, "");
15311
- try {
15312
- const u = new URL(trimmed);
15313
- return `${u.protocol}//${u.host.toLowerCase()}${u.pathname.replace(/\/+$/, "")}`;
15314
- } catch {
15315
- return trimmed.toLowerCase();
15316
- }
15317
- }
15343
+ var normalizeUrl = normalizeServiceUrl;
15318
15344
  function pinRefusalMessage(verdict, targetUrl) {
15319
15345
  if (verdict.reason === "unpinnable") {
15320
15346
  return `Refusing to send your Verity login to ${targetUrl}: this machine's credential does not record which service issued it, so it cannot be verified. Run "verity login" to re-issue it.`;
@@ -15325,7 +15351,7 @@ function pinRefusalMessage(verdict, targetUrl) {
15325
15351
  // src/lib/cli-version.ts
15326
15352
  function cliVersion() {
15327
15353
  try {
15328
- return true ? "0.35.3-experimental.f980d82" : "dev";
15354
+ return true ? "0.35.4" : "dev";
15329
15355
  } catch {
15330
15356
  return "dev";
15331
15357
  }
@@ -15530,7 +15556,7 @@ async function serviceUrlFromVerityMd() {
15530
15556
  const line2 = content.split("\n").find((l) => /\*{0,2}(?:url|service)\*{0,2}\s*:/i.test(l));
15531
15557
  if (line2) {
15532
15558
  const urlMatch = line2.match(/https:\/\/[^\s]+/);
15533
- if (urlMatch) return urlMatch[0];
15559
+ if (urlMatch) return canonicalServiceUrl(urlMatch[0]);
15534
15560
  }
15535
15561
  } catch {
15536
15562
  }
@@ -18048,6 +18074,124 @@ function describeRemote(found) {
18048
18074
  return `version ${found.standard.version}, pushed${who} on ${when}${also}`;
18049
18075
  }
18050
18076
 
18077
+ // src/lib/json-file.ts
18078
+ var import_promises3 = require("node:fs/promises");
18079
+ var import_node_path12 = require("node:path");
18080
+ function jsonSemanticEqual(a, b) {
18081
+ if (a === b) return true;
18082
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
18083
+ return a === b;
18084
+ }
18085
+ const aIsArr = Array.isArray(a);
18086
+ const bIsArr = Array.isArray(b);
18087
+ if (aIsArr || bIsArr) {
18088
+ if (!aIsArr || !bIsArr || a.length !== b.length) return false;
18089
+ for (let i2 = 0; i2 < a.length; i2++) {
18090
+ if (!jsonSemanticEqual(a[i2], b[i2])) return false;
18091
+ }
18092
+ return true;
18093
+ }
18094
+ const ao = a;
18095
+ const bo = b;
18096
+ const aKeys = Object.keys(ao).filter((k) => ao[k] !== void 0);
18097
+ const bKeys = Object.keys(bo).filter((k) => bo[k] !== void 0);
18098
+ if (aKeys.length !== bKeys.length) return false;
18099
+ for (const k of aKeys) {
18100
+ if (bo[k] === void 0) return false;
18101
+ if (!jsonSemanticEqual(ao[k], bo[k])) return false;
18102
+ }
18103
+ return true;
18104
+ }
18105
+ function detectJsonIndent(raw) {
18106
+ const m = raw.match(/\n([ \t]+)\S/);
18107
+ return m ? m[1] : 2;
18108
+ }
18109
+ async function writeJsonFilePreservingStyle(file, value) {
18110
+ let currentRaw = null;
18111
+ try {
18112
+ currentRaw = await (0, import_promises3.readFile)(file, "utf-8");
18113
+ } catch {
18114
+ currentRaw = null;
18115
+ }
18116
+ if (currentRaw !== null) {
18117
+ try {
18118
+ if (jsonSemanticEqual(JSON.parse(currentRaw), value)) return false;
18119
+ } catch {
18120
+ }
18121
+ }
18122
+ const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
18123
+ const next = JSON.stringify(value, null, indent) + "\n";
18124
+ if (next === currentRaw) return false;
18125
+ await (0, import_promises3.mkdir)((0, import_node_path12.dirname)(file), { recursive: true });
18126
+ await (0, import_promises3.writeFile)(file, next);
18127
+ return true;
18128
+ }
18129
+
18130
+ // src/lib/setup-state.ts
18131
+ var SETUP_STATE_FILE = `${VERITY_DIR}/setup.json`;
18132
+ var MOMENTS = ["stop", "pre-commit", "pre-push"];
18133
+ var INTENSITIES = ["lightweight", "balanced", "thorough"];
18134
+ var TELEMETRY = ["enabled", "declined", "deferred"];
18135
+ var VERSION = /^[0-9A-Za-z.+-]{1,32}$/;
18136
+ var stamp = (v) => typeof v === "string" && v.length > 0 && v.length <= 64;
18137
+ function setupStateFrom(raw) {
18138
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
18139
+ const r = raw;
18140
+ if (r.version !== 1) return null;
18141
+ const out2 = { version: 1 };
18142
+ if (INTENSITIES.includes(r.intensity)) out2.intensity = r.intensity;
18143
+ if (Array.isArray(r.moments) && r.moments.every((m) => MOMENTS.includes(m))) {
18144
+ out2.moments = [...new Set(r.moments)];
18145
+ }
18146
+ if (TELEMETRY.includes(r.telemetry)) out2.telemetry = r.telemetry;
18147
+ if (Number.isSafeInteger(r.declinedStandardVersion) && r.declinedStandardVersion >= 1) {
18148
+ out2.declinedStandardVersion = r.declinedStandardVersion;
18149
+ }
18150
+ if (Number.isSafeInteger(r.standardVersion) && r.standardVersion >= 1) {
18151
+ out2.standardVersion = r.standardVersion;
18152
+ }
18153
+ const init2 = r.init;
18154
+ if (init2 && typeof init2 === "object" && stamp(init2.completed_at) && typeof init2.cli_version === "string" && VERSION.test(init2.cli_version)) {
18155
+ out2.init = { completed_at: init2.completed_at, cli_version: init2.cli_version };
18156
+ }
18157
+ const plugin = r.pluginSetup;
18158
+ if (plugin && typeof plugin === "object" && typeof plugin.version === "string" && VERSION.test(plugin.version) && stamp(plugin.completed_at)) {
18159
+ out2.pluginSetup = { version: plugin.version, completed_at: plugin.completed_at };
18160
+ }
18161
+ if (r.wiring === "project") out2.wiring = "project";
18162
+ return out2;
18163
+ }
18164
+ var MAX_SETUP_BYTES = 64 * 1024;
18165
+ function readSetupText() {
18166
+ const read2 = readRegularFileInRoot(repoRoot(), SETUP_STATE_FILE, MAX_SETUP_BYTES);
18167
+ return read2.ok ? read2.content : null;
18168
+ }
18169
+ async function readSetupState() {
18170
+ const raw = readSetupText();
18171
+ if (raw === null) return null;
18172
+ try {
18173
+ return setupStateFrom(JSON.parse(raw));
18174
+ } catch {
18175
+ return null;
18176
+ }
18177
+ }
18178
+ async function writeSetupState(patch) {
18179
+ const current = await readSetupState() ?? { version: 1 };
18180
+ const next = { ...current, ...patch, version: 1 };
18181
+ const raw = readSetupText();
18182
+ if (raw !== null) {
18183
+ try {
18184
+ if (jsonSemanticEqual(JSON.parse(raw), next)) return next;
18185
+ } catch {
18186
+ }
18187
+ }
18188
+ const text3 = JSON.stringify(next, null, raw !== null ? detectJsonIndent(raw) : 2) + "\n";
18189
+ if (!replaceInRoot(projectPath(VERITY_DIR), "setup.json", text3)) {
18190
+ throw new Error(`Could not write ${SETUP_STATE_FILE}: it is a link, or ${VERITY_DIR} is not a folder of this repository`);
18191
+ }
18192
+ return next;
18193
+ }
18194
+
18051
18195
  // src/lib/catalogue-conversion.ts
18052
18196
  var PACK_ID2 = /^[a-z][a-z0-9-]{0,39}$/;
18053
18197
  var MAX_PACKS2 = 12;
@@ -18078,6 +18222,12 @@ function count(v) {
18078
18222
  }
18079
18223
 
18080
18224
  // src/lib/push-setup.ts
18225
+ function latestFromConflict(result) {
18226
+ const err2 = result.errorBody?.error;
18227
+ const raw = err2 && typeof err2 === "object" ? err2.latest_version : void 0;
18228
+ const n2 = typeof raw === "string" && /^[1-9][0-9]*$/.test(raw) ? Number(raw) : typeof raw === "number" ? raw : NaN;
18229
+ return Number.isSafeInteger(n2) && n2 >= 1 ? n2 : null;
18230
+ }
18081
18231
  async function pushStandard(globals, opts = {}) {
18082
18232
  const lines = [];
18083
18233
  const token = await resolveToken(globals.token);
@@ -18113,11 +18263,15 @@ async function pushStandard(globals, opts = {}) {
18113
18263
  if (result.ok) {
18114
18264
  standardVersion = result.data.version;
18115
18265
  lines.push(`Standard pushed to Verity (v${result.data.version}) \u2713`);
18266
+ if (Number.isSafeInteger(standardVersion) && standardVersion >= 1) {
18267
+ await writeSetupState({ standardVersion }).catch(() => {
18268
+ });
18269
+ }
18116
18270
  const converted = conversionLine(result.data);
18117
18271
  if (converted) lines.push(converted);
18118
18272
  } else if (opts.expectedLatest !== void 0 && result.http_status === 409 && result.error.startsWith("STANDARD_EXISTS")) {
18119
18273
  lines.push("The service has a Standard now that this run did not see, so nothing was pushed over it.");
18120
- return { lines, standardVersion: null, conflict: true };
18274
+ return { lines, standardVersion: null, conflict: true, latest: latestFromConflict(result) };
18121
18275
  } else {
18122
18276
  lines.push(`Could not push the Standard: ${terminalText(result.error, 300)} \u2014 run "verity standard push" later.`);
18123
18277
  }
@@ -18285,7 +18439,7 @@ async function finishSplitLogin(opts) {
18285
18439
  if (read2.status === "invalid") fail2("invalid", `the pending sign-in is unusable (${read2.reason}) \u2014 run "verity login --start" again`, true);
18286
18440
  if (read2.status === "expired") fail2("expired", 'the code expired before it was approved \u2014 run "verity login --start" again', true);
18287
18441
  const pending = read2.record;
18288
- if (pending.service_url !== opts.serviceUrl) {
18442
+ if (!sameServiceUrl(pending.service_url, opts.serviceUrl)) {
18289
18443
  fail2(
18290
18444
  "service_changed",
18291
18445
  `the sign-in was started for ${pending.service_url}, but this machine now resolves ${opts.serviceUrl} \u2014 run "verity login --start" again`,
@@ -18646,61 +18800,6 @@ function registerLogoutCommand(program2) {
18646
18800
  // src/lib/hooks.ts
18647
18801
  var import_promises4 = require("node:fs/promises");
18648
18802
  var import_node_path13 = require("node:path");
18649
-
18650
- // src/lib/json-file.ts
18651
- var import_promises3 = require("node:fs/promises");
18652
- var import_node_path12 = require("node:path");
18653
- function jsonSemanticEqual(a, b) {
18654
- if (a === b) return true;
18655
- if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
18656
- return a === b;
18657
- }
18658
- const aIsArr = Array.isArray(a);
18659
- const bIsArr = Array.isArray(b);
18660
- if (aIsArr || bIsArr) {
18661
- if (!aIsArr || !bIsArr || a.length !== b.length) return false;
18662
- for (let i2 = 0; i2 < a.length; i2++) {
18663
- if (!jsonSemanticEqual(a[i2], b[i2])) return false;
18664
- }
18665
- return true;
18666
- }
18667
- const ao = a;
18668
- const bo = b;
18669
- const aKeys = Object.keys(ao).filter((k) => ao[k] !== void 0);
18670
- const bKeys = Object.keys(bo).filter((k) => bo[k] !== void 0);
18671
- if (aKeys.length !== bKeys.length) return false;
18672
- for (const k of aKeys) {
18673
- if (bo[k] === void 0) return false;
18674
- if (!jsonSemanticEqual(ao[k], bo[k])) return false;
18675
- }
18676
- return true;
18677
- }
18678
- function detectJsonIndent(raw) {
18679
- const m = raw.match(/\n([ \t]+)\S/);
18680
- return m ? m[1] : 2;
18681
- }
18682
- async function writeJsonFilePreservingStyle(file, value) {
18683
- let currentRaw = null;
18684
- try {
18685
- currentRaw = await (0, import_promises3.readFile)(file, "utf-8");
18686
- } catch {
18687
- currentRaw = null;
18688
- }
18689
- if (currentRaw !== null) {
18690
- try {
18691
- if (jsonSemanticEqual(JSON.parse(currentRaw), value)) return false;
18692
- } catch {
18693
- }
18694
- }
18695
- const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
18696
- const next = JSON.stringify(value, null, indent) + "\n";
18697
- if (next === currentRaw) return false;
18698
- await (0, import_promises3.mkdir)((0, import_node_path12.dirname)(file), { recursive: true });
18699
- await (0, import_promises3.writeFile)(file, next);
18700
- return true;
18701
- }
18702
-
18703
- // src/lib/hooks.ts
18704
18803
  var VERITY_STOP_HOOK = {
18705
18804
  type: "command",
18706
18805
  command: "verity analyze",
@@ -19207,13 +19306,21 @@ function readConfigFile(path) {
19207
19306
  git_moments: [...new Set(moments)],
19208
19307
  ...source2 ? { git_moments_source: source2 } : {},
19209
19308
  ...raw.formal === "push" || raw.formal === "off" ? { formal: raw.formal } : {},
19210
- ...raw.turn_review === "on" || raw.turn_review === "off" ? { turn_review: raw.turn_review } : {}
19309
+ ...raw.turn_review === "on" || raw.turn_review === "off" ? { turn_review: raw.turn_review } : {},
19310
+ ...raw.verbose === "on" || raw.verbose === "off" ? { verbose: raw.verbose } : {}
19211
19311
  }
19212
19312
  };
19213
19313
  } catch {
19214
19314
  return { cfg: DEFAULTS };
19215
19315
  }
19216
19316
  }
19317
+ function readVerbose() {
19318
+ try {
19319
+ return readProjectConfigRaw().verbose === "on";
19320
+ } catch {
19321
+ return false;
19322
+ }
19323
+ }
19217
19324
  function configRefusalMessage(reason) {
19218
19325
  switch (reason) {
19219
19326
  case "link":
@@ -19285,12 +19392,13 @@ function formalRefusalMessage(reason) {
19285
19392
  }
19286
19393
  }
19287
19394
  function writeProjectConfig(patch) {
19288
- const { git_moments, git_moments_source, formal, turn_review } = readProjectConfigRaw();
19395
+ const { git_moments, git_moments_source, formal, turn_review, verbose } = readProjectConfigRaw();
19289
19396
  const next = {
19290
19397
  git_moments,
19291
19398
  ...git_moments_source ? { git_moments_source } : {},
19292
19399
  ...formal ? { formal } : {},
19293
19400
  ...turn_review ? { turn_review } : {},
19401
+ ...verbose ? { verbose } : {},
19294
19402
  ...patch
19295
19403
  };
19296
19404
  writeRepoFile(PROJECT_CONFIG_FILE, JSON.stringify(next, null, 2) + "\n");
@@ -21085,13 +21193,15 @@ var import_node_child_process9 = require("node:child_process");
21085
21193
  var import_node_fs24 = require("node:fs");
21086
21194
  var VERITY_GITIGNORE_MARKER = "# Verity \u2014 machine-local state.";
21087
21195
  var SETTINGS_LOCAL_IGNORE_ENTRY = ".claude/settings.local.json";
21196
+ var BLOCK_COMMENT = [
21197
+ "# Verity \u2014 machine-local state. Everything in .verity/ is ignored. The Standard",
21198
+ "# is held by the Verity service (verity standard pull writes it here), and the",
21199
+ "# knowledge graph under .verity/memory/ is rebuilt from it. Committing either",
21200
+ "# puts generated files in every diff and pull request."
21201
+ ];
21088
21202
  var VERITY_GITIGNORE_BLOCK = [
21089
- "# Verity \u2014 machine-local state. Everything in .verity/ is ignored except the",
21090
- "# shared standard, which is meant to be committed. The knowledge graph under",
21091
- "# .verity/memory/ is machine-local too: the service rebuilds it, and committing",
21092
- "# it puts generated notes in every diff and pull request.",
21203
+ ...BLOCK_COMMENT,
21093
21204
  ".verity/*",
21094
- "!.verity/standard.yaml",
21095
21205
  SETTINGS_LOCAL_IGNORE_ENTRY,
21096
21206
  ""
21097
21207
  ].join("\n");
@@ -21116,9 +21226,11 @@ var SUPERSEDED_MEMORY_LINES = /* @__PURE__ */ new Set([
21116
21226
  ".verity/memory/log.md",
21117
21227
  SUPERSEDED_COMMENT
21118
21228
  ]);
21119
- function isIgnored3(path) {
21229
+ var SUPERSEDED_STANDARD_LINE = "!.verity/standard.yaml";
21230
+ var OLD_COMMENT_LINES = /* @__PURE__ */ new Set([SUPERSEDED_COMMENT, ...MEMORY_COMMENT]);
21231
+ function isIgnored3(path, opts = {}) {
21120
21232
  try {
21121
- (0, import_node_child_process9.execSync)(`git check-ignore -q -- "${path}"`, { stdio: "pipe" });
21233
+ (0, import_node_child_process9.execSync)(`git check-ignore -q ${opts.noIndex ? "--no-index " : ""}-- "${path}"`, { stdio: "pipe" });
21122
21234
  return true;
21123
21235
  } catch (err2) {
21124
21236
  return err2.status === 1 ? false : null;
@@ -21126,10 +21238,9 @@ function isIgnored3(path) {
21126
21238
  }
21127
21239
  function semanticsHold() {
21128
21240
  const snapshot = isIgnored3(".verity/.snapshot/__probe__");
21129
- const standard = isIgnored3(".verity/standard.yaml");
21130
21241
  const futureState = isIgnored3(".verity/.__probe-future-state__");
21131
- if (snapshot === null || standard === null || futureState === null) return null;
21132
- return snapshot === true && futureState === true && standard === false;
21242
+ if (snapshot === null || futureState === null) return null;
21243
+ return snapshot === true && futureState === true;
21133
21244
  }
21134
21245
  function memoryPosture() {
21135
21246
  const ignored = isIgnored3(".verity/memory/domain/__probe__.md");
@@ -21146,6 +21257,22 @@ function committedMemoryFiles() {
21146
21257
  return [];
21147
21258
  }
21148
21259
  }
21260
+ var STANDARD_PATH = ".verity/standard.yaml";
21261
+ function standardPosture() {
21262
+ const ignored = isIgnored3(STANDARD_PATH, { noIndex: true });
21263
+ if (ignored === null) return "unknown";
21264
+ return ignored ? "ignored" : "committable";
21265
+ }
21266
+ function committedStandardFile() {
21267
+ try {
21268
+ return (0, import_node_child_process9.execSync)(`git ls-files -z -- ${STANDARD_PATH}`, {
21269
+ encoding: "utf-8",
21270
+ stdio: ["pipe", "pipe", "pipe"]
21271
+ }).split("\0").filter(Boolean).length > 0;
21272
+ } catch {
21273
+ return false;
21274
+ }
21275
+ }
21149
21276
  var OPT_OUT_COMMENT_LINES = /* @__PURE__ */ new Set([
21150
21277
  MEMORY_OPT_OUT_MARKER,
21151
21278
  '# Remove these three lines (or run "verity memory untrack") to stop tracking it.'
@@ -21163,6 +21290,19 @@ function fenceMemoryLines(lines) {
21163
21290
  }
21164
21291
  return out2;
21165
21292
  }
21293
+ function fenceStandardLines(lines) {
21294
+ const out2 = [];
21295
+ for (const line2 of lines) {
21296
+ const trimmed = line2.trim();
21297
+ if (trimmed.startsWith(VERITY_GITIGNORE_MARKER) && trimmed !== BLOCK_COMMENT[0]) {
21298
+ out2.push(...BLOCK_COMMENT);
21299
+ continue;
21300
+ }
21301
+ if (trimmed === SUPERSEDED_STANDARD_LINE || OLD_COMMENT_LINES.has(trimmed)) continue;
21302
+ out2.push(line2);
21303
+ }
21304
+ return out2;
21305
+ }
21166
21306
  function ensureVerityGitignore() {
21167
21307
  let content = "";
21168
21308
  try {
@@ -21174,9 +21314,11 @@ function ensureVerityGitignore() {
21174
21314
  const lines = content.split("\n");
21175
21315
  const needsRepair = lines.some((l) => BREAKING_ENTRIES.has(l.trim()));
21176
21316
  const hasSupersededMemory = hasMarker && !optedOut && lines.some((l) => SUPERSEDED_MEMORY_LINES.has(l.trim()));
21317
+ const hasSupersededStandard = hasMarker && lines.some((l) => l.trim() === SUPERSEDED_STANDARD_LINE);
21177
21318
  const memoryIsCommitted = hasSupersededMemory && committedMemoryFiles().length > 0;
21319
+ const standardIsCommitted = hasSupersededStandard && committedStandardFile();
21178
21320
  const verified = (result) => semanticsHold() === false ? "conflict" : result;
21179
- if (hasMarker && !needsRepair && !hasSupersededMemory) return verified("covered");
21321
+ if (hasMarker && !needsRepair && !hasSupersededMemory && !hasSupersededStandard) return verified("covered");
21180
21322
  if (!hasMarker && !needsRepair) {
21181
21323
  if (semanticsHold() === true) return "covered";
21182
21324
  }
@@ -21185,6 +21327,9 @@ function ensureVerityGitignore() {
21185
21327
  if (needsRepair) {
21186
21328
  next = next.map((l) => BREAKING_ENTRIES.has(l.trim()) ? ".verity/*" : l);
21187
21329
  }
21330
+ if (hasSupersededStandard && !standardIsCommitted) {
21331
+ next = fenceStandardLines(next);
21332
+ }
21188
21333
  if (hasSupersededMemory && !memoryIsCommitted) {
21189
21334
  next = fenceMemoryLines(next);
21190
21335
  }
@@ -21195,7 +21340,7 @@ function ensureVerityGitignore() {
21195
21340
  }
21196
21341
  if (text3 !== content) (0, import_node_fs24.writeFileSync)(".gitignore", text3);
21197
21342
  return verified(
21198
- memoryIsCommitted ? "memory-tracked" : hasSupersededMemory ? "memory-fenced" : needsRepair ? "repaired" : "added"
21343
+ memoryIsCommitted ? "memory-tracked" : standardIsCommitted ? "standard-tracked" : hasSupersededMemory ? "memory-fenced" : hasSupersededStandard ? "standard-fenced" : needsRepair ? "repaired" : "added"
21199
21344
  );
21200
21345
  } catch {
21201
21346
  return "failed";
@@ -21210,7 +21355,7 @@ function untrackMemory() {
21210
21355
  return "failed";
21211
21356
  }
21212
21357
  }
21213
- function writeFencedBlock() {
21358
+ function writeFencedBlock(fence) {
21214
21359
  let content = "";
21215
21360
  try {
21216
21361
  content = (0, import_node_fs24.readFileSync)(".gitignore", "utf-8");
@@ -21218,7 +21363,8 @@ function writeFencedBlock() {
21218
21363
  }
21219
21364
  const hasMarker = content.includes(VERITY_GITIGNORE_MARKER);
21220
21365
  let lines = content.split("\n").map((l) => BREAKING_ENTRIES.has(l.trim()) ? ".verity/*" : l);
21221
- lines = fenceMemoryLines(lines);
21366
+ if (fence.standard) lines = fenceStandardLines(lines);
21367
+ if (fence.memory) lines = fenceMemoryLines(lines);
21222
21368
  let text3 = lines.join("\n");
21223
21369
  if (!hasMarker) {
21224
21370
  const sep7 = text3 === "" ? "" : text3.endsWith("\n") ? "\n" : "\n\n";
@@ -21245,7 +21391,7 @@ function fenceMemory() {
21245
21391
  } catch {
21246
21392
  }
21247
21393
  };
21248
- if (!writeFencedBlock()) return { untracked: 0, ok: false };
21394
+ if (!writeFencedBlock({ memory: true })) return { untracked: 0, ok: false };
21249
21395
  if (memoryPosture() !== "ignored") {
21250
21396
  restore();
21251
21397
  return { untracked: 0, ok: false };
@@ -21257,6 +21403,34 @@ function fenceMemory() {
21257
21403
  }
21258
21404
  return { untracked: tracked, ok: true };
21259
21405
  }
21406
+ function untrackStandard() {
21407
+ let original = null;
21408
+ try {
21409
+ original = (0, import_node_fs24.readFileSync)(".gitignore", "utf-8");
21410
+ } catch {
21411
+ original = null;
21412
+ }
21413
+ const restore = () => {
21414
+ try {
21415
+ if (original === null) (0, import_node_fs24.rmSync)(".gitignore", { force: true });
21416
+ else (0, import_node_fs24.writeFileSync)(".gitignore", original);
21417
+ } catch {
21418
+ }
21419
+ };
21420
+ if (!writeFencedBlock({ standard: true })) return { ok: false, why: "gitignore" };
21421
+ if (standardPosture() !== "ignored") {
21422
+ restore();
21423
+ return { ok: false, why: "still-committable" };
21424
+ }
21425
+ if (!committedStandardFile()) return { ok: true, untracked: false };
21426
+ try {
21427
+ (0, import_node_child_process9.execSync)(`git rm --cached --quiet -- ${STANDARD_PATH}`, { stdio: "pipe" });
21428
+ return { ok: true, untracked: true };
21429
+ } catch {
21430
+ restore();
21431
+ return { ok: false, why: "index" };
21432
+ }
21433
+ }
21260
21434
  function memoryOptOut() {
21261
21435
  try {
21262
21436
  return (0, import_node_fs24.readFileSync)(".gitignore", "utf-8").includes(MEMORY_OPT_OUT_MARKER);
@@ -21286,18 +21460,23 @@ function committedVerityState() {
21286
21460
  } catch {
21287
21461
  return [];
21288
21462
  }
21289
- return out2.split("\0").filter(Boolean).filter((p) => p !== ".verity/standard.yaml" && !p.startsWith(".verity/memory/"));
21463
+ return out2.split("\0").filter(Boolean).filter((p) => p !== STANDARD_PATH && !p.startsWith(".verity/memory/"));
21290
21464
  }
21291
21465
  function untrackVerityState() {
21292
21466
  const tracked = committedVerityState();
21293
21467
  if (tracked.length === 0) return "none";
21468
+ const standardWasTracked = committedStandardFile();
21294
21469
  try {
21295
21470
  (0, import_node_child_process9.execSync)("git rm -r --cached --quiet -- .verity", { stdio: "pipe" });
21296
- const keeps = [".verity/standard.yaml"];
21297
- if (memoryPosture() === "committable") keeps.push(".verity/memory");
21298
- for (const keep of keeps) {
21471
+ if (standardWasTracked) {
21299
21472
  try {
21300
- (0, import_node_child_process9.execSync)(`git add -- "${keep}"`, { stdio: "pipe" });
21473
+ (0, import_node_child_process9.execSync)(`git add -f -- ${STANDARD_PATH}`, { stdio: "pipe" });
21474
+ } catch {
21475
+ }
21476
+ }
21477
+ if (memoryPosture() === "committable") {
21478
+ try {
21479
+ (0, import_node_child_process9.execSync)("git add -- .verity/memory", { stdio: "pipe" });
21301
21480
  } catch {
21302
21481
  }
21303
21482
  }
@@ -21715,6 +21894,117 @@ var LEGACY_MD_END = "<!-- gate-memory:end -->";
21715
21894
  var LEGACY_PRESERVE_START = "<!-- gate-memory:preserve -->";
21716
21895
  var LEGACY_PRESERVE_END = "<!-- /gate-memory:preserve -->";
21717
21896
  var CLAUDE_MD_PROSE = [
21897
+ "## Project Memory",
21898
+ "",
21899
+ "This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
21900
+ "non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
21901
+ "that may apply to the change you are about to make. Open specific node files via",
21902
+ "the Read tool when the title or scope suggests relevance.",
21903
+ "",
21904
+ "The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
21905
+ "superseded \u2014 ignore them unless investigating history.",
21906
+ "",
21907
+ "Knowledge the organization's other repositories learned is mirrored outside the",
21908
+ "repo at `~/.verity/orgs/<host>/<owner>/memory/` (`verity memory org` lists it).",
21909
+ "Each claim says where it came from; one that is wrong here is demoted for",
21910
+ 'everyone with `verity memory demote <id> --reason "\u2026"`.',
21911
+ "",
21912
+ "## Quality gate: accepted risks",
21913
+ "",
21914
+ "When the Verity pre-commit/pre-push gate FAILs, fix the findings \u2014 that is the",
21915
+ 'default. Use `verity waive <pattern-id> --file <path> --reason "\u2026"` ONLY to relay',
21916
+ "a risk a human has explicitly accepted: a named code-review finding, an ADR, or",
21917
+ "the user saying so in this conversation. The --reason must cite that source.",
21918
+ "",
21919
+ "Never waive on your own judgment, to get past a block, or pre-emptively. A waive",
21920
+ "binds to the file's current bytes and voids automatically when the file changes,",
21921
+ "and every waive is recorded in the run ledger. For a pattern-level false positive",
21922
+ "use `verity feedback finding <run-id> <pattern-id> false_positive` instead.",
21923
+ "",
21924
+ "## Post-task reflection",
21925
+ "",
21926
+ `When a task is complete (you've created a PR, the user says "done" or "ship it",`,
21927
+ "or the work is clearly finished), **draft the reflection yourself** \u2014 1\u20133",
21928
+ "concrete things worth remembering (a decision, a gotcha, or a pattern), each",
21929
+ "cited with the files / PR / commands / error-signatures it came from. Skip",
21930
+ "entirely if nothing non-obvious happened \u2014 that judgement is the ONLY filter,",
21931
+ "because nothing reviews the reflection before it lands.",
21932
+ "",
21933
+ "Then record it straight away. There is no confirm step, in any environment:",
21934
+ "",
21935
+ "```bash",
21936
+ "verity reflect --user-input - --kind <kind> <<'VERITY_EOF_<random>'",
21937
+ "<your draft>",
21938
+ "VERITY_EOF_<random>",
21939
+ "```",
21940
+ "",
21941
+ 'The draft goes through the quoted heredoc, never inside `"\u2026"` on the command',
21942
+ 'line, where a `"` or `$(\u2026)` in it would run in the shell. Replace `<random>`',
21943
+ "with 8 random letters and digits that you pick for this call, the same at both",
21944
+ "ends. Text written before you picked them cannot end the heredoc early.",
21945
+ "",
21946
+ "Add `--confirmed` ONLY when the user authored or dictated the words. Without",
21947
+ "it the node is stored as `source: agent` \u2014 Verity thought this, nobody checked",
21948
+ "it. With it, `source: user` at full confidence \u2014 a person stands behind this.",
21949
+ "Never claim the second for your own draft, however good it is.",
21950
+ "",
21951
+ "**Name the files in the text.** Verity scopes the reflection to the paths it",
21952
+ "cites, and a reflection that names no file in this repo is never retrieved for",
21953
+ "a later review \u2014 it is recorded and then invisible. The command says so when it",
21954
+ "happens; `--file-globs '<path or glob>'` is the fix when the prose cannot carry",
21955
+ "the paths.",
21956
+ "",
21957
+ "Then tell the user, in one line, what you recorded and where: the command",
21958
+ "prints the node id, the path under `.verity/memory/`, and a dashboard link.",
21959
+ "They did not agree to it in advance, so say it happened \u2014 editing or deleting",
21960
+ "that file is how they correct it.",
21961
+ "",
21962
+ "> Durable, hand-curated guidance goes in the preserve region below (it survives",
21963
+ "> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
21964
+ "> markers is tool-owned and overwritten on each run.",
21965
+ "",
21966
+ "## Housekeeping Turns",
21967
+ "",
21968
+ "When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
21969
+ "rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
21970
+ "",
21971
+ "```bash",
21972
+ 'verity ignore --turn --agent --reason "pulling latest before starting"',
21973
+ "```",
21974
+ "",
21975
+ "This skips the review for that turn, which saves the turn Verity would",
21976
+ "otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
21977
+ "`--turn` when a single piece of housekeeping spans several turns.",
21978
+ "",
21979
+ "**It is a claim about the turn, not a way to silence review.** The declaration",
21980
+ "is checked against what the turn actually did: if anything is authored \u2014 by you,",
21981
+ "by a subagent, or by a shell command that can write files \u2014 it voids, the review",
21982
+ "runs anyway, and the broken declaration is reported. So declare housekeeping you",
21983
+ "are about to do, never work you have already done, and never as a way to get past",
21984
+ "a finding. Declarations are budgeted per session and every one is recorded with",
21985
+ "its reason.",
21986
+ "",
21987
+ "## Verity settings",
21988
+ "",
21989
+ "Change these only when the user asks. Each command prints what it changed.",
21990
+ "",
21991
+ "- When Verity reviews: `verity config moments turn,commit,push`. Give any of the",
21992
+ " three, or `none`. With no list, it prints the current setting.",
21993
+ "- Formal verification at push: `verity config formal push`, or `verity config formal off`.",
21994
+ "- The line Verity prints when a turn has nothing new to review: `verity config verbose on`,",
21995
+ " or `verity config verbose off` (the default).",
21996
+ "- Project rules, written by Verity from this repository's history and docs:",
21997
+ " `verity standard generate`. It works only for a project with no Standard on the",
21998
+ " service. It writes `.verity/repo-profile.json` and sends nothing without consent.",
21999
+ " Show the user that file. Run the command again with `--yes` only after they agree.",
22000
+ "- The rules themselves: run `verity standard pull` first (the file here can be behind",
22001
+ " the team's), edit `.verity/standard.yaml`, then run `verity standard push`."
22002
+ ].join("\n");
22003
+ var CLAUDE_MD_PROSE_PRE_PULL = CLAUDE_MD_PROSE.replace(
22004
+ /- The rules themselves: run `verity standard pull` first \(the file here can be behind\n the team's\), edit `\.verity\/standard\.yaml`, then run `verity standard push`\.$/,
22005
+ "- The rules themselves: edit `.verity/standard.yaml`, then run `verity standard push`."
22006
+ );
22007
+ var CLAUDE_MD_PROSE_PRE_VERBOSE = [
21718
22008
  "## Project Memory",
21719
22009
  "",
21720
22010
  "This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
@@ -22351,6 +22641,8 @@ function stripKnownProse(interior) {
22351
22641
  const trimmed = interior.replace(/^\n+/, "");
22352
22642
  for (const prose of [
22353
22643
  CLAUDE_MD_PROSE,
22644
+ CLAUDE_MD_PROSE_PRE_PULL,
22645
+ CLAUDE_MD_PROSE_PRE_VERBOSE,
22354
22646
  CLAUDE_MD_PROSE_PRE_SETTINGS,
22355
22647
  CLAUDE_MD_PROSE_PRE_STDIN,
22356
22648
  CLAUDE_MD_PROSE_PRE_ORG,
@@ -25415,6 +25707,8 @@ async function generateStandard(opts) {
25415
25707
  } catch (err2) {
25416
25708
  return { status: "failed", reason: `the Standard is on the service (v${version}) but could not be written here: ${terminalText(err2.message, 200)}` };
25417
25709
  }
25710
+ await writeSetupState({ standardVersion: version }).catch(() => {
25711
+ });
25418
25712
  return {
25419
25713
  status: "created",
25420
25714
  version,
@@ -25595,68 +25889,6 @@ async function runGenerate(globals, opts) {
25595
25889
  var import_node_fs37 = require("node:fs");
25596
25890
  var import_yaml6 = __toESM2(require_dist());
25597
25891
 
25598
- // src/lib/setup-state.ts
25599
- var SETUP_STATE_FILE = `${VERITY_DIR}/setup.json`;
25600
- var MOMENTS = ["stop", "pre-commit", "pre-push"];
25601
- var INTENSITIES = ["lightweight", "balanced", "thorough"];
25602
- var TELEMETRY = ["enabled", "declined", "deferred"];
25603
- var VERSION = /^[0-9A-Za-z.+-]{1,32}$/;
25604
- var stamp = (v) => typeof v === "string" && v.length > 0 && v.length <= 64;
25605
- function setupStateFrom(raw) {
25606
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
25607
- const r = raw;
25608
- if (r.version !== 1) return null;
25609
- const out2 = { version: 1 };
25610
- if (INTENSITIES.includes(r.intensity)) out2.intensity = r.intensity;
25611
- if (Array.isArray(r.moments) && r.moments.every((m) => MOMENTS.includes(m))) {
25612
- out2.moments = [...new Set(r.moments)];
25613
- }
25614
- if (TELEMETRY.includes(r.telemetry)) out2.telemetry = r.telemetry;
25615
- if (Number.isSafeInteger(r.declinedStandardVersion) && r.declinedStandardVersion >= 1) {
25616
- out2.declinedStandardVersion = r.declinedStandardVersion;
25617
- }
25618
- const init2 = r.init;
25619
- if (init2 && typeof init2 === "object" && stamp(init2.completed_at) && typeof init2.cli_version === "string" && VERSION.test(init2.cli_version)) {
25620
- out2.init = { completed_at: init2.completed_at, cli_version: init2.cli_version };
25621
- }
25622
- const plugin = r.pluginSetup;
25623
- if (plugin && typeof plugin === "object" && typeof plugin.version === "string" && VERSION.test(plugin.version) && stamp(plugin.completed_at)) {
25624
- out2.pluginSetup = { version: plugin.version, completed_at: plugin.completed_at };
25625
- }
25626
- if (r.wiring === "project") out2.wiring = "project";
25627
- return out2;
25628
- }
25629
- var MAX_SETUP_BYTES = 64 * 1024;
25630
- function readSetupText() {
25631
- const read2 = readRegularFileInRoot(repoRoot(), SETUP_STATE_FILE, MAX_SETUP_BYTES);
25632
- return read2.ok ? read2.content : null;
25633
- }
25634
- async function readSetupState() {
25635
- const raw = readSetupText();
25636
- if (raw === null) return null;
25637
- try {
25638
- return setupStateFrom(JSON.parse(raw));
25639
- } catch {
25640
- return null;
25641
- }
25642
- }
25643
- async function writeSetupState(patch) {
25644
- const current = await readSetupState() ?? { version: 1 };
25645
- const next = { ...current, ...patch, version: 1 };
25646
- const raw = readSetupText();
25647
- if (raw !== null) {
25648
- try {
25649
- if (jsonSemanticEqual(JSON.parse(raw), next)) return next;
25650
- } catch {
25651
- }
25652
- }
25653
- const text3 = JSON.stringify(next, null, raw !== null ? detectJsonIndent(raw) : 2) + "\n";
25654
- if (!replaceInRoot(projectPath(VERITY_DIR), "setup.json", text3)) {
25655
- throw new Error(`Could not write ${SETUP_STATE_FILE}: it is a link, or ${VERITY_DIR} is not a folder of this repository`);
25656
- }
25657
- return next;
25658
- }
25659
-
25660
25892
  // src/commands/init/rules.ts
25661
25893
  async function projectLabel(token, serviceUrl, verbose) {
25662
25894
  try {
@@ -25801,6 +26033,8 @@ async function pullStandard(globals, opts = {}) {
25801
26033
  } catch {
25802
26034
  }
25803
26035
  if (local && typeof local === "object" && !Array.isArray(local) && sameStandard(local, remote.standard.content)) {
26036
+ await writeSetupState({ standardVersion: version, declinedStandardVersion: void 0 }).catch(() => {
26037
+ });
25804
26038
  return { status: "same", version };
25805
26039
  }
25806
26040
  try {
@@ -25811,7 +26045,7 @@ async function pullStandard(globals, opts = {}) {
25811
26045
  }
25812
26046
  try {
25813
26047
  const outcome = await adoptRemoteSetup(remote, { serviceUrl: url.data, projectId, expectStandard: readBytes });
25814
- await writeSetupState({ declinedStandardVersion: void 0 }).catch(() => {
26048
+ await writeSetupState({ standardVersion: version, declinedStandardVersion: void 0 }).catch(() => {
25815
26049
  });
25816
26050
  return { status: "pulled", version, backup, notes: outcome.notes };
25817
26051
  } catch (err2) {
@@ -26030,8 +26264,13 @@ function registerStandardCommands(program2) {
26030
26264
  for (const path of result.written) printInfo(` ${terminalText(path, 200)} \u2713`);
26031
26265
  for (const note of result.notes) printWarn(` ${terminalText(note, 300)}`);
26032
26266
  if (opts.push) {
26033
- const pushed = await pushStandard(globals);
26267
+ const token = await resolveToken(globals.token);
26268
+ const expected = token.ok && urlResult.ok ? await expectedLatestForPush(globals, token.data.token, urlResult.data) : void 0;
26269
+ const pushed = await pushStandard(globals, expected !== void 0 ? { expectedLatest: expected } : {});
26034
26270
  for (const line2 of pushed.lines) printInfo(` ${terminalText(line2, 600)}`);
26271
+ if (pushed.conflict) {
26272
+ printWarn(` The team's Standard is v${pushed.latest ?? "?"} now. Run verity standard pull to use it here, or verity standard push --force to replace it with this file.`);
26273
+ }
26035
26274
  if (pushed.standardVersion !== null) {
26036
26275
  await correctVerityMdVersion({
26037
26276
  standard: result.standard,
@@ -26045,7 +26284,7 @@ function registerStandardCommands(program2) {
26045
26284
  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));
26046
26285
  standard.command("pull").description("Write the team's Standard on the service into .verity/standard.yaml (keeps a copy of yours)").action(() => runPull(program2.opts()));
26047
26286
  standard.command("catalogue [pack]").description("What each review pack holds: every pack, or the rules of one (core, api, data, \u2026)").option("--json", "Print the catalogue as JSON").action((pack, opts) => runCatalogue(program2.opts(), pack, opts));
26048
- 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) => {
26287
+ standard.command("push").description("Upload the project Standard to the service, as the next version after the one this machine last saw").option("--file <path>", "Path to standard YAML file", STANDARD_FILE).option("--created-by <name>", "Who created this version", "claude-code").option("--force", "Upload even when the team's Standard moved since this machine last pulled or pushed it").action(async (opts) => {
26049
26288
  const globals = program2.opts();
26050
26289
  const tokenResult = await resolveToken(globals.token);
26051
26290
  if (!tokenResult.ok) {
@@ -26068,19 +26307,33 @@ function registerStandardCommands(program2) {
26068
26307
  process.exit(1);
26069
26308
  }
26070
26309
  if (upload.warning) printWarn(terminalText(upload.warning, 600));
26310
+ const expected = opts.force ? void 0 : await expectedLatestForPush(globals, tokenResult.data.token, urlResult.data);
26071
26311
  const result = await apiRequest({
26072
26312
  method: "POST",
26073
26313
  path: "/standards",
26074
26314
  serviceUrl: urlResult.data,
26075
26315
  token: tokenResult.data.token,
26076
- body: { content: upload.content, created_by: upload.createdBy },
26316
+ body: {
26317
+ content: upload.content,
26318
+ created_by: upload.createdBy,
26319
+ ...expected !== void 0 ? { expected_latest: expected } : {}
26320
+ },
26077
26321
  verbose: globals.verbose
26078
26322
  });
26079
26323
  if (!result.ok) {
26324
+ if (expected !== void 0 && result.http_status === 409 && result.error.startsWith("STANDARD_EXISTS")) {
26325
+ const latest = latestFromConflict(result);
26326
+ printError(latest !== null ? `The team's Standard is v${latest} now${expected !== null ? `, and this machine last saw v${expected}` : ", and this machine saw none"}. Nothing was pushed. Run "verity standard pull" first (it keeps a copy of your file), put your changes into the pulled file, then push again. To replace v${latest} with this file as it is: verity standard push --force` : `The service has no Standard for this project, but this machine last saw v${expected}. Nothing was pushed. To share this file as v1: verity standard push --force`);
26327
+ process.exit(1);
26328
+ }
26080
26329
  printError(terminalText(result.error, 300));
26081
26330
  process.exit(1);
26082
26331
  }
26083
26332
  printInfo(`Standard uploaded (version ${versionText(result.data.version)})`);
26333
+ if (Number.isSafeInteger(result.data.version) && result.data.version >= 1) {
26334
+ await writeSetupState({ standardVersion: result.data.version }).catch(() => {
26335
+ });
26336
+ }
26084
26337
  const converted = conversionLine(result.data);
26085
26338
  if (converted) {
26086
26339
  printInfo(converted);
@@ -26088,6 +26341,7 @@ function registerStandardCommands(program2) {
26088
26341
  }
26089
26342
  printJson(result.data);
26090
26343
  });
26344
+ standard.command("untrack").description(`Stop tracking ${STANDARD_FILE} in git: Verity keeps it on each machine, and reviews read the service`).action(() => runUntrack());
26091
26345
  standard.command("get").description("Get the current Standard from the service").option("--version <n>", "Specific version number").option("--resolved", "Show it as a review reads it: the catalogue packs in `extends` expanded into patterns").action(async (opts) => {
26092
26346
  const globals = program2.opts();
26093
26347
  const tokenResult = await resolveToken(globals.token);
@@ -26162,6 +26416,32 @@ function registerStandardCommands(program2) {
26162
26416
  function versionText(value) {
26163
26417
  return typeof value === "number" && Number.isSafeInteger(value) ? String(value) : "?";
26164
26418
  }
26419
+ async function expectedLatestForPush(globals, token, serviceUrl) {
26420
+ const state = await readSetupState();
26421
+ if (state?.standardVersion !== void 0) return state.standardVersion;
26422
+ const remote = await fetchRemoteSetup({ token, serviceUrl, verbose: globals.verbose });
26423
+ if (remote.status === "found") {
26424
+ printInfo(`This machine had no record of the team's Standard. It is v${remote.standard.version} on the service, and this push replaces it.`);
26425
+ return remote.standard.version;
26426
+ }
26427
+ if (remote.status === "none") return null;
26428
+ printWarn(`Could not read the team's Standard from the service (${terminalText(remote.reason, 200)}), so this push is not conditional on it.`);
26429
+ return void 0;
26430
+ }
26431
+ function runUntrack() {
26432
+ const wasTracked = committedStandardFile();
26433
+ const result = untrackStandard();
26434
+ if (!result.ok) {
26435
+ printError(result.why === "gitignore" ? "Could not write .gitignore. Nothing changed." : result.why === "still-committable" ? `Git still lets ${STANDARD_FILE} be committed after the .gitignore change, so nothing was untracked and .gitignore is as it was. A nested .gitignore re-includes it: check with git check-ignore -v --no-index ${STANDARD_FILE}` : `Could not remove ${STANDARD_FILE} from the git index. .gitignore is as it was.`);
26436
+ process.exit(1);
26437
+ }
26438
+ if (!wasTracked) {
26439
+ printInfo(`${STANDARD_FILE} is not tracked in git. Git ignores it from here on; nothing else changed.`);
26440
+ return;
26441
+ }
26442
+ printInfo(`Untracked ${STANDARD_FILE} (staged). The file stays on disk here. Commit to finish.`);
26443
+ printInfo("Teammates lose the file on their next pull: `verity standard pull` (or `verity init`) writes the team's Standard back for them. Reviews read the Standard on the service, so none is affected.");
26444
+ }
26165
26445
  var UPLOAD_MAX_BYTES = 512 * 1024;
26166
26446
  function readUploadFile(file) {
26167
26447
  const root = realOrSelf(repoRoot());
@@ -26343,6 +26623,18 @@ function registerConfigCommands(program2) {
26343
26623
  printInfo(state === "push" ? "Formal verification at push: on for this checkout (Lean must be installed \u2014 verity lean status)" : "Formal verification at push: off");
26344
26624
  if (state === "push") printInfo("The phase runs with your Verity login (a push without one is skipped and says so: not-logged-in)");
26345
26625
  });
26626
+ config.command("verbose [state]").description('Get or set the line Verity prints when a turn has nothing new to review: "on" or "off" (the default)').action((state) => {
26627
+ if (state === void 0) {
26628
+ process.stdout.write((readProjectConfig().verbose === "on" ? "on" : "off") + "\n");
26629
+ return;
26630
+ }
26631
+ if (state !== "on" && state !== "off") {
26632
+ printError(`Unrecognised state: ${state}. Use "on" or "off".`);
26633
+ process.exit(1);
26634
+ }
26635
+ writeProjectConfig({ verbose: state });
26636
+ printInfo(state === "on" ? "Verbose: on. A turn with nothing new to review now says so." : "Verbose: off. A turn with nothing new to review says nothing.");
26637
+ });
26346
26638
  config.command("push").description("Upload .codacy/codacy.config.json to the service (not used by the gate, which runs Trivy only)").option("--file <path>", "Path to config file", CODACY_CONFIG_FILE).action(async (opts) => {
26347
26639
  const globals = program2.opts();
26348
26640
  const tokenResult = await resolveToken(globals.token);
@@ -26818,8 +27110,8 @@ function timeAgo(isoDate) {
26818
27110
  }
26819
27111
  var PLAN_NAME = { free: "Free", pro: "Pro", fleet: "Fleet", enterprise: "Enterprise" };
26820
27112
  function planLine(plan, accounts) {
26821
- const teams = accounts.filter((a) => a.kind !== "personal").map((a) => `${a.name} (${PLAN_NAME[a.plan] ?? a.plan})`);
26822
- return `${PLAN_NAME[plan] ?? plan}${teams.length ? ` \xB7 member of ${teams.join(", ")}` : ""} \u2014 limits: "verity usage"`;
27113
+ const teams = accounts.filter((a) => !(a.is_owner ?? a.kind === "personal")).map((a) => `${terminalText(a.name)} (${PLAN_NAME[a.plan] ?? terminalText(a.plan)})`);
27114
+ return `${PLAN_NAME[plan] ?? terminalText(plan)}${teams.length ? ` \xB7 member of ${teams.join(", ")}` : ""} \u2014 limits: "verity usage"`;
26823
27115
  }
26824
27116
  function registerStatusCommand(program2) {
26825
27117
  program2.command("status").description("Show project quality status").option("--history", "Include recent run history").option("--limit <n>", "Number of history entries (1-100)", "5").option("--json", "Output raw JSON").action(async (opts) => {
@@ -27070,7 +27362,7 @@ function dollars(cents2) {
27070
27362
  return `$${((cents2 ?? 0) / 100).toFixed(2)}`;
27071
27363
  }
27072
27364
  function link(l) {
27073
- return (l.upgrade_url ?? "https://verity.md/upgrade").replace(/^https?:\/\//, "");
27365
+ return terminalUrl(l.upgrade_url) || "https://verity.md/upgrade";
27074
27366
  }
27075
27367
  function detail(l, now) {
27076
27368
  const reset = resetPhrase(l, now, true);
@@ -27081,7 +27373,8 @@ function detail(l, now) {
27081
27373
  return `Extra usage reached this month's ${dollars(l.limit)} cap. ${Reset}. ${way2}`;
27082
27374
  }
27083
27375
  if (l.period === "week") {
27084
- const who = shared && l.account ? `${l.account} used its` : "You used your";
27376
+ const team = shared ? terminalText(l.account) : "";
27377
+ const who = team ? `${team} used its` : "You used your";
27085
27378
  const way2 = l.budget === "free" ? `Upgrade: ${link(l)}` : shared ? `An admin can turn on extra usage, or upgrade: ${link(l)}` : `Turn on extra usage or upgrade: ${link(l)}`;
27086
27379
  return `${who} ${l.limit} ${METER[l.meter]} this week. ${Reset}. ${way2}`;
27087
27380
  }
@@ -27160,8 +27453,11 @@ function windowText(w, resetsAt, now, weekly) {
27160
27453
  function cents(n2) {
27161
27454
  return n2 === null ? "\u2014" : n2 < 100 ? `${n2}\xA2` : `$${(n2 / 100).toFixed(2)}`;
27162
27455
  }
27456
+ function ownsAccount(b) {
27457
+ return b?.account?.is_owner ?? b?.account?.kind === "personal";
27458
+ }
27163
27459
  function extraOffText(b, ui) {
27164
- if (b.account?.kind === "personal") return `off \u2014 turn it on at ${ui}/account/billing`;
27460
+ if (ownsAccount(b)) return `off \u2014 turn it on at ${ui}/account/billing`;
27165
27461
  return "off \u2014 a team admin can turn it on";
27166
27462
  }
27167
27463
  function renderBudget(b, now = /* @__PURE__ */ new Date(), ui = uiBase()) {
@@ -27177,12 +27473,14 @@ function renderBudget(b, now = /* @__PURE__ */ new Date(), ui = uiBase()) {
27177
27473
  return lines;
27178
27474
  }
27179
27475
  function budgetName(b) {
27180
- return b.account ? `${PLAN2[b.plan]} \xB7 ${b.account.name} (${b.account.kind})` : PLAN2[b.plan];
27476
+ if (!b.account) return PLAN2[b.plan];
27477
+ const name2 = terminalText(b.account.name);
27478
+ return ownsAccount(b) ? `${PLAN2[b.plan]} \xB7 ${name2} (your plan)` : `${PLAN2[b.plan]} \xB7 ${name2}`;
27181
27479
  }
27182
27480
  function payerText(b) {
27183
27481
  if (!b || !b.account) return "your Free plan";
27184
- if (b.account.kind === "personal") return `your personal ${PLAN2[b.plan]} plan`;
27185
- return `the ${PLAN2[b.plan]} plan of ${b.account.name}`;
27482
+ if (ownsAccount(b)) return `your ${PLAN2[b.plan]} plan`;
27483
+ return `the ${PLAN2[b.plan]} plan of ${terminalText(b.account.name)}`;
27186
27484
  }
27187
27485
  function renderLimits(d, now = /* @__PURE__ */ new Date()) {
27188
27486
  const ui = d.ui_url ? d.ui_url.replace(/\/+$/, "") : uiBase();
@@ -27190,10 +27488,10 @@ function renderLimits(d, now = /* @__PURE__ */ new Date()) {
27190
27488
  if (!d.enforced) lines.push(d.mode === "shadow" ? "Limits are counted, not yet enforced." : "Limits are not active.");
27191
27489
  const payer = d.budgets.find((b) => b.applies_here);
27192
27490
  if (d.team_here && !d.team_here.member) {
27193
- lines.push(d.team_here.auto_join ? `${d.team_here.name} pays for Verity in this repository. Your first review here makes you a member (${d.team_here.default_plan}).` : `${d.team_here.name} pays for Verity here for its members. An admin approves new members. Until an admin adds you, ${payerText(payer)} pays for your reviews here.`);
27491
+ lines.push(d.team_here.auto_join ? `${terminalText(d.team_here.name)} pays for Verity in this repository. Your first review here makes you a member (${terminalText(d.team_here.default_plan)}).` : `${terminalText(d.team_here.name)} pays for Verity here for its members. An admin approves new members. Until an admin adds you, ${payerText(payer)} pays for your reviews here.`);
27194
27492
  }
27195
- const hasPersonal = d.budgets.some((b) => b.account?.kind === "personal");
27196
- const shown = d.budgets.filter((b) => !(hasPersonal && b.budget === "free"));
27493
+ const hasOwn = d.budgets.some((b) => ownsAccount(b));
27494
+ const shown = d.budgets.filter((b) => !(hasOwn && b.budget === "free"));
27197
27495
  const full = payer ? shown.filter((b) => b === payer) : shown;
27198
27496
  for (const b of full) {
27199
27497
  lines.push("");
@@ -30244,7 +30542,13 @@ async function passAndExit(run4, reason, skip, kindOverride) {
30244
30542
  const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([]);
30245
30543
  const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
30246
30544
  const HUMAN_SILENT_SKIPS = /* @__PURE__ */ new Set(["command-only-turn"]);
30247
- const humanNote = HUMAN_SILENT_SKIPS.has(skip) ? null : `Verity: ${reason}`;
30545
+ const quiet = quietSkip({
30546
+ skip,
30547
+ kind: skipKind,
30548
+ nothingChanged: nothingChanged(run4),
30549
+ verbose: readVerbose()
30550
+ });
30551
+ const humanNote = HUMAN_SILENT_SKIPS.has(skip) || quiet ? null : `Verity: ${reason}`;
30248
30552
  printJsonCompact(
30249
30553
  buildHookOutput(
30250
30554
  verdict,
@@ -30257,6 +30561,22 @@ async function passAndExit(run4, reason, skip, kindOverride) {
30257
30561
  );
30258
30562
  process.exit(0);
30259
30563
  }
30564
+ var NOTHING_NEW = /* @__PURE__ */ new Set(["no-delta-since-last-review", "no-analyzable-files"]);
30565
+ var QUIET_ON_CLEAN_TREE = /* @__PURE__ */ new Set([
30566
+ "debounce",
30567
+ "verity-command",
30568
+ "bare-acknowledgment",
30569
+ "reflection-prompt",
30570
+ "skip-mode",
30571
+ "zero-increment"
30572
+ ]);
30573
+ function quietSkip(s) {
30574
+ if (s.verbose || s.kind !== "policy") return false;
30575
+ return NOTHING_NEW.has(s.skip) || QUIET_ON_CLEAN_TREE.has(s.skip) && s.nothingChanged;
30576
+ }
30577
+ function nothingChanged(run4) {
30578
+ return partitionVerityOwned(run4.changedUniverse).kept.length === 0;
30579
+ }
30260
30580
 
30261
30581
  // src/commands/analyze/phases/02-scope.ts
30262
30582
  async function scope(run4) {
@@ -31148,7 +31468,8 @@ async function evidence(run4) {
31148
31468
  const scoped = scopeToAuthored(allForReview, actionSummary);
31149
31469
  const canTrustNoneAuthored = scoped.signal === "none-authored" && authorshipIsObservable;
31150
31470
  if (canTrustNoneAuthored && !hasNonEditAuthorship(actionSummary, sessionAuthoredCode)) {
31151
- await passAndExit(run4, "No agent-authored code this turn \u2014 working-tree changes were not authored by this session", "zero-increment");
31471
+ const why = nothingChanged(run4) ? "No code changed this turn" : "No agent-authored code this turn \u2014 working-tree changes were not authored by this session";
31472
+ await passAndExit(run4, why, "zero-increment");
31152
31473
  }
31153
31474
  if (scoped.signal === "none-authored" && !authorshipIsObservable) {
31154
31475
  logEvent("none_authored_unverifiable", {
@@ -34195,15 +34516,23 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
34195
34516
  ].join("\n");
34196
34517
  return fm;
34197
34518
  }
34198
- async function runSeed(opts) {
34199
- if (!(0, import_node_fs53.existsSync)(STANDARD_FILE)) {
34200
- return { created: 0, existing: 0, failed: 0, skipped: "no_standard", candidates: [] };
34519
+ async function standardForSeed(opts) {
34520
+ if ((0, import_node_fs53.existsSync)(STANDARD_FILE)) {
34521
+ const raw = readRepoFile(STANDARD_FILE);
34522
+ if (raw === null) return null;
34523
+ try {
34524
+ const parsed = (0, import_yaml9.parse)(raw);
34525
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
34526
+ } catch {
34527
+ return null;
34528
+ }
34201
34529
  }
34202
- let standardDoc;
34203
- try {
34204
- const raw = await (0, import_promises11.readFile)(STANDARD_FILE, "utf-8");
34205
- standardDoc = (0, import_yaml9.parse)(raw);
34206
- } catch {
34530
+ const remote = await fetchRemoteSetup({ serviceUrl: opts.serviceUrl, token: opts.token, verbose: opts.verbose });
34531
+ return remote.status === "found" ? remote.standard.content : null;
34532
+ }
34533
+ async function runSeed(opts) {
34534
+ const standardDoc = await standardForSeed(opts);
34535
+ if (standardDoc === null) {
34207
34536
  return { created: 0, existing: 0, failed: 0, skipped: "no_standard", candidates: [] };
34208
34537
  }
34209
34538
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
@@ -34330,9 +34659,8 @@ async function memoryManifest(run4) {
34330
34659
  try {
34331
34660
  await ensureMemoryDir();
34332
34661
  const seedMarker = (0, import_node_path43.join)(VERITY_DIR, ".seeded");
34333
- const hasStandard = (0, import_node_fs54.existsSync)(STANDARD_FILE);
34334
34662
  const alreadyTried = (0, import_node_fs54.existsSync)(seedMarker);
34335
- if (hasStandard && !alreadyTried) {
34663
+ if (!alreadyTried) {
34336
34664
  const preManifest = await buildManifest();
34337
34665
  if (preManifest.nodes.length === 0) {
34338
34666
  try {
@@ -43225,8 +43553,8 @@ async function scaffoldStep(ui, given = {}) {
43225
43553
  rail.warn(".gitignore: could not write the Verity block");
43226
43554
  rail.line("Add it by hand: .verity/ holds copies of analyzed files, including any secret the gate flagged.");
43227
43555
  } else if (ignore === "conflict") {
43228
- rail.warn(".gitignore: git still ignores .verity/standard.yaml");
43229
- rail.line("A global or nested .gitignore covers it, so the Standard cannot be committed. Check: git check-ignore -v .verity/standard.yaml");
43556
+ rail.warn(".gitignore: git does not ignore the files under .verity/");
43557
+ rail.line("A nested .gitignore re-includes them, so copies of analyzed files, including any secret the gate flagged, can be committed. Check: git check-ignore -v .verity/.snapshot/x");
43230
43558
  } else if (ignore === "memory-tracked") {
43231
43559
  rail.warn("This project commits its knowledge graph");
43232
43560
  }
@@ -43241,15 +43569,25 @@ async function scaffoldStep(ui, given = {}) {
43241
43569
  rail.line("They show up in every diff. Stop with: verity memory untrack (or keep them on purpose: verity memory track)");
43242
43570
  }
43243
43571
  }
43572
+ if (committedStandardFile()) {
43573
+ if (given.untrackStandard) {
43574
+ const result = untrackStandard();
43575
+ if (result.ok) rail.line(`Untracked ${STANDARD_FILE} (staged). Commit to finish. Teammates get it back with: verity standard pull`);
43576
+ else rail.line(`Could not untrack ${STANDARD_FILE}. Run: verity standard untrack`, "warn");
43577
+ } else {
43578
+ rail.warn(`${STANDARD_FILE} is tracked in git`);
43579
+ rail.line("Verity keeps it on each machine now, and reviews read the Standard on the service. Stop tracking it with: verity standard untrack");
43580
+ }
43581
+ }
43244
43582
  const tracked = committedVerityState();
43245
43583
  if (tracked.length > 0) {
43246
43584
  if (given.untrackState || ui.asking && await askUntrackState(ui, tracked)) {
43247
43585
  const result = untrackVerityState();
43248
43586
  if (result === "untracked") rail.line("Untracked the Verity state files (staged). Commit to finish.");
43249
- else if (result === "failed") rail.line("Could not untrack them. Run: git rm -r --cached .verity && git add .verity/standard.yaml", "warn");
43587
+ else if (result === "failed") rail.line("Could not untrack them. Run: verity init --untrack-state", "warn");
43250
43588
  } else {
43251
43589
  rail.warn(`${tracked.length} Verity state file(s) are tracked in git`);
43252
- rail.line(`For example ${terminalText(tracked[0], 200)}. Fix: git rm -r --cached .verity && git add .verity/standard.yaml`);
43590
+ rail.line(`For example ${terminalText(tracked[0], 200)}. Fix: verity init --untrack-state`);
43253
43591
  }
43254
43592
  }
43255
43593
  retireCodacyConfig(ui);
@@ -43499,13 +43837,20 @@ function sayPacks(ui, plan) {
43499
43837
  }
43500
43838
 
43501
43839
  // src/commands/init/team-standard.ts
43840
+ function refusalOutcome(code) {
43841
+ return code === "FORBIDDEN" ? "denied" : "sign-in-needed";
43842
+ }
43502
43843
  async function lookUpTeamStandard(opts) {
43503
43844
  if (!opts.adopt) return { status: "skipped", why: "no-adopt" };
43504
43845
  const token = await resolveToken(opts.globals.token);
43505
43846
  const url = await resolveServiceUrl(opts.globals.serviceUrl);
43506
43847
  if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
43507
43848
  const found = await fetchRemoteSetup({ token: token.data.token, serviceUrl: url.data, verbose: opts.globals.verbose });
43508
- if (found.status === "unavailable") return { status: "unavailable", reason: terminalText(found.reason, 300) };
43849
+ if (found.status === "unavailable") {
43850
+ const denial = authDenialRemedy(found.reason);
43851
+ if (denial) return { status: "denied", code: denial.code, reason: terminalText(found.reason, 300) };
43852
+ return { status: "unavailable", reason: terminalText(found.reason, 300) };
43853
+ }
43509
43854
  if (found.status === "none") return { status: "none" };
43510
43855
  if (opts.previous?.declinedStandardVersion === found.standard.version) {
43511
43856
  return { status: "declined-before", version: found.standard.version, found };
@@ -43522,10 +43867,8 @@ async function writeTeamStandard(team, opts) {
43522
43867
  serviceUrl: url.data,
43523
43868
  projectId: await projectLabel(token.data.token, url.data, opts.globals.verbose)
43524
43869
  });
43525
- if (opts.previous?.declinedStandardVersion != null) {
43526
- await writeSetupState({ declinedStandardVersion: void 0 }).catch(() => {
43527
- });
43528
- }
43870
+ await writeSetupState({ standardVersion: found.standard.version, declinedStandardVersion: void 0 }).catch(() => {
43871
+ });
43529
43872
  return { status: "adopted", version: found.standard.version, summary: describeRemote(found), notes: outcome.notes };
43530
43873
  } catch (err2) {
43531
43874
  return { status: "write-failed", reason: terminalText(err2.message, 300) };
@@ -43546,6 +43889,17 @@ async function askTeamChoice(ui, team) {
43546
43889
  rail.collapse();
43547
43890
  return k === "n" ? "new" : "use";
43548
43891
  }
43892
+ async function askKeepOrPull(ui, team) {
43893
+ const { rail, screen } = ui;
43894
+ rail.active(`${STANDARD_FILE} differs from your team's Standard`);
43895
+ rail.line(`On the service: ${describeRemote(team.found)}. Reviews use that one.`);
43896
+ rail.line(`Use it here too (a copy of your file is kept), or keep your file and share it over the team's later: verity standard push`);
43897
+ rail.gap();
43898
+ rail.keys([["Enter", "use the team's"], ["k", "keep yours"]]);
43899
+ const k = await screen.key(["enter", "k"], "enter", { freshMs: 300 });
43900
+ rail.collapse();
43901
+ return k === "k" ? "keep" : "use";
43902
+ }
43549
43903
  function sayAdopted(ui, team) {
43550
43904
  ui.rail.done(`Using your team's Standard v${team.version}`);
43551
43905
  ui.rail.line(terminalText(team.summary, 300));
@@ -43614,19 +43968,19 @@ async function deriveFromCatalogue(ui, globals, plan, expectedLatest) {
43614
43968
  return { ok: false, version: null };
43615
43969
  }
43616
43970
  }
43617
- function localStandardProblem() {
43971
+ function localStandard() {
43618
43972
  const raw = readRepoFile(STANDARD_FILE);
43619
- if (raw === null) return `${STANDARD_FILE} is a link, not a regular file, or larger than 512 KB.`;
43973
+ if (raw === null) return { ok: false, problem: `${STANDARD_FILE} is a link, not a regular file, or larger than 512 KB.` };
43620
43974
  let parsed;
43621
43975
  try {
43622
43976
  parsed = (0, import_yaml10.parse)(raw);
43623
43977
  } catch (err2) {
43624
- return `${STANDARD_FILE} is not valid YAML: ${terminalText(err2 instanceof Error ? err2.message : String(err2), 200)}`;
43978
+ return { ok: false, problem: `${STANDARD_FILE} is not valid YAML: ${terminalText(err2 instanceof Error ? err2.message : String(err2), 200)}` };
43625
43979
  }
43626
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return `${STANDARD_FILE} holds no Standard.`;
43980
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { ok: false, problem: `${STANDARD_FILE} holds no Standard.` };
43627
43981
  const valid = validateStandardContent(parsed);
43628
- if (!valid.ok) return `${STANDARD_FILE} is not a Standard the service accepts: ${terminalText(valid.error, 200)}`;
43629
- return null;
43982
+ if (!valid.ok) return { ok: false, problem: `${STANDARD_FILE} is not a Standard the service accepts: ${terminalText(valid.error, 200)}` };
43983
+ return { ok: true, content: parsed };
43630
43984
  }
43631
43985
  function removeLocalStandard() {
43632
43986
  const path = projectPath(STANDARD_FILE);
@@ -43679,12 +44033,12 @@ async function standardStep(ui, ctx) {
43679
44033
  return end({ source: "adopted", version: again.version, profile: profile2 });
43680
44034
  }
43681
44035
  removeLocalStandard();
43682
- if (again.status === "write-failed" || again.status === "unavailable") rail.line(terminalText(again.reason, 300));
43683
- return { ...leaveTeamStandard(again.status === "write-failed" ? "write-failed" : "unavailable", null), profile: profile2 };
44036
+ if (again.status === "write-failed" || again.status === "unavailable" || again.status === "denied") rail.line(terminalText(again.reason, 300));
44037
+ return { ...leaveTeamStandard(again.status === "write-failed" ? "write-failed" : again.status === "denied" ? refusalOutcome(again.code) : "unavailable", null), profile: profile2 };
43684
44038
  };
43685
44039
  const leaveTeamStandard = (why, version) => {
43686
- rail.line(why === "write-failed" ? `The team's Standard${version !== null ? ` v${version}` : ""} stays in effect for reviews. Nothing was sent to the service.` : "If the team has a Standard on the service, it stays in effect for reviews. Nothing was written here or to the service.");
43687
- rail.line(why === "unavailable" ? "Run verity init again once the Verity service answers." : "Fix the cause above, then run verity init again.");
44040
+ rail.line(why === "write-failed" ? `The team's Standard${version !== null ? ` v${version}` : ""} stays in effect for reviews. Nothing was sent to the service.` : why === "denied" ? "Verity has no access to this repository, so nothing was read or written here or on the service." : why === "sign-in-needed" ? "The Verity service did not accept your sign-in, so nothing was read or written here or on the service." : "If the team has a Standard on the service, it stays in effect for reviews. Nothing was written here or to the service.");
44041
+ rail.line(why === "unavailable" ? "Run verity init again once the Verity service answers." : why === "denied" ? "Give the Verity GitHub App access to this repository (the line above says how), then run: verity login, and verity init. Verity reads your repository access from GitHub only when you sign in." : why === "sign-in-needed" ? "Sign in again: run verity login, then verity init." : "Fix the cause above, then run verity init again.");
43688
44042
  return end({ source: "none", version: null, profile: notAsked, teamInEffect: why });
43689
44043
  };
43690
44044
  const useTeam = async (team2) => {
@@ -43706,7 +44060,7 @@ async function standardStep(ui, ctx) {
43706
44060
  rail.line(`Reviews use the team's Standard v${team2.version} on the service. Fix ${STANDARD_FILE}, or replace it with the team's: verity standard pull`);
43707
44061
  return end({ source: "kept", version: team2.version, profile: notAsked });
43708
44062
  }
43709
- rail.line(team2.status === "none" ? `There is no Standard on the service either, so reviews have no rules. Fix ${STANDARD_FILE}, or delete it, then run verity init again.` : team2.status === "unavailable" ? `The Verity service did not answer (${terminalText(team2.reason, 200)}), so no Standard is confirmed. Fix ${STANDARD_FILE}, then run verity init again.` : `No Standard on the service was looked up. Fix ${STANDARD_FILE}, or sign in, then run verity init again.`);
44063
+ rail.line(team2.status === "none" ? `There is no Standard on the service either, so reviews have no rules. Fix ${STANDARD_FILE}, or delete it, then run verity init again.` : team2.status === "unavailable" ? `The Verity service did not answer (${terminalText(team2.reason, 200)}), so no Standard is confirmed. Fix ${STANDARD_FILE}, then run verity init again.` : team2.status === "denied" && refusalOutcome(team2.code) === "denied" ? `Verity has no access to this repository (${terminalText(team2.reason, 200)}). Fix ${STANDARD_FILE}, give the Verity GitHub App access, then run verity login and verity init.` : team2.status === "denied" ? `The Verity service did not accept your sign-in (${terminalText(team2.reason, 200)}). Fix ${STANDARD_FILE}, then run verity login and verity init.` : `No Standard on the service was looked up. Fix ${STANDARD_FILE}, or sign in, then run verity init again.`);
43710
44064
  return end({ source: "none", version: null, profile: notAsked });
43711
44065
  };
43712
44066
  if (ctx.standard === "new" && (0, import_node_fs77.lstatSync)(projectPath(STANDARD_FILE), { throwIfNoEntry: false })) {
@@ -43719,26 +44073,77 @@ async function standardStep(ui, ctx) {
43719
44073
  }
43720
44074
  rail.done(`Set your Standard aside ${s.gray}\xB7 a copy is in ${aside.path}${s.reset}`);
43721
44075
  }
43722
- if ((0, import_node_fs77.existsSync)(projectPath(STANDARD_FILE))) {
43723
- const problem = localStandardProblem();
43724
- if (problem !== null) return unusableHere(problem);
43725
- const sync = await pushLocalStandardIfMissing(ctx.globals);
43726
- rail.done("Kept your Standard");
43727
- 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`);
44076
+ const keptHints = async () => {
43728
44077
  if (!usesReviewPacks()) {
43729
44078
  rail.line("It does not use review packs. To move it to packs and keep your rules: verity standard push. To build a new one: verity init --standard new", "muted");
43730
44079
  }
43731
- if (sync.status === "pushed") {
43732
- 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");
43733
- for (const line2 of sync.lines) if (!line2.startsWith("Standard pushed")) rail.line(terminalText(line2, 600), "warn");
44080
+ if (!hasProjectRules() && await rulesOn(ctx.globals)) {
44081
+ rail.line("It has no project rules yet. Add them from a repo profile: verity standard generate");
44082
+ }
44083
+ };
44084
+ const keptHere = async (local) => {
44085
+ const team2 = await lookUpTeamStandard(ctx);
44086
+ if (team2.status === "found" || team2.status === "declined-before") {
44087
+ if (sameStandard(local, team2.found.standard.content)) {
44088
+ rail.done(`Kept your Standard ${s.gray}\xB7 it is the team's v${team2.version}${s.reset}`);
44089
+ rail.line(`Edit it in ${STANDARD_FILE}, then share the change: verity standard push`);
44090
+ await writeSetupState({ standardVersion: team2.version }).catch(() => {
44091
+ });
44092
+ await keptHints();
44093
+ return end({ source: "kept", version: team2.version, profile: notAsked });
44094
+ }
44095
+ const choice = ctx.standard === "use" ? "use" : team2.status === "declined-before" ? "keep" : ui.asking ? await askKeepOrPull(ui, team2) : "use";
44096
+ if (choice === "use") {
44097
+ const pulled = await pullStandard(ctx.globals);
44098
+ if (pulled.status === "pulled" || pulled.status === "same") {
44099
+ rail.done(`Using your team's Standard v${pulled.version}`);
44100
+ rail.line(terminalText(describeRemote(team2.found), 300));
44101
+ if (pulled.status === "pulled" && pulled.backup) rail.line(`Your file is kept as ${pulled.backup}`, "muted");
44102
+ rail.line(`Edit it in ${STANDARD_FILE}, then share the change: verity standard push`);
44103
+ if (pulled.status === "pulled") for (const note of pulled.notes) rail.line(terminalText(note, 300), "warn");
44104
+ return end({ source: "adopted", version: pulled.version, profile: notAsked });
44105
+ }
44106
+ rail.warn("Could not write the team's Standard here");
44107
+ rail.line(pulled.status === "failed" ? terminalText(pulled.message, 300) : `${STANDARD_FILE} changed while this ran.`);
44108
+ rail.line(`Reviews use the team's Standard v${team2.version} on the service. To write it here: verity standard pull`);
44109
+ return end({ source: "kept", version: team2.version, profile: notAsked });
44110
+ }
44111
+ rail.done(`Kept your Standard ${s.gray}\xB7 it differs from the team's v${team2.version}${s.reset}`);
44112
+ rail.line(team2.status === "declined-before" ? `This project turned down the team's Standard v${team2.version} before, so this run keeps yours. Reviews use the team's.` : "Reviews use the team's Standard on the service.");
44113
+ rail.line(`To share yours over the team's: verity standard push. To use the team's here: verity standard pull`);
44114
+ await writeSetupState({ standardVersion: team2.version }).catch(() => {
44115
+ });
44116
+ await keptHints();
44117
+ return end({ source: "kept", version: team2.version, profile: notAsked });
44118
+ }
44119
+ rail.done("Kept your Standard");
44120
+ 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`);
44121
+ let version = null;
44122
+ if (team2.status === "none") {
44123
+ const pushed = await pushStandard(ctx.globals, { expectedLatest: null });
44124
+ if (pushed.conflict) {
44125
+ rail.line("Someone shared a Standard for this repository while this ran. Reviews use theirs; to compare with yours, run verity init again, or verity standard pull.", "warn");
44126
+ } else if (pushed.standardVersion !== null) {
44127
+ version = pushed.standardVersion;
44128
+ rail.line(`It was only on this machine. It is shared now as v${version}.`, "muted");
44129
+ } else {
44130
+ rail.line("It is only on this machine:", "warn");
44131
+ }
44132
+ for (const line2 of pushed.lines) if (!line2.startsWith("Standard pushed")) rail.line(terminalText(line2, 600), "warn");
43734
44133
  }
43735
- if (sync.status === "skipped" && sync.reason === "unavailable") {
44134
+ if (team2.status === "unavailable") {
43736
44135
  rail.line(`It could not be shared: the Verity service did not answer. It is shared at the first review once the service answers, or now with: verity standard push`, "warn");
43737
44136
  }
43738
- if (!hasProjectRules() && await rulesOn(ctx.globals)) {
43739
- rail.line("It has no project rules yet. Add them from a repo profile: verity standard generate");
44137
+ if (team2.status === "denied") {
44138
+ rail.line(`It could not be compared with the team's: ${terminalText(team2.reason, 300)}`, "warn");
43740
44139
  }
43741
- return end({ source: "kept", version: sync.status === "pushed" ? sync.version : null, profile: notAsked });
44140
+ await keptHints();
44141
+ return end({ source: "kept", version, profile: notAsked });
44142
+ };
44143
+ if ((0, import_node_fs77.existsSync)(projectPath(STANDARD_FILE))) {
44144
+ const local = localStandard();
44145
+ if (!local.ok) return unusableHere(local.problem);
44146
+ return keptHere(local.content);
43742
44147
  }
43743
44148
  const team = await lookUpTeamStandard(ctx);
43744
44149
  let replaces;
@@ -43761,6 +44166,12 @@ async function standardStep(ui, ctx) {
43761
44166
  rail.line(terminalText(team.reason, 300));
43762
44167
  return leaveTeamStandard("unavailable", null);
43763
44168
  }
44169
+ if (team.status === "denied") {
44170
+ const refused2 = refusalOutcome(team.code);
44171
+ rail.warn(refused2 === "denied" ? "Verity cannot access this repository yet" : "The Verity service did not accept your sign-in");
44172
+ rail.line(terminalText(team.reason, 300));
44173
+ return leaveTeamStandard(refused2, null);
44174
+ }
43764
44175
  if (team.status === "skipped") return derived(notAsked);
43765
44176
  if (!await rulesOffered(ui, ctx.globals)) return derived(notAsked);
43766
44177
  const profile = buildRepoProfile();
@@ -43811,10 +44222,10 @@ async function standardStep(ui, ctx) {
43811
44222
  sayAdopted(ui, again);
43812
44223
  return end({ source: "adopted", version: again.version, profile: { outcome: "ok", detail: detail2 } });
43813
44224
  }
43814
- if (again.status === "unavailable" || again.status === "write-failed") {
43815
- rail.warn(again.status === "unavailable" ? "Could not read the team's new Standard from the Verity service" : "Could not write the team's Standard here");
44225
+ if (again.status === "unavailable" || again.status === "write-failed" || again.status === "denied") {
44226
+ rail.warn(again.status === "write-failed" ? "Could not write the team's Standard here" : "Could not read the team's new Standard from the Verity service");
43816
44227
  rail.line(terminalText(again.reason, 300));
43817
- return { ...leaveTeamStandard(again.status, null), profile: { outcome: "ok", detail: detail2 } };
44228
+ return { ...leaveTeamStandard(again.status === "denied" ? refusalOutcome(again.code) : again.status, null), profile: { outcome: "ok", detail: detail2 } };
43818
44229
  }
43819
44230
  }
43820
44231
  return derived({ outcome: "ok", detail: detail2 });
@@ -43830,8 +44241,8 @@ async function finishRun(ui, ctx) {
43830
44241
  sayChanged(ui, ctx.before);
43831
44242
  if (ctx.teamInEffect) {
43832
44243
  rail.end(
43833
- ctx.teamInEffect === "unavailable" ? `${s.yellow}Not finished:${s.reset} the Verity service did not answer, so this machine has no Standard yet.` : `${s.yellow}Not finished:${s.reset} the team's Standard could not be written on this machine. Reviews still use it on the service.`,
43834
- ctx.teamInEffect === "unavailable" ? "Run verity init again once the Verity service answers." : "Fix the error above, then run verity init again."
44244
+ ctx.teamInEffect === "unavailable" ? `${s.yellow}Not finished:${s.reset} the Verity service did not answer, so this machine has no Standard yet.` : ctx.teamInEffect === "denied" ? `${s.yellow}Not finished:${s.reset} Verity has no access to this repository yet, so it cannot review it.` : ctx.teamInEffect === "sign-in-needed" ? `${s.yellow}Not finished:${s.reset} the Verity service did not accept your sign-in, so this machine has no Standard yet.` : `${s.yellow}Not finished:${s.reset} the team's Standard could not be written on this machine. Reviews still use it on the service.`,
44245
+ ctx.teamInEffect === "unavailable" ? "Run verity init again once the Verity service answers." : ctx.teamInEffect === "denied" ? "Give the Verity GitHub App access to this repository, then run: verity login, and verity init. Verity reads your repository access from GitHub only when you sign in." : ctx.teamInEffect === "sign-in-needed" ? "Sign in again: run verity login, then verity init." : "Fix the error above, then run verity init again."
43835
44246
  );
43836
44247
  return;
43837
44248
  }
@@ -44227,13 +44638,14 @@ async function classifyExistingLogin(serviceUrl, remote, opts) {
44227
44638
  if (!existing.ok) return { kind: "none" };
44228
44639
  const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
44229
44640
  if (who.ok && who.data.logged_in) {
44641
+ const nudge = reverifyNudge(who.data);
44230
44642
  return {
44231
44643
  kind: "signed-in",
44232
44644
  identity: terminalText(who.data.email ?? `user #${who.data.user_id}`, 120),
44233
44645
  // Being logged in does not mean THIS repo is covered (VRT-83): grants come
44234
44646
  // from the login sweep. grant_status is non-null only when a grant exists.
44235
44647
  covered: remote ? who.data.grant_status != null : null,
44236
- nudge: reverifyNudge(who.data)
44648
+ nudge: nudge && terminalText(nudge, 200)
44237
44649
  };
44238
44650
  }
44239
44651
  if (!who.ok) {
@@ -44263,6 +44675,7 @@ function coverageLines(ui, repo, covered) {
44263
44675
  } else {
44264
44676
  ui.rail.line(`${repo} is not covered by your Verity access, so nothing syncs.`, "warn");
44265
44677
  ui.rail.line("Give the Verity GitHub App access to it, then run: verity login");
44678
+ ui.rail.line("Verity reads your repository access from GitHub only when you sign in. The new sign-in is how Verity sees the repository.", "muted");
44266
44679
  }
44267
44680
  }
44268
44681
  async function signInStep(ui, resolution, opts) {
@@ -44283,6 +44696,7 @@ async function signInStep(ui, resolution, opts) {
44283
44696
  const heal = await maybeHealServiceUrl(resolution, opts.verbose);
44284
44697
  const serviceUrl = heal.serviceUrl;
44285
44698
  const remote = gitRemote();
44699
+ let grantAccess = false;
44286
44700
  if (heal.healed) {
44287
44701
  rail.warn("The saved Verity service no longer answers");
44288
44702
  rail.line("Sign in again to repair ~/.verity/credentials.");
@@ -44290,9 +44704,14 @@ async function signInStep(ui, resolution, opts) {
44290
44704
  const state = await classifyExistingLogin(serviceUrl, remote, opts);
44291
44705
  if (state.kind === "signed-in") {
44292
44706
  rail.done(`Signed in as ${s.bold}${state.identity}${s.reset}`);
44293
- coverageLines(ui, opts.repo, state.covered);
44294
44707
  if (state.nudge) rail.line(state.nudge, "warn");
44295
- return end("already");
44708
+ if (state.covered !== false || !ui.asking) {
44709
+ coverageLines(ui, opts.repo, state.covered);
44710
+ return end("already");
44711
+ }
44712
+ rail.line(`Verity can't see ${opts.repo} yet: the Verity GitHub App has no access to it.`, "warn");
44713
+ grantAccess = true;
44714
+ rail.settle();
44296
44715
  }
44297
44716
  if (state.kind === "cached") {
44298
44717
  rail.done(state.identity ? `Signed in as ${s.bold}${state.identity}${s.reset} (cached)` : "Using your existing Verity token");
@@ -44313,16 +44732,28 @@ async function signInStep(ui, resolution, opts) {
44313
44732
  sayWhatASkipCosts(ui);
44314
44733
  return end("unattended");
44315
44734
  }
44316
- rail.active("Sign in with GitHub");
44317
- rail.line("One sign-in covers every repository on this machine.");
44318
- rail.line("Signing in does not give Verity access to the contents of your repositories.");
44735
+ if (grantAccess) {
44736
+ rail.active(`Give Verity access to ${opts.repo}`);
44737
+ rail.line("Verity reads your repository access from GitHub only when you sign in.");
44738
+ rail.line("So sign in again: Verity opens the page where you add this repository to its app, and waits.");
44739
+ rail.line("When the repository is added, Verity can see it, and this run continues.");
44740
+ } else {
44741
+ rail.active("Sign in with GitHub");
44742
+ rail.line("One sign-in covers every repository on this machine.");
44743
+ rail.line("Signing in does not give Verity access to the contents of your repositories.");
44744
+ }
44319
44745
  rail.gap();
44320
44746
  rail.keys([["Enter", "open the browser"], ["s", "skip"]]);
44321
44747
  const choice = await screen.key(["enter", "s"], "s");
44322
44748
  if (choice === "s") {
44323
44749
  rail.collapse();
44324
- rail.warn("Sign-in skipped");
44325
- sayWhatASkipCosts(ui);
44750
+ rail.warn(grantAccess ? "Access skipped" : "Sign-in skipped");
44751
+ if (grantAccess) {
44752
+ rail.line(`Verity can't review ${opts.repo} until its GitHub App has access to it.`);
44753
+ rail.line("Later: add the repository to the Verity app, then run verity login. Verity reads your repository access from GitHub only when you sign in.");
44754
+ } else {
44755
+ sayWhatASkipCosts(ui);
44756
+ }
44326
44757
  return end("declined");
44327
44758
  }
44328
44759
  const failed = (why) => {
@@ -44338,20 +44769,23 @@ async function signInStep(ui, resolution, opts) {
44338
44769
  const dc = code.data;
44339
44770
  const copied = copyToClipboard(dc.user_code);
44340
44771
  const opened = openInBrowser(dc.verification_uri);
44341
- rail.line(opened ? `Opened ${dc.verification_uri} in your browser. If no window appeared, open it and enter the code.` : `Open ${dc.verification_uri} and enter the code.`, opened ? "muted" : "plain");
44772
+ const where2 = terminalText(dc.verification_uri, 200);
44773
+ const userCode = terminalText(dc.user_code, 40);
44774
+ rail.line(opened ? `Opened ${where2} in your browser. If no window appeared, open it and enter the code.` : `Open ${where2} and enter the code.`, opened ? "muted" : "plain");
44342
44775
  const prefix = `${s.gray}\u2502${s.reset} `;
44343
44776
  const polled = await screen.breathe(
44344
44777
  prefix,
44345
- () => `Waiting for you in the browser \xB7 code ${s.bold}${dc.user_code}${s.reset}${copied ? ` ${s.gray}(copied)${s.reset}` : ""}`,
44778
+ () => `Waiting for you in the browser \xB7 code ${s.bold}${userCode}${s.reset}${copied ? ` ${s.gray}(copied)${s.reset}` : ""}`,
44346
44779
  pollDeviceToken(dc, { codeExpiresAt: Date.now() + dc.expires_in * 1e3, quiet: true })
44347
44780
  );
44348
44781
  if (!polled.ok) return failed(polled.error);
44349
44782
  let gaveUp = null;
44350
44783
  const appInstallUi = {
44351
44784
  wait: async (ctx, visible) => {
44352
- rail.line(`The Verity GitHub App cannot see ${ctx.owner}/${ctx.repo} yet. Install it${ctx.opened ? " (opened in your browser)" : ""}:`, "warn");
44353
- rail.line(ctx.installUrl, "plain");
44354
- return screen.breathe(prefix, () => `Waiting for the install on ${ctx.owner}`, visible);
44785
+ const owner = terminalText(ctx.owner, 100);
44786
+ rail.line(`The Verity GitHub App cannot see ${owner}/${terminalText(ctx.repo, 100)} yet. Install it${ctx.opened ? " (opened in your browser)" : ""}:`, "warn");
44787
+ rail.line(terminalText(ctx.installUrl, 300), "plain");
44788
+ return screen.breathe(prefix, () => `Waiting for the install on ${owner}`, visible);
44355
44789
  },
44356
44790
  gaveUp: (message) => {
44357
44791
  gaveUp = message;
@@ -44372,7 +44806,7 @@ async function signInStep(ui, resolution, opts) {
44372
44806
  if (gaveUp) rail.line(terminalText(gaveUp, 300), "warn");
44373
44807
  for (const line2 of report.slice(1)) {
44374
44808
  if (line2.level === "verbose") continue;
44375
- rail.line(line2.text.trim().replace(/^✓\s*/, ""), line2.level === "warn" ? "warn" : "muted");
44809
+ rail.line(terminalText(line2.text.trim().replace(/^✓\s*/, ""), 300), line2.level === "warn" ? "warn" : "muted");
44376
44810
  }
44377
44811
  return end("new");
44378
44812
  }
@@ -44412,7 +44846,7 @@ async function recordAnswers(ui, momentIds, telemetry, completed) {
44412
44846
  moments: momentIds,
44413
44847
  ...telemetry ? { telemetry } : {},
44414
44848
  // A failed run also clears an earlier stamp: "done" describes this checkout now.
44415
- init: completed ? { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.35.3-experimental.f980d82" : "dev" } : void 0
44849
+ init: completed ? { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.35.4" : "dev" } : void 0
44416
44850
  });
44417
44851
  } catch (err2) {
44418
44852
  ui.rail.line(`Could not record the setup answers: ${terminalText(err2.message, 200)}`, "warn");
@@ -44491,7 +44925,7 @@ async function runInit(program2, opts) {
44491
44925
  process.exit(1);
44492
44926
  }
44493
44927
  trace.mark("prerequisites", "ok");
44494
- await scaffoldStep(ui, { untrackMemory: opts.untrackMemory === true, untrackState: opts.untrackState === true });
44928
+ await scaffoldStep(ui, { untrackMemory: opts.untrackMemory === true, untrackStandard: opts.untrackStandard === true, untrackState: opts.untrackState === true });
44495
44929
  trace.mark("scaffold", "ok");
44496
44930
  await (0, import_promises17.mkdir)((0, import_node_path68.join)((0, import_node_os15.homedir)(), ".verity"), { recursive: true });
44497
44931
  await repoStep(ui, detectProject());
@@ -44542,7 +44976,7 @@ async function runInit(program2, opts) {
44542
44976
  }
44543
44977
  }
44544
44978
  function registerInitCommand(program2) {
44545
- 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));
44979
+ 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-standard", "Stop tracking .verity/standard.yaml in git: Verity keeps it on each machine (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));
44546
44980
  }
44547
44981
 
44548
44982
  // src/commands/doctor.ts
@@ -44571,17 +45005,23 @@ async function buildReport() {
44571
45005
  if (legacyOnly) {
44572
45006
  next.push(`Run "verity init" \u2014 it moves the old GATE.md install, with its Standard (${LEGACY_STANDARD_FILE}), to .verity/.`);
44573
45007
  }
45008
+ const standard = { tracked: committedStandardFile(), version: state?.standardVersion ?? null };
44574
45009
  const missing = [
44575
45010
  !artifacts.standard && !legacyOnly && ".verity/standard.yaml",
44576
45011
  !artifacts.verityMd && !legacyOnly && "VERITY.md"
44577
45012
  ].filter((x) => !!x);
44578
- if (missing.length === 2) {
44579
- next.push('Run "verity standard synthesize --push" \u2014 it derives the Standard and VERITY.md from this codebase.');
45013
+ if (!artifacts.standard && !legacyOnly) {
45014
+ next.push(standard.version !== null ? `Run "verity standard pull" \u2014 the team's Standard (v${standard.version}) is on the service, and this machine has no copy of it.` : `Run "verity init" \u2014 it writes the team's Standard from the service, or builds one when the team has none.`);
44580
45015
  } else if (missing.length > 0) {
44581
45016
  next.push(
44582
45017
  `Missing ${missing.join(" and ")}. Run "verity standard synthesize --force" to regenerate \u2014 note that replaces the Standard.`
44583
45018
  );
44584
45019
  }
45020
+ if (standard.tracked) {
45021
+ next.push(
45022
+ '.verity/standard.yaml is tracked in git. Verity keeps it on each machine now, and reviews read the Standard on the service. Run "verity standard untrack" to stop tracking it (the file stays on disk; teammates get it back with "verity standard pull").'
45023
+ );
45024
+ }
44585
45025
  const noAnalysisMoment = !hooks.stop && hooks.guardOn.length === 0;
44586
45026
  const configRead = readProjectConfig();
44587
45027
  if (configRead.refused) next.push(configRefusalMessage(configRead.refused));
@@ -44659,6 +45099,7 @@ async function buildReport() {
44659
45099
  artifacts,
44660
45100
  memory: memory2,
44661
45101
  trackedState,
45102
+ standard,
44662
45103
  next
44663
45104
  };
44664
45105
  }
@@ -44706,7 +45147,7 @@ function registerDoctorCommand(program2) {
44706
45147
  if (process.platform === "win32") printWarn(WINDOWS_STATIC_NOTE);
44707
45148
  else printInfo("Static analysis: Trivy (dependencies, malicious packages, secrets)");
44708
45149
  printInfo("Artifacts:");
44709
- printInfo(` .verity/standard.yaml: ${report.artifacts.standard ? "\u2713" : report.artifacts.legacyStandard ? `not yet (an old GATE.md Standard is at ${LEGACY_STANDARD_FILE}; verity init moves it)` : "missing"}`);
45150
+ printInfo(` .verity/standard.yaml: ${report.artifacts.standard ? report.standard.tracked ? "\u2713 (tracked in git \u2014 Verity keeps it machine-local now: verity standard untrack)" : "\u2713" : report.artifacts.legacyStandard ? `not yet (an old GATE.md Standard is at ${LEGACY_STANDARD_FILE}; verity init moves it)` : report.standard.version !== null ? `not on this machine (the team's v${report.standard.version} is on the service: verity standard pull)` : "missing"}`);
44710
45151
  printInfo(` VERITY.md: ${report.artifacts.verityMd ? "\u2713" : "missing"}`);
44711
45152
  printInfo(
44712
45153
  ` .verity/memory/: ${report.memory.trackedFiles > 0 ? `${report.memory.trackedFiles} file(s) tracked in git \u2014 they appear in every diff` : report.memory.posture === "ignored" ? "machine-local \u2713" : "not tracked"}`
@@ -46335,10 +46776,10 @@ function registerLeanCommand(program2) {
46335
46776
  }
46336
46777
 
46337
46778
  // src/cli.ts
46338
- program.name("verity").description("CLI for Verity quality gate service").version("0.35.3-experimental.f980d82").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) => {
46779
+ program.name("verity").description("CLI for Verity quality gate service").version("0.35.4").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) => {
46339
46780
  const isTelemetryHelper = actionCommand.name() === "headers" && actionCommand.parent?.name() === "telemetry";
46340
46781
  if (!isTelemetryHelper) {
46341
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.35.3-experimental.f980d82");
46782
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.35.4");
46342
46783
  }
46343
46784
  setUserNamedServiceUrl(program.opts().serviceUrl);
46344
46785
  try {