@codacy/verity-cli 0.35.3 β†’ 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/CHANGELOG.md CHANGED
@@ -64,6 +64,48 @@ installs are untouched.
64
64
  which is the ordinary case for someone whose first Verity install *is* the
65
65
  plugin.
66
66
 
67
+ ## [0.35.4] β€” 2026-11-10
68
+
69
+ ### πŸ“ The Standard file is machine-local
70
+
71
+ - **`.verity/standard.yaml` is no longer committed.** Reviews read the Standard
72
+ on the Verity service, and the service copy moves without the file (`verity
73
+ standard generate` on another machine, the catalogue conversion, a suggestion
74
+ accepted on the dashboard), so the committed copy was routinely stale. The
75
+ file is now this machine's working copy: `verity init` and `verity standard
76
+ pull` write it from the service, and the `.gitignore` block `verity init`
77
+ writes ignores it. A project whose file is already tracked is not converted:
78
+ `verity init` and `verity doctor` say so and name `verity standard untrack`,
79
+ which stages the removal (the file stays on disk). Teammates get the file
80
+ back with `verity standard pull`. `.verityignore` and `VERITY.md` stay
81
+ committed.
82
+ - **`verity standard push` is conditional.** It sends the version this machine
83
+ last pulled, adopted, pushed or matched, and the service writes the next
84
+ version only when that is still the latest. Otherwise nothing is written, and
85
+ the message names `verity standard pull` (pull, re-apply your changes, push
86
+ again) and `--force`, which keeps the old unconditional push. A machine with
87
+ no record reads the latest first and expects exactly that.
88
+ - **`verity init` with a Standard file here compares it with the team's.** The
89
+ same Standard: kept, and named as that version. None on the service: shared.
90
+ A different one: init asks (Enter uses the team's, `k` keeps yours), and an
91
+ unattended run uses the team's; your file is kept as
92
+ `.verity/standard.<time>.yaml`. `--standard use` is honoured here now. Before,
93
+ the file was kept without a look at the team's, so a teammate on a cloned
94
+ repository was never told it differed, and a push of it replaced the team's.
95
+ - **`verity doctor`** reports a tracked Standard file, and a missing file is no
96
+ longer "run `synthesize --push`" (which would replace the team's Standard
97
+ with a derived one from a clone): it is `verity standard pull` when this
98
+ machine knows the team's version, and `verity init` otherwise.
99
+ - **The one-time knowledge seed** reads the Standard on the service when there is
100
+ no file here, so a checkout without the file seeds its graph too.
101
+
102
+ ### 🌐 The service has a domain
103
+
104
+ - **The Verity service is `https://server.verity.md`.** A new login, and every
105
+ credential and VERITY.md that names the old `ofcamwrjwrkazqvdchko.supabase.co`
106
+ address, uses it. Nothing to do: the old address names the same service and
107
+ keeps working, and a login made against one address is accepted at the other.
108
+
67
109
  ## [0.35.3] β€” 2026-11-05
68
110
 
69
111
  ### πŸ”§ Fixes
package/bin/verity.js CHANGED
@@ -14732,7 +14732,9 @@ var SECURITY_PATTERNS = [
14732
14732
  /(^|\/)pom\.xml$/,
14733
14733
  /(^|\/)gradle\.lockfile$/
14734
14734
  ];
14735
- var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
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`;
14736
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";
@@ -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" : "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",
@@ -21094,13 +21193,15 @@ var import_node_child_process9 = require("node:child_process");
21094
21193
  var import_node_fs24 = require("node:fs");
21095
21194
  var VERITY_GITIGNORE_MARKER = "# Verity \u2014 machine-local state.";
21096
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
+ ];
21097
21202
  var VERITY_GITIGNORE_BLOCK = [
21098
- "# Verity \u2014 machine-local state. Everything in .verity/ is ignored except the",
21099
- "# shared standard, which is meant to be committed. The knowledge graph under",
21100
- "# .verity/memory/ is machine-local too: the service rebuilds it, and committing",
21101
- "# it puts generated notes in every diff and pull request.",
21203
+ ...BLOCK_COMMENT,
21102
21204
  ".verity/*",
21103
- "!.verity/standard.yaml",
21104
21205
  SETTINGS_LOCAL_IGNORE_ENTRY,
21105
21206
  ""
21106
21207
  ].join("\n");
@@ -21125,9 +21226,11 @@ var SUPERSEDED_MEMORY_LINES = /* @__PURE__ */ new Set([
21125
21226
  ".verity/memory/log.md",
21126
21227
  SUPERSEDED_COMMENT
21127
21228
  ]);
21128
- 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 = {}) {
21129
21232
  try {
21130
- (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" });
21131
21234
  return true;
21132
21235
  } catch (err2) {
21133
21236
  return err2.status === 1 ? false : null;
@@ -21135,10 +21238,9 @@ function isIgnored3(path) {
21135
21238
  }
21136
21239
  function semanticsHold() {
21137
21240
  const snapshot = isIgnored3(".verity/.snapshot/__probe__");
21138
- const standard = isIgnored3(".verity/standard.yaml");
21139
21241
  const futureState = isIgnored3(".verity/.__probe-future-state__");
21140
- if (snapshot === null || standard === null || futureState === null) return null;
21141
- return snapshot === true && futureState === true && standard === false;
21242
+ if (snapshot === null || futureState === null) return null;
21243
+ return snapshot === true && futureState === true;
21142
21244
  }
21143
21245
  function memoryPosture() {
21144
21246
  const ignored = isIgnored3(".verity/memory/domain/__probe__.md");
@@ -21155,6 +21257,22 @@ function committedMemoryFiles() {
21155
21257
  return [];
21156
21258
  }
21157
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
+ }
21158
21276
  var OPT_OUT_COMMENT_LINES = /* @__PURE__ */ new Set([
21159
21277
  MEMORY_OPT_OUT_MARKER,
21160
21278
  '# Remove these three lines (or run "verity memory untrack") to stop tracking it.'
@@ -21172,6 +21290,19 @@ function fenceMemoryLines(lines) {
21172
21290
  }
21173
21291
  return out2;
21174
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
+ }
21175
21306
  function ensureVerityGitignore() {
21176
21307
  let content = "";
21177
21308
  try {
@@ -21183,9 +21314,11 @@ function ensureVerityGitignore() {
21183
21314
  const lines = content.split("\n");
21184
21315
  const needsRepair = lines.some((l) => BREAKING_ENTRIES.has(l.trim()));
21185
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);
21186
21318
  const memoryIsCommitted = hasSupersededMemory && committedMemoryFiles().length > 0;
21319
+ const standardIsCommitted = hasSupersededStandard && committedStandardFile();
21187
21320
  const verified = (result) => semanticsHold() === false ? "conflict" : result;
21188
- if (hasMarker && !needsRepair && !hasSupersededMemory) return verified("covered");
21321
+ if (hasMarker && !needsRepair && !hasSupersededMemory && !hasSupersededStandard) return verified("covered");
21189
21322
  if (!hasMarker && !needsRepair) {
21190
21323
  if (semanticsHold() === true) return "covered";
21191
21324
  }
@@ -21194,6 +21327,9 @@ function ensureVerityGitignore() {
21194
21327
  if (needsRepair) {
21195
21328
  next = next.map((l) => BREAKING_ENTRIES.has(l.trim()) ? ".verity/*" : l);
21196
21329
  }
21330
+ if (hasSupersededStandard && !standardIsCommitted) {
21331
+ next = fenceStandardLines(next);
21332
+ }
21197
21333
  if (hasSupersededMemory && !memoryIsCommitted) {
21198
21334
  next = fenceMemoryLines(next);
21199
21335
  }
@@ -21204,7 +21340,7 @@ function ensureVerityGitignore() {
21204
21340
  }
21205
21341
  if (text3 !== content) (0, import_node_fs24.writeFileSync)(".gitignore", text3);
21206
21342
  return verified(
21207
- 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"
21208
21344
  );
21209
21345
  } catch {
21210
21346
  return "failed";
@@ -21219,7 +21355,7 @@ function untrackMemory() {
21219
21355
  return "failed";
21220
21356
  }
21221
21357
  }
21222
- function writeFencedBlock() {
21358
+ function writeFencedBlock(fence) {
21223
21359
  let content = "";
21224
21360
  try {
21225
21361
  content = (0, import_node_fs24.readFileSync)(".gitignore", "utf-8");
@@ -21227,7 +21363,8 @@ function writeFencedBlock() {
21227
21363
  }
21228
21364
  const hasMarker = content.includes(VERITY_GITIGNORE_MARKER);
21229
21365
  let lines = content.split("\n").map((l) => BREAKING_ENTRIES.has(l.trim()) ? ".verity/*" : l);
21230
- lines = fenceMemoryLines(lines);
21366
+ if (fence.standard) lines = fenceStandardLines(lines);
21367
+ if (fence.memory) lines = fenceMemoryLines(lines);
21231
21368
  let text3 = lines.join("\n");
21232
21369
  if (!hasMarker) {
21233
21370
  const sep7 = text3 === "" ? "" : text3.endsWith("\n") ? "\n" : "\n\n";
@@ -21254,7 +21391,7 @@ function fenceMemory() {
21254
21391
  } catch {
21255
21392
  }
21256
21393
  };
21257
- if (!writeFencedBlock()) return { untracked: 0, ok: false };
21394
+ if (!writeFencedBlock({ memory: true })) return { untracked: 0, ok: false };
21258
21395
  if (memoryPosture() !== "ignored") {
21259
21396
  restore();
21260
21397
  return { untracked: 0, ok: false };
@@ -21266,6 +21403,34 @@ function fenceMemory() {
21266
21403
  }
21267
21404
  return { untracked: tracked, ok: true };
21268
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
+ }
21269
21434
  function memoryOptOut() {
21270
21435
  try {
21271
21436
  return (0, import_node_fs24.readFileSync)(".gitignore", "utf-8").includes(MEMORY_OPT_OUT_MARKER);
@@ -21295,18 +21460,23 @@ function committedVerityState() {
21295
21460
  } catch {
21296
21461
  return [];
21297
21462
  }
21298
- 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/"));
21299
21464
  }
21300
21465
  function untrackVerityState() {
21301
21466
  const tracked = committedVerityState();
21302
21467
  if (tracked.length === 0) return "none";
21468
+ const standardWasTracked = committedStandardFile();
21303
21469
  try {
21304
21470
  (0, import_node_child_process9.execSync)("git rm -r --cached --quiet -- .verity", { stdio: "pipe" });
21305
- const keeps = [".verity/standard.yaml"];
21306
- if (memoryPosture() === "committable") keeps.push(".verity/memory");
21307
- for (const keep of keeps) {
21471
+ if (standardWasTracked) {
21308
21472
  try {
21309
- (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" });
21310
21480
  } catch {
21311
21481
  }
21312
21482
  }
@@ -21827,8 +21997,13 @@ var CLAUDE_MD_PROSE = [
21827
21997
  " `verity standard generate`. It works only for a project with no Standard on the",
21828
21998
  " service. It writes `.verity/repo-profile.json` and sends nothing without consent.",
21829
21999
  " Show the user that file. Run the command again with `--yes` only after they agree.",
21830
- "- The rules themselves: edit `.verity/standard.yaml`, then run `verity standard push`."
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`."
21831
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
+ );
21832
22007
  var CLAUDE_MD_PROSE_PRE_VERBOSE = [
21833
22008
  "## Project Memory",
21834
22009
  "",
@@ -22466,6 +22641,7 @@ function stripKnownProse(interior) {
22466
22641
  const trimmed = interior.replace(/^\n+/, "");
22467
22642
  for (const prose of [
22468
22643
  CLAUDE_MD_PROSE,
22644
+ CLAUDE_MD_PROSE_PRE_PULL,
22469
22645
  CLAUDE_MD_PROSE_PRE_VERBOSE,
22470
22646
  CLAUDE_MD_PROSE_PRE_SETTINGS,
22471
22647
  CLAUDE_MD_PROSE_PRE_STDIN,
@@ -25531,6 +25707,8 @@ async function generateStandard(opts) {
25531
25707
  } catch (err2) {
25532
25708
  return { status: "failed", reason: `the Standard is on the service (v${version}) but could not be written here: ${terminalText(err2.message, 200)}` };
25533
25709
  }
25710
+ await writeSetupState({ standardVersion: version }).catch(() => {
25711
+ });
25534
25712
  return {
25535
25713
  status: "created",
25536
25714
  version,
@@ -25711,68 +25889,6 @@ async function runGenerate(globals, opts) {
25711
25889
  var import_node_fs37 = require("node:fs");
25712
25890
  var import_yaml6 = __toESM2(require_dist());
25713
25891
 
25714
- // src/lib/setup-state.ts
25715
- var SETUP_STATE_FILE = `${VERITY_DIR}/setup.json`;
25716
- var MOMENTS = ["stop", "pre-commit", "pre-push"];
25717
- var INTENSITIES = ["lightweight", "balanced", "thorough"];
25718
- var TELEMETRY = ["enabled", "declined", "deferred"];
25719
- var VERSION = /^[0-9A-Za-z.+-]{1,32}$/;
25720
- var stamp = (v) => typeof v === "string" && v.length > 0 && v.length <= 64;
25721
- function setupStateFrom(raw) {
25722
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
25723
- const r = raw;
25724
- if (r.version !== 1) return null;
25725
- const out2 = { version: 1 };
25726
- if (INTENSITIES.includes(r.intensity)) out2.intensity = r.intensity;
25727
- if (Array.isArray(r.moments) && r.moments.every((m) => MOMENTS.includes(m))) {
25728
- out2.moments = [...new Set(r.moments)];
25729
- }
25730
- if (TELEMETRY.includes(r.telemetry)) out2.telemetry = r.telemetry;
25731
- if (Number.isSafeInteger(r.declinedStandardVersion) && r.declinedStandardVersion >= 1) {
25732
- out2.declinedStandardVersion = r.declinedStandardVersion;
25733
- }
25734
- const init2 = r.init;
25735
- if (init2 && typeof init2 === "object" && stamp(init2.completed_at) && typeof init2.cli_version === "string" && VERSION.test(init2.cli_version)) {
25736
- out2.init = { completed_at: init2.completed_at, cli_version: init2.cli_version };
25737
- }
25738
- const plugin = r.pluginSetup;
25739
- if (plugin && typeof plugin === "object" && typeof plugin.version === "string" && VERSION.test(plugin.version) && stamp(plugin.completed_at)) {
25740
- out2.pluginSetup = { version: plugin.version, completed_at: plugin.completed_at };
25741
- }
25742
- if (r.wiring === "project") out2.wiring = "project";
25743
- return out2;
25744
- }
25745
- var MAX_SETUP_BYTES = 64 * 1024;
25746
- function readSetupText() {
25747
- const read2 = readRegularFileInRoot(repoRoot(), SETUP_STATE_FILE, MAX_SETUP_BYTES);
25748
- return read2.ok ? read2.content : null;
25749
- }
25750
- async function readSetupState() {
25751
- const raw = readSetupText();
25752
- if (raw === null) return null;
25753
- try {
25754
- return setupStateFrom(JSON.parse(raw));
25755
- } catch {
25756
- return null;
25757
- }
25758
- }
25759
- async function writeSetupState(patch) {
25760
- const current = await readSetupState() ?? { version: 1 };
25761
- const next = { ...current, ...patch, version: 1 };
25762
- const raw = readSetupText();
25763
- if (raw !== null) {
25764
- try {
25765
- if (jsonSemanticEqual(JSON.parse(raw), next)) return next;
25766
- } catch {
25767
- }
25768
- }
25769
- const text3 = JSON.stringify(next, null, raw !== null ? detectJsonIndent(raw) : 2) + "\n";
25770
- if (!replaceInRoot(projectPath(VERITY_DIR), "setup.json", text3)) {
25771
- throw new Error(`Could not write ${SETUP_STATE_FILE}: it is a link, or ${VERITY_DIR} is not a folder of this repository`);
25772
- }
25773
- return next;
25774
- }
25775
-
25776
25892
  // src/commands/init/rules.ts
25777
25893
  async function projectLabel(token, serviceUrl, verbose) {
25778
25894
  try {
@@ -25917,6 +26033,8 @@ async function pullStandard(globals, opts = {}) {
25917
26033
  } catch {
25918
26034
  }
25919
26035
  if (local && typeof local === "object" && !Array.isArray(local) && sameStandard(local, remote.standard.content)) {
26036
+ await writeSetupState({ standardVersion: version, declinedStandardVersion: void 0 }).catch(() => {
26037
+ });
25920
26038
  return { status: "same", version };
25921
26039
  }
25922
26040
  try {
@@ -25927,7 +26045,7 @@ async function pullStandard(globals, opts = {}) {
25927
26045
  }
25928
26046
  try {
25929
26047
  const outcome = await adoptRemoteSetup(remote, { serviceUrl: url.data, projectId, expectStandard: readBytes });
25930
- await writeSetupState({ declinedStandardVersion: void 0 }).catch(() => {
26048
+ await writeSetupState({ standardVersion: version, declinedStandardVersion: void 0 }).catch(() => {
25931
26049
  });
25932
26050
  return { status: "pulled", version, backup, notes: outcome.notes };
25933
26051
  } catch (err2) {
@@ -26146,8 +26264,13 @@ function registerStandardCommands(program2) {
26146
26264
  for (const path of result.written) printInfo(` ${terminalText(path, 200)} \u2713`);
26147
26265
  for (const note of result.notes) printWarn(` ${terminalText(note, 300)}`);
26148
26266
  if (opts.push) {
26149
- 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 } : {});
26150
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
+ }
26151
26274
  if (pushed.standardVersion !== null) {
26152
26275
  await correctVerityMdVersion({
26153
26276
  standard: result.standard,
@@ -26161,7 +26284,7 @@ function registerStandardCommands(program2) {
26161
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));
26162
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()));
26163
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));
26164
- 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) => {
26165
26288
  const globals = program2.opts();
26166
26289
  const tokenResult = await resolveToken(globals.token);
26167
26290
  if (!tokenResult.ok) {
@@ -26184,19 +26307,33 @@ function registerStandardCommands(program2) {
26184
26307
  process.exit(1);
26185
26308
  }
26186
26309
  if (upload.warning) printWarn(terminalText(upload.warning, 600));
26310
+ const expected = opts.force ? void 0 : await expectedLatestForPush(globals, tokenResult.data.token, urlResult.data);
26187
26311
  const result = await apiRequest({
26188
26312
  method: "POST",
26189
26313
  path: "/standards",
26190
26314
  serviceUrl: urlResult.data,
26191
26315
  token: tokenResult.data.token,
26192
- 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
+ },
26193
26321
  verbose: globals.verbose
26194
26322
  });
26195
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
+ }
26196
26329
  printError(terminalText(result.error, 300));
26197
26330
  process.exit(1);
26198
26331
  }
26199
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
+ }
26200
26337
  const converted = conversionLine(result.data);
26201
26338
  if (converted) {
26202
26339
  printInfo(converted);
@@ -26204,6 +26341,7 @@ function registerStandardCommands(program2) {
26204
26341
  }
26205
26342
  printJson(result.data);
26206
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());
26207
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) => {
26208
26346
  const globals = program2.opts();
26209
26347
  const tokenResult = await resolveToken(globals.token);
@@ -26278,6 +26416,32 @@ function registerStandardCommands(program2) {
26278
26416
  function versionText(value) {
26279
26417
  return typeof value === "number" && Number.isSafeInteger(value) ? String(value) : "?";
26280
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
+ }
26281
26445
  var UPLOAD_MAX_BYTES = 512 * 1024;
26282
26446
  function readUploadFile(file) {
26283
26447
  const root = realOrSelf(repoRoot());
@@ -34352,15 +34516,23 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
34352
34516
  ].join("\n");
34353
34517
  return fm;
34354
34518
  }
34355
- async function runSeed(opts) {
34356
- if (!(0, import_node_fs53.existsSync)(STANDARD_FILE)) {
34357
- 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
+ }
34358
34529
  }
34359
- let standardDoc;
34360
- try {
34361
- const raw = await (0, import_promises11.readFile)(STANDARD_FILE, "utf-8");
34362
- standardDoc = (0, import_yaml9.parse)(raw);
34363
- } 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) {
34364
34536
  return { created: 0, existing: 0, failed: 0, skipped: "no_standard", candidates: [] };
34365
34537
  }
34366
34538
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
@@ -34487,9 +34659,8 @@ async function memoryManifest(run4) {
34487
34659
  try {
34488
34660
  await ensureMemoryDir();
34489
34661
  const seedMarker = (0, import_node_path43.join)(VERITY_DIR, ".seeded");
34490
- const hasStandard = (0, import_node_fs54.existsSync)(STANDARD_FILE);
34491
34662
  const alreadyTried = (0, import_node_fs54.existsSync)(seedMarker);
34492
- if (hasStandard && !alreadyTried) {
34663
+ if (!alreadyTried) {
34493
34664
  const preManifest = await buildManifest();
34494
34665
  if (preManifest.nodes.length === 0) {
34495
34666
  try {
@@ -43382,8 +43553,8 @@ async function scaffoldStep(ui, given = {}) {
43382
43553
  rail.warn(".gitignore: could not write the Verity block");
43383
43554
  rail.line("Add it by hand: .verity/ holds copies of analyzed files, including any secret the gate flagged.");
43384
43555
  } else if (ignore === "conflict") {
43385
- rail.warn(".gitignore: git still ignores .verity/standard.yaml");
43386
- 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");
43387
43558
  } else if (ignore === "memory-tracked") {
43388
43559
  rail.warn("This project commits its knowledge graph");
43389
43560
  }
@@ -43398,15 +43569,25 @@ async function scaffoldStep(ui, given = {}) {
43398
43569
  rail.line("They show up in every diff. Stop with: verity memory untrack (or keep them on purpose: verity memory track)");
43399
43570
  }
43400
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
+ }
43401
43582
  const tracked = committedVerityState();
43402
43583
  if (tracked.length > 0) {
43403
43584
  if (given.untrackState || ui.asking && await askUntrackState(ui, tracked)) {
43404
43585
  const result = untrackVerityState();
43405
43586
  if (result === "untracked") rail.line("Untracked the Verity state files (staged). Commit to finish.");
43406
- 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");
43407
43588
  } else {
43408
43589
  rail.warn(`${tracked.length} Verity state file(s) are tracked in git`);
43409
- 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`);
43410
43591
  }
43411
43592
  }
43412
43593
  retireCodacyConfig(ui);
@@ -43686,10 +43867,8 @@ async function writeTeamStandard(team, opts) {
43686
43867
  serviceUrl: url.data,
43687
43868
  projectId: await projectLabel(token.data.token, url.data, opts.globals.verbose)
43688
43869
  });
43689
- if (opts.previous?.declinedStandardVersion != null) {
43690
- await writeSetupState({ declinedStandardVersion: void 0 }).catch(() => {
43691
- });
43692
- }
43870
+ await writeSetupState({ standardVersion: found.standard.version, declinedStandardVersion: void 0 }).catch(() => {
43871
+ });
43693
43872
  return { status: "adopted", version: found.standard.version, summary: describeRemote(found), notes: outcome.notes };
43694
43873
  } catch (err2) {
43695
43874
  return { status: "write-failed", reason: terminalText(err2.message, 300) };
@@ -43710,6 +43889,17 @@ async function askTeamChoice(ui, team) {
43710
43889
  rail.collapse();
43711
43890
  return k === "n" ? "new" : "use";
43712
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
+ }
43713
43903
  function sayAdopted(ui, team) {
43714
43904
  ui.rail.done(`Using your team's Standard v${team.version}`);
43715
43905
  ui.rail.line(terminalText(team.summary, 300));
@@ -43778,19 +43968,19 @@ async function deriveFromCatalogue(ui, globals, plan, expectedLatest) {
43778
43968
  return { ok: false, version: null };
43779
43969
  }
43780
43970
  }
43781
- function localStandardProblem() {
43971
+ function localStandard() {
43782
43972
  const raw = readRepoFile(STANDARD_FILE);
43783
- 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.` };
43784
43974
  let parsed;
43785
43975
  try {
43786
43976
  parsed = (0, import_yaml10.parse)(raw);
43787
43977
  } catch (err2) {
43788
- 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)}` };
43789
43979
  }
43790
- 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.` };
43791
43981
  const valid = validateStandardContent(parsed);
43792
- if (!valid.ok) return `${STANDARD_FILE} is not a Standard the service accepts: ${terminalText(valid.error, 200)}`;
43793
- 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 };
43794
43984
  }
43795
43985
  function removeLocalStandard() {
43796
43986
  const path = projectPath(STANDARD_FILE);
@@ -43883,26 +44073,77 @@ async function standardStep(ui, ctx) {
43883
44073
  }
43884
44074
  rail.done(`Set your Standard aside ${s.gray}\xB7 a copy is in ${aside.path}${s.reset}`);
43885
44075
  }
43886
- if ((0, import_node_fs77.existsSync)(projectPath(STANDARD_FILE))) {
43887
- const problem = localStandardProblem();
43888
- if (problem !== null) return unusableHere(problem);
43889
- const sync = await pushLocalStandardIfMissing(ctx.globals);
43890
- rail.done("Kept your Standard");
43891
- 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 () => {
43892
44077
  if (!usesReviewPacks()) {
43893
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");
43894
44079
  }
43895
- if (sync.status === "pushed") {
43896
- 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");
43897
- 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 });
43898
44118
  }
43899
- if (sync.status === "skipped" && sync.reason === "unavailable") {
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");
44133
+ }
44134
+ if (team2.status === "unavailable") {
43900
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");
43901
44136
  }
43902
- if (!hasProjectRules() && await rulesOn(ctx.globals)) {
43903
- 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");
43904
44139
  }
43905
- 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);
43906
44147
  }
43907
44148
  const team = await lookUpTeamStandard(ctx);
43908
44149
  let replaces;
@@ -44605,7 +44846,7 @@ async function recordAnswers(ui, momentIds, telemetry, completed) {
44605
44846
  moments: momentIds,
44606
44847
  ...telemetry ? { telemetry } : {},
44607
44848
  // A failed run also clears an earlier stamp: "done" describes this checkout now.
44608
- init: completed ? { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.35.3" : "dev" } : void 0
44849
+ init: completed ? { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.35.4" : "dev" } : void 0
44609
44850
  });
44610
44851
  } catch (err2) {
44611
44852
  ui.rail.line(`Could not record the setup answers: ${terminalText(err2.message, 200)}`, "warn");
@@ -44684,7 +44925,7 @@ async function runInit(program2, opts) {
44684
44925
  process.exit(1);
44685
44926
  }
44686
44927
  trace.mark("prerequisites", "ok");
44687
- 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 });
44688
44929
  trace.mark("scaffold", "ok");
44689
44930
  await (0, import_promises17.mkdir)((0, import_node_path68.join)((0, import_node_os15.homedir)(), ".verity"), { recursive: true });
44690
44931
  await repoStep(ui, detectProject());
@@ -44735,7 +44976,7 @@ async function runInit(program2, opts) {
44735
44976
  }
44736
44977
  }
44737
44978
  function registerInitCommand(program2) {
44738
- 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));
44739
44980
  }
44740
44981
 
44741
44982
  // src/commands/doctor.ts
@@ -44764,17 +45005,23 @@ async function buildReport() {
44764
45005
  if (legacyOnly) {
44765
45006
  next.push(`Run "verity init" \u2014 it moves the old GATE.md install, with its Standard (${LEGACY_STANDARD_FILE}), to .verity/.`);
44766
45007
  }
45008
+ const standard = { tracked: committedStandardFile(), version: state?.standardVersion ?? null };
44767
45009
  const missing = [
44768
45010
  !artifacts.standard && !legacyOnly && ".verity/standard.yaml",
44769
45011
  !artifacts.verityMd && !legacyOnly && "VERITY.md"
44770
45012
  ].filter((x) => !!x);
44771
- if (missing.length === 2) {
44772
- 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.`);
44773
45015
  } else if (missing.length > 0) {
44774
45016
  next.push(
44775
45017
  `Missing ${missing.join(" and ")}. Run "verity standard synthesize --force" to regenerate \u2014 note that replaces the Standard.`
44776
45018
  );
44777
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
+ }
44778
45025
  const noAnalysisMoment = !hooks.stop && hooks.guardOn.length === 0;
44779
45026
  const configRead = readProjectConfig();
44780
45027
  if (configRead.refused) next.push(configRefusalMessage(configRead.refused));
@@ -44852,6 +45099,7 @@ async function buildReport() {
44852
45099
  artifacts,
44853
45100
  memory: memory2,
44854
45101
  trackedState,
45102
+ standard,
44855
45103
  next
44856
45104
  };
44857
45105
  }
@@ -44899,7 +45147,7 @@ function registerDoctorCommand(program2) {
44899
45147
  if (process.platform === "win32") printWarn(WINDOWS_STATIC_NOTE);
44900
45148
  else printInfo("Static analysis: Trivy (dependencies, malicious packages, secrets)");
44901
45149
  printInfo("Artifacts:");
44902
- 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"}`);
44903
45151
  printInfo(` VERITY.md: ${report.artifacts.verityMd ? "\u2713" : "missing"}`);
44904
45152
  printInfo(
44905
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"}`
@@ -46528,10 +46776,10 @@ function registerLeanCommand(program2) {
46528
46776
  }
46529
46777
 
46530
46778
  // src/cli.ts
46531
- program.name("verity").description("CLI for Verity quality gate service").version("0.35.3").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) => {
46532
46780
  const isTelemetryHelper = actionCommand.name() === "headers" && actionCommand.parent?.name() === "telemetry";
46533
46781
  if (!isTelemetryHelper) {
46534
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.35.3");
46782
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.35.4");
46535
46783
  }
46536
46784
  setUserNamedServiceUrl(program.opts().serviceUrl);
46537
46785
  try {
@@ -37,9 +37,15 @@ Use this one report:
37
37
  - **`hooks.versions.skew` is set** β†’ tell the user first, in one line, with
38
38
  `hooks.versions.skew.remedy`.
39
39
  - **`artifacts.standard` or `artifacts.legacyStandard` is true** β†’ this project
40
- already has a Standard here. `legacyStandard` is an old GATE.md Standard at
41
- `.gate/standard.yaml`: init moves it to `.verity/` first, then keeps it. Do not
42
- ask question 2. Init keeps the Standard and sends no profile.
40
+ already has a Standard file here. `legacyStandard` is an old GATE.md Standard at
41
+ `.gate/standard.yaml`: init moves it to `.verity/` first. Do not ask question 2,
42
+ and init sends no profile. The file is this machine's working copy of the
43
+ team's Standard: init keeps it when it is the team's or the team has none yet
44
+ (then it shares it), and writes the team's here when the two differ (the file
45
+ is kept as a copy, `.verity/standard.<time>.yaml`). Reviews read the Standard
46
+ on the service either way.
47
+ - **`standard.tracked` is true** β†’ `.verity/standard.yaml` is tracked in git, from
48
+ an install older than 0.36.0. Question 3b applies.
43
49
 
44
50
  When both are false, also run:
45
51
 
@@ -119,6 +125,14 @@ say how many files)
119
125
  | `Yes, untrack it (recommended)` | The files stay on disk. Their removal from the index is staged for you to commit. Generated notes stop appearing in every diff. |
120
126
  | `No, not now` | Nothing changes. `verity memory track` records it as deliberate. |
121
127
 
128
+ **3b β€” "Stop tracking `.verity/standard.yaml` in git?"** (only when
129
+ `standard.tracked` is true)
130
+
131
+ | Option | Description |
132
+ |---|---|
133
+ | `Yes, untrack it (recommended)` | Verity keeps the Standard on the service and writes the file on each machine (`verity init`, `verity standard pull`). The file stays on disk here; its removal from the index is staged for you to commit. Teammates lose the file on their next pull and get it back with `verity standard pull`. Reviews are not affected: they read the service. |
134
+ | `No, not now` | Nothing changes. `verity doctor` keeps reporting it; `verity standard untrack` does it later. |
135
+
122
136
  **4 β€” "Untrack committed Verity state?"** (only when `trackedState.files > 0`;
123
137
  say how many files)
124
138
 
@@ -201,7 +215,7 @@ can sign the machine in to a different server, and the plugin follows that sign-
201
215
  One command. It asks nothing:
202
216
 
203
217
  ```bash
204
- verity init --yes [--standard <use|new>] --profile <send|skip> [--telemetry <on|off>] [--untrack-memory] [--untrack-state]
218
+ verity init --yes [--standard <use|new>] --profile <send|skip> [--telemetry <on|off>] [--untrack-memory] [--untrack-standard] [--untrack-state]
205
219
  ```
206
220
 
207
221
  - Question 2a: `Use it` β†’ `--standard use`. `Create a new one from a repo
@@ -210,7 +224,7 @@ verity init --yes [--standard <use|new>] --profile <send|skip> [--telemetry <on|
210
224
  - Question 2b: `--profile send` only when the user said yes and signed in.
211
225
  Otherwise `--profile skip`.
212
226
  - When question 2 was not asked, leave `--standard` and `--profile` off.
213
- - `--untrack-memory` and `--untrack-state` only on a yes.
227
+ - `--untrack-memory`, `--untrack-standard` and `--untrack-state` only on a yes.
214
228
  - Question 5: `--telemetry on` on a yes, `--telemetry off` on a no. When it was
215
229
  not asked, leave `--telemetry` off.
216
230
  - Do not pass `--moments`. Without it, init keeps the moments the project has,
@@ -235,9 +249,10 @@ verity doctor --json
235
249
 
236
250
  Summarise from this report and from init's output, not from memory:
237
251
 
238
- - the Standard: **kept**, **adopted** from the team, **written** from the profile
239
- (with its rules), or built from the catalogue. When it is a new one that
240
- replaced the team's, say which version it replaced;
252
+ - the Standard: **kept**, **adopted** from the team (when the file here differed,
253
+ say that the old file is kept as a copy and where), **written** from the
254
+ profile (with its rules), or built from the catalogue. When it is a new one
255
+ that replaced the team's, say which version it replaced;
241
256
  - the moments in effect. Say: "Verity reviews every turn, every commit and every
242
257
  push. To change it, run `verity config moments`, or just ask me.";
243
258
  - what init removed (superseded skills, duplicate hooks). Those skill folders are
@@ -82,10 +82,13 @@ Branch on the same `doctor` output:
82
82
  - **`artifacts.standard` is true and `phases.setup.done` is true** β†’ the
83
83
  project already has a Standard, derived or adopted. Your job is the part the CLI
84
84
  leaves empty: propose 2–5 `custom_patterns` from the codebase, show them, and on
85
- the user's agreement add them to `.verity/standard.yaml` and run
86
- `verity standard push`. Do NOT rewrite the rest of the Standard β€” `standard push`
87
- auto-versions, so a full re-synthesis lands as the version everybody is then
88
- held to.
85
+ the user's agreement run `verity standard pull` first (the file here is this
86
+ machine's working copy and can be behind the team's), add them to
87
+ `.verity/standard.yaml`, and run `verity standard push`. Do NOT rewrite the rest
88
+ of the Standard β€” `standard push` auto-versions, so a full re-synthesis lands as
89
+ the version everybody is then held to. A push that answers "the team's Standard
90
+ is vN now" means someone changed it since the pull: pull again, re-apply the
91
+ patterns, push again.
89
92
  - **`artifacts.verityMd` is true and the user did not pass `--force`** β†’ already
90
93
  configured. Say "Already configured. Run `/verity-setup --force` to
91
94
  reconfigure." and stop.
@@ -249,7 +252,11 @@ The `verity` CLI handles YAML→JSON conversion automatically:
249
252
  verity standard push
250
253
  ```
251
254
 
252
- This reads `.verity/standard.yaml`, converts it to JSON, and uploads it. Verify the output shows `version: 1`.
255
+ This reads `.verity/standard.yaml`, converts it to JSON, and uploads it as the next
256
+ version after the one this machine last saw. Verify the output shows `version: 1`.
257
+ If it answers that the team's Standard moved, run `verity standard pull`, re-apply
258
+ the changes to the pulled file, and push again. The file is machine-local: do not
259
+ commit it.
253
260
 
254
261
  ### Seed the knowledge base
255
262
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.35.3",
3
+ "version": "0.35.4",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",