@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 +42 -0
- package/bin/verity.js +453 -205
- package/data/skills/verity-init/SKILL.md +23 -8
- package/data/skills/verity-setup/SKILL.md +12 -5
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
21141
|
-
return snapshot === true && futureState === true
|
|
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 =
|
|
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 !==
|
|
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
|
-
|
|
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 --
|
|
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:
|
|
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
|
|
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: {
|
|
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
|
|
34356
|
-
if (
|
|
34357
|
-
|
|
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
|
-
|
|
34360
|
-
|
|
34361
|
-
|
|
34362
|
-
|
|
34363
|
-
|
|
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 (
|
|
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
|
|
43386
|
-
rail.line("A
|
|
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:
|
|
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:
|
|
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
|
-
|
|
43690
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
43896
|
-
rail.line(
|
|
43897
|
-
|
|
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
|
-
|
|
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 (
|
|
43903
|
-
rail.line(
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
44772
|
-
next.push(
|
|
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.
|
|
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.
|
|
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
|
|
42
|
-
|
|
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
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
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
|
|