@codacy/verity-cli 0.35.0-experimental.3a13a5e → 0.35.0-experimental.7c5e75b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/verity.js CHANGED
@@ -23881,7 +23881,7 @@ async function readHookStdin() {
23881
23881
 
23882
23882
  // src/commands/standard.ts
23883
23883
  var import_promises10 = require("node:fs/promises");
23884
- var import_yaml6 = __toESM2(require_dist());
23884
+ var import_yaml5 = __toESM2(require_dist());
23885
23885
 
23886
23886
  // src/lib/synthesize.ts
23887
23887
  var import_node_fs35 = require("node:fs");
@@ -24869,29 +24869,8 @@ function consentLines(p) {
24869
24869
  ];
24870
24870
  }
24871
24871
 
24872
- // src/commands/standard-generate.ts
24873
- var import_yaml5 = __toESM2(require_dist());
24874
-
24875
24872
  // src/lib/generate-standard.ts
24876
24873
  var import_yaml4 = __toESM2(require_dist());
24877
- async function generationStatus(opts) {
24878
- const res = await apiRequest({
24879
- method: "GET",
24880
- path: "/standards/generate",
24881
- serviceUrl: opts.serviceUrl,
24882
- token: opts.token,
24883
- verbose: opts.verbose,
24884
- cmd: "standard_generate_status"
24885
- });
24886
- if (!res.ok) {
24887
- if (res.http_status === 404 && /Unknown route/.test(res.error)) return "unsupported";
24888
- if (res.http_status === 400 && /Version must be a positive integer/.test(res.error)) return "unsupported";
24889
- return "unknown";
24890
- }
24891
- if (res.data.enabled === true) return "on";
24892
- if (res.data.enabled === false) return "off";
24893
- return "unknown";
24894
- }
24895
24874
  var GENERATE_TIMEOUT_MS = 6e4;
24896
24875
  function rulesFrom(content, before) {
24897
24876
  const list2 = Array.isArray(content.custom_patterns) ? content.custom_patterns : [];
@@ -24990,94 +24969,6 @@ async function promptYes(question, opts) {
24990
24969
  return a === "" || a === "y" || a === "yes";
24991
24970
  }
24992
24971
 
24993
- // src/commands/standard-generate.ts
24994
- function generationBase() {
24995
- const local = readRepoFile(STANDARD_FILE);
24996
- try {
24997
- const parsed = local !== null ? (0, import_yaml5.parse)(local) : null;
24998
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : buildStandard({ detected: detectProject() }, loadCatalog());
24999
- } catch (err2) {
25000
- printError(`${STANDARD_FILE} is not valid YAML: ${err2.message}. Fix it, or delete it, and run this again.`);
25001
- process.exit(1);
25002
- }
25003
- }
25004
- async function consentToSend(profile, yes) {
25005
- try {
25006
- writeRepoFile(PROFILE_FILE, JSON.stringify(profile, null, 2) + "\n");
25007
- } catch (err2) {
25008
- printError(`Could not write ${PROFILE_FILE} for you to read: ${err2.message}`);
25009
- process.exit(1);
25010
- }
25011
- for (const line2 of consentLines(profile)) printInfo(line2);
25012
- if (yes) return true;
25013
- if (!interactive()) {
25014
- printError(`Nothing was sent. Read ${PROFILE_FILE}, then run this again with --yes to send it.`);
25015
- process.exit(1);
25016
- }
25017
- const send = await promptYes("Send the profile? [Y/n] ", { nonInteractive: false });
25018
- if (!send) printInfo("Not sent. Nothing changed.");
25019
- return send;
25020
- }
25021
- async function runGenerate(globals, opts) {
25022
- const tokenResult = await resolveToken(globals.token);
25023
- const urlResult = await resolveServiceUrl(globals.serviceUrl);
25024
- if (!tokenResult.ok || !urlResult.ok) {
25025
- printError("Sign in first: verity login. Rule generation needs your Verity account.");
25026
- process.exit(1);
25027
- }
25028
- const status = await generationStatus({ serviceUrl: urlResult.data, token: tokenResult.data.token, verbose: globals.verbose });
25029
- if (opts.status) {
25030
- process.stdout.write(`${status}
25031
- `);
25032
- return;
25033
- }
25034
- if (status === "off") {
25035
- printWarn("Rule generation is not switched on for this project yet. The built-in rules apply. Nothing was sent.");
25036
- return;
25037
- }
25038
- if (status === "unsupported") {
25039
- printWarn("This Verity service does not write project rules yet: it is older than this CLI. The built-in rules apply. Nothing was sent.");
25040
- return;
25041
- }
25042
- const base = generationBase();
25043
- const profile = buildRepoProfile();
25044
- if (!await consentToSend(profile, opts.yes === true)) return;
25045
- const spinner = startSpinner("Writing rules for this repository");
25046
- const outcome = await generateStandard({
25047
- serviceUrl: urlResult.data,
25048
- token: tokenResult.data.token,
25049
- profile,
25050
- base,
25051
- verbose: globals.verbose
25052
- });
25053
- if (outcome.status === "created") {
25054
- spinner.succeed(`${outcome.rules.length} ${outcome.rules.length === 1 ? "rule" : "rules"} for this repository \xB7 Standard v${outcome.version}`);
25055
- for (const r of outcome.rules) {
25056
- printInfo(` ${r.id}`);
25057
- printInfo(` ${r.description}`);
25058
- printInfo(` Applies: ${r.scope} \xB7 ${r.basis}`);
25059
- }
25060
- if (outcome.rejected > 0) printInfo(` ${outcome.rejected} proposed rule(s) were left out: too generic, or not safe to use.`);
25061
- printInfo(`Edit them in ${STANDARD_FILE}, then: verity standard push`);
25062
- if (outcome.warning) printWarn(outcome.warning);
25063
- return;
25064
- }
25065
- if (outcome.status === "exists") {
25066
- spinner.warn('This project already has a Standard on the Verity service. Run "verity init" to use it.');
25067
- return;
25068
- }
25069
- if (outcome.status === "disabled") {
25070
- spinner.warn("Rule generation is not switched on for this project yet. The built-in rules apply.");
25071
- return;
25072
- }
25073
- if (outcome.status === "unsupported") {
25074
- spinner.warn("This Verity service does not write project rules yet: it is older than this CLI. The built-in rules apply.");
25075
- return;
25076
- }
25077
- spinner.warn(`The rules could not be written: ${outcome.reason}`);
25078
- process.exit(1);
25079
- }
25080
-
25081
24972
  // src/commands/standard.ts
25082
24973
  function registerStandardCommands(program2) {
25083
24974
  const standard = program2.command("standard").description("Manage the project Standard");
@@ -25094,15 +24985,15 @@ function registerStandardCommands(program2) {
25094
24985
  serviceUrl: urlResult.ok ? urlResult.data : void 0
25095
24986
  });
25096
24987
  if ("refused" in result) {
25097
- printWarn(terminalText(result.refused, 300));
24988
+ printWarn(result.refused);
25098
24989
  process.exit(1);
25099
24990
  }
25100
- printInfo(` ${terminalText(describeDetected(result.detected), 400)}`);
25101
- for (const path of result.written) printInfo(` ${terminalText(path, 200)} \u2713`);
25102
- for (const note of result.notes) printWarn(` ${terminalText(note, 300)}`);
24991
+ printInfo(` ${describeDetected(result.detected)}`);
24992
+ for (const path of result.written) printInfo(` ${path} \u2713`);
24993
+ for (const note of result.notes) printWarn(` ${note}`);
25103
24994
  if (opts.push) {
25104
24995
  const pushed = await pushStandard(globals);
25105
- for (const line2 of pushed.lines) printInfo(` ${terminalText(line2, 600)}`);
24996
+ for (const line2 of pushed.lines) printInfo(` ${line2}`);
25106
24997
  if (pushed.standardVersion !== null) {
25107
24998
  await correctVerityMdVersion({
25108
24999
  standard: result.standard,
@@ -25113,7 +25004,7 @@ function registerStandardCommands(program2) {
25113
25004
  }
25114
25005
  }
25115
25006
  });
25116
- standard.command("generate").description("Have Verity write project-specific rules from a profile of this repository (asks before sending it)").option("--yes", `Send the profile without asking (you have read ${PROFILE_FILE})`).option("--status", "Print whether the service writes project rules for this project: on, off, unsupported or unknown. Sends nothing.").action((opts) => runGenerate(program2.opts(), opts));
25007
+ standard.command("generate").description("Have Verity write project-specific rules from a profile of this repository (asks before sending it)").option("--yes", `Send the profile without asking (you have read ${PROFILE_FILE})`).action((opts) => runGenerate(program2.opts(), opts));
25117
25008
  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) => {
25118
25009
  const globals = program2.opts();
25119
25010
  const tokenResult = await resolveToken(globals.token);
@@ -25135,7 +25026,7 @@ function registerStandardCommands(program2) {
25135
25026
  }
25136
25027
  let content;
25137
25028
  try {
25138
- content = (0, import_yaml6.parse)(yamlContent);
25029
+ content = (0, import_yaml5.parse)(yamlContent);
25139
25030
  } catch (err2) {
25140
25031
  printError(`Invalid YAML in ${opts.file}: ${err2.message}`);
25141
25032
  process.exit(1);
@@ -25151,10 +25042,10 @@ function registerStandardCommands(program2) {
25151
25042
  verbose: globals.verbose
25152
25043
  });
25153
25044
  if (!result.ok) {
25154
- printError(terminalText(result.error, 300));
25045
+ printError(result.error);
25155
25046
  process.exit(1);
25156
25047
  }
25157
- printInfo(`Standard uploaded (version ${versionText(result.data.version)})`);
25048
+ printInfo(`Standard uploaded (version ${result.data.version})`);
25158
25049
  printJson(result.data);
25159
25050
  });
25160
25051
  standard.command("get").description("Get the current Standard from the service").option("--version <n>", "Specific version number").action(async (opts) => {
@@ -25169,15 +25060,7 @@ function registerStandardCommands(program2) {
25169
25060
  printError(urlResult.error);
25170
25061
  process.exit(1);
25171
25062
  }
25172
- let path = "/standards/latest";
25173
- if (opts.version !== void 0) {
25174
- const version = Number(opts.version);
25175
- if (!/^[1-9][0-9]*$/.test(String(opts.version)) || !Number.isSafeInteger(version)) {
25176
- printError(`--version: "${terminalText(opts.version, 40)}" is not a Standard version. Use a positive whole number, such as --version 3.`);
25177
- process.exit(1);
25178
- }
25179
- path = `/standards/${version}`;
25180
- }
25063
+ const path = opts.version ? `/standards/${opts.version}` : "/standards/latest";
25181
25064
  const result = await apiRequest({
25182
25065
  method: "GET",
25183
25066
  path,
@@ -25186,7 +25069,7 @@ function registerStandardCommands(program2) {
25186
25069
  verbose: globals.verbose
25187
25070
  });
25188
25071
  if (!result.ok) {
25189
- printError(terminalText(result.error, 300));
25072
+ printError(result.error);
25190
25073
  process.exit(1);
25191
25074
  }
25192
25075
  printJson(result.data);
@@ -25211,24 +25094,93 @@ function registerStandardCommands(program2) {
25211
25094
  verbose: globals.verbose
25212
25095
  });
25213
25096
  if (!result.ok) {
25214
- printError(terminalText(result.error, 300));
25097
+ printError(result.error);
25215
25098
  process.exit(1);
25216
25099
  }
25217
- const versions = Array.isArray(result.data.versions) ? result.data.versions : [];
25100
+ const versions = result.data.versions;
25218
25101
  if (versions.length === 0) {
25219
25102
  printInfo("No Standard versions found.");
25220
25103
  return;
25221
25104
  }
25222
25105
  printInfo("Standard versions:");
25223
25106
  for (const v of versions) {
25224
- const date = typeof v.created_at === "string" ? v.created_at.slice(0, 19).replace("T", " ") : "unknown date";
25107
+ const date = v.created_at.slice(0, 19).replace("T", " ");
25225
25108
  const by = v.created_by ?? "unknown";
25226
- printInfo(` v${versionText(v.version)} ${terminalText(date, 40)} (by ${terminalText(by, 80)})`);
25109
+ printInfo(` v${v.version} ${date} (by ${by})`);
25227
25110
  }
25228
25111
  });
25229
25112
  }
25230
- function versionText(value) {
25231
- return typeof value === "number" && Number.isSafeInteger(value) ? String(value) : "?";
25113
+ function generationBase() {
25114
+ const local = readRepoFile(STANDARD_FILE);
25115
+ try {
25116
+ const parsed = local !== null ? (0, import_yaml5.parse)(local) : null;
25117
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : buildStandard({ detected: detectProject() }, loadCatalog());
25118
+ } catch (err2) {
25119
+ printError(`${STANDARD_FILE} is not valid YAML: ${err2.message}. Fix it, or delete it, and run this again.`);
25120
+ process.exit(1);
25121
+ }
25122
+ }
25123
+ async function consentToSend(profile, yes) {
25124
+ try {
25125
+ writeRepoFile(PROFILE_FILE, JSON.stringify(profile, null, 2) + "\n");
25126
+ } catch (err2) {
25127
+ printError(`Could not write ${PROFILE_FILE} for you to read: ${err2.message}`);
25128
+ process.exit(1);
25129
+ }
25130
+ for (const line2 of consentLines(profile)) printInfo(line2);
25131
+ if (yes) return true;
25132
+ if (!interactive()) {
25133
+ printError(`Nothing was sent. Read ${PROFILE_FILE}, then run this again with --yes to send it.`);
25134
+ process.exit(1);
25135
+ }
25136
+ const send = await promptYes("Send the profile? [Y/n] ", { nonInteractive: false });
25137
+ if (!send) printInfo("Not sent. Nothing changed.");
25138
+ return send;
25139
+ }
25140
+ async function runGenerate(globals, opts) {
25141
+ const tokenResult = await resolveToken(globals.token);
25142
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
25143
+ if (!tokenResult.ok || !urlResult.ok) {
25144
+ printError("Sign in first: verity login. Rule generation needs your Verity account.");
25145
+ process.exit(1);
25146
+ }
25147
+ const base = generationBase();
25148
+ const profile = buildRepoProfile();
25149
+ if (!await consentToSend(profile, opts.yes === true)) return;
25150
+ const spinner = startSpinner("Writing rules for this repository");
25151
+ const outcome = await generateStandard({
25152
+ serviceUrl: urlResult.data,
25153
+ token: tokenResult.data.token,
25154
+ profile,
25155
+ base,
25156
+ verbose: globals.verbose
25157
+ });
25158
+ if (outcome.status === "created") {
25159
+ spinner.succeed(`${outcome.rules.length} ${outcome.rules.length === 1 ? "rule" : "rules"} for this repository \xB7 Standard v${outcome.version}`);
25160
+ for (const r of outcome.rules) {
25161
+ printInfo(` ${r.id}`);
25162
+ printInfo(` ${r.description}`);
25163
+ printInfo(` Applies: ${r.scope} \xB7 ${r.basis}`);
25164
+ }
25165
+ if (outcome.rejected > 0) printInfo(` ${outcome.rejected} proposed rule(s) were left out: too generic, or not safe to use.`);
25166
+ printInfo(`Edit them in ${STANDARD_FILE}, then: verity standard push`);
25167
+ if (outcome.warning) printWarn(outcome.warning);
25168
+ return;
25169
+ }
25170
+ if (outcome.status === "exists") {
25171
+ spinner.warn('This project already has a Standard on the Verity service. Run "verity init" to use it.');
25172
+ return;
25173
+ }
25174
+ if (outcome.status === "disabled") {
25175
+ spinner.warn("Rule generation is not switched on for this project yet. The built-in rules apply.");
25176
+ return;
25177
+ }
25178
+ if (outcome.status === "unsupported") {
25179
+ spinner.warn("This Verity service does not write project rules yet: it is older than this CLI. The built-in rules apply.");
25180
+ return;
25181
+ }
25182
+ spinner.warn(`The rules could not be written: ${outcome.reason}`);
25183
+ process.exit(1);
25232
25184
  }
25233
25185
 
25234
25186
  // src/commands/config.ts
@@ -28366,7 +28318,7 @@ function channelSilence(input) {
28366
28318
  // src/lib/cli-version.ts
28367
28319
  function cliVersion() {
28368
28320
  try {
28369
- return true ? "0.35.0-experimental.3a13a5e" : "dev";
28321
+ return true ? "0.35.0-experimental.7c5e75b" : "dev";
28370
28322
  } catch {
28371
28323
  return "dev";
28372
28324
  }
@@ -32425,7 +32377,7 @@ async function repoContext(run4) {
32425
32377
  var import_promises13 = require("node:fs/promises");
32426
32378
  var import_node_fs50 = require("node:fs");
32427
32379
  var import_node_path39 = require("node:path");
32428
- var import_yaml7 = __toESM2(require_dist());
32380
+ var import_yaml6 = __toESM2(require_dist());
32429
32381
 
32430
32382
  // src/lib/seed.ts
32431
32383
  function extractReadmeIntro(content) {
@@ -32736,7 +32688,7 @@ async function runSeed(opts) {
32736
32688
  let standardDoc;
32737
32689
  try {
32738
32690
  const raw = await (0, import_promises13.readFile)(STANDARD_FILE, "utf-8");
32739
- standardDoc = (0, import_yaml7.parse)(raw);
32691
+ standardDoc = (0, import_yaml6.parse)(raw);
32740
32692
  } catch {
32741
32693
  return { created: 0, existing: 0, failed: 0, skipped: "no_standard", candidates: [] };
32742
32694
  }
@@ -34218,11 +34170,11 @@ async function render(run4) {
34218
34170
  const sev = String(f.severity ?? "").toLowerCase();
34219
34171
  return sev === "critical" || sev === "high";
34220
34172
  });
34221
- const fingerprint2 = findingsFingerprint(blocking);
34173
+ const fingerprint = findingsFingerprint(blocking);
34222
34174
  const prior = readBlockState(currentCommit, {
34223
34175
  newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
34224
34176
  });
34225
- const sameProblem = isSameProblem(prior.fingerprint, fingerprint2);
34177
+ const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
34226
34178
  const maxIterations = parseInt(opts.maxIterations, 10);
34227
34179
  const attempts = sameProblem ? prior.attempts + 1 : 1;
34228
34180
  const blocks = prior.blocks + 1;
@@ -34235,7 +34187,7 @@ async function render(run4) {
34235
34187
  maxIterations
34236
34188
  });
34237
34189
  if (decisionNow.block) {
34238
- writeBlockState(currentCommit, { attempts, blocks, fingerprint: fingerprint2 });
34190
+ writeBlockState(currentCommit, { attempts, blocks, fingerprint });
34239
34191
  iteration = attempts;
34240
34192
  } else {
34241
34193
  release = decisionNow.release;
@@ -34246,7 +34198,7 @@ async function render(run4) {
34246
34198
  blocks,
34247
34199
  reviewed_files: codeDelta.files.length,
34248
34200
  cycle_cut: silenced,
34249
- fingerprint: fingerprint2
34201
+ fingerprint
34250
34202
  });
34251
34203
  }
34252
34204
  }
@@ -36318,7 +36270,7 @@ async function runAgentReview(options) {
36318
36270
  // src/lib/effort.ts
36319
36271
  var import_node_fs66 = require("node:fs");
36320
36272
  var import_node_path54 = require("node:path");
36321
- var import_yaml8 = __toESM2(require_dist());
36273
+ var import_yaml7 = __toESM2(require_dist());
36322
36274
  var EFFORT_PROFILES = {
36323
36275
  default: { pushDeadlineMs: 18e4, formal: { tier: "fast", maxRounds: 2, minScore: 0.7, costCapUsd: 1, maxTargets: 1, relaxedGates: false, dailyModels: 5, dailyCostUsd: 5 } },
36324
36276
  high: { pushDeadlineMs: 22e4, formal: { tier: "deep", maxRounds: 2, minScore: 0.6, costCapUsd: 2, maxTargets: 2, relaxedGates: false, dailyModels: 15, dailyCostUsd: 15 } },
@@ -36330,7 +36282,7 @@ function asEffort(value) {
36330
36282
  function resolveEffort(root, env = process.env) {
36331
36283
  if (env.VERITY_EFFORT) return asEffort(env.VERITY_EFFORT);
36332
36284
  try {
36333
- const standard = (0, import_yaml8.parse)((0, import_node_fs66.readFileSync)((0, import_node_path54.join)(root, STANDARD_FILE), "utf8"));
36285
+ const standard = (0, import_yaml7.parse)((0, import_node_fs66.readFileSync)((0, import_node_path54.join)(root, STANDARD_FILE), "utf8"));
36334
36286
  return asEffort(standard?.process_constraints?.git_moment_effort);
36335
36287
  } catch {
36336
36288
  return "default";
@@ -38036,8 +37988,8 @@ function readVerified(root, now = Date.now()) {
38036
37988
  return [];
38037
37989
  }
38038
37990
  }
38039
- function findVerified(root, file, symbol, fingerprint2, now = Date.now()) {
38040
- return readVerified(root, now).find((r) => r.file === file && r.symbol === symbol && r.fingerprint === fingerprint2) ?? null;
37991
+ function findVerified(root, file, symbol, fingerprint, now = Date.now()) {
37992
+ return readVerified(root, now).find((r) => r.file === file && r.symbol === symbol && r.fingerprint === fingerprint) ?? null;
38041
37993
  }
38042
37994
  function recordVerified(root, rec, now = Date.now()) {
38043
37995
  const others = readVerified(root, now).filter((r) => !(r.file === rec.file && r.symbol === rec.symbol));
@@ -39970,99 +39922,14 @@ function registerWaiveCommand(program2) {
39970
39922
  }
39971
39923
 
39972
39924
  // src/commands/init/index.ts
39973
- var import_node_fs77 = require("node:fs");
39925
+ var import_node_fs76 = require("node:fs");
39974
39926
  var import_promises19 = require("node:fs/promises");
39975
- var import_node_path65 = require("node:path");
39927
+ var import_node_path64 = require("node:path");
39976
39928
 
39977
- // src/lib/init-changes.ts
39929
+ // src/lib/init-trace.ts
39978
39930
  var import_node_crypto23 = require("node:crypto");
39979
39931
  var import_node_fs69 = require("node:fs");
39980
- var import_node_os12 = require("node:os");
39981
39932
  var import_node_path57 = require("node:path");
39982
- var REPO_FILES = [
39983
- ".gitignore",
39984
- "CLAUDE.md",
39985
- ".claude/settings.json",
39986
- ".claude/settings.local.json",
39987
- STANDARD_FILE,
39988
- VERITY_MD_FILE,
39989
- CODACY_CONFIG_FILE,
39990
- ".codacy/tools-configs/eslint.config.mjs"
39991
- ];
39992
- var SKILLS_DIR = ".claude/skills";
39993
- var MAX_HASH_BYTES = 2 * 1024 * 1024;
39994
- var MAX_TREE_ENTRIES = 2e3;
39995
- function userSettingsDir(env) {
39996
- return env.CLAUDE_CONFIG_DIR || (0, import_node_path57.join)(env.HOME || (0, import_node_os12.homedir)(), ".claude");
39997
- }
39998
- function fingerprint(root, rel) {
39999
- const opened = openRegularInRoot(root, rel);
40000
- if (!opened.ok) {
40001
- if (opened.reason === "missing") return null;
40002
- if (opened.reason === "symlink") {
40003
- try {
40004
- return `link:${(0, import_node_fs69.readlinkSync)((0, import_node_path57.join)(root, rel))}`;
40005
- } catch {
40006
- return "link";
40007
- }
40008
- }
40009
- return `refused:${opened.reason}`;
40010
- }
40011
- if (opened.size > MAX_HASH_BYTES) {
40012
- closeOpened(opened.fd);
40013
- return `size:${opened.size}`;
40014
- }
40015
- try {
40016
- return (0, import_node_crypto23.createHash)("sha256").update(readOpened(opened.fd, opened.size)).digest("hex");
40017
- } catch {
40018
- return "unreadable";
40019
- }
40020
- }
40021
- function treeFingerprint(root, dirRel) {
40022
- try {
40023
- if (!(0, import_node_fs69.lstatSync)((0, import_node_path57.join)(root, dirRel)).isDirectory()) return fingerprint(root, dirRel);
40024
- } catch {
40025
- return null;
40026
- }
40027
- const parts2 = [];
40028
- const walk2 = (rel) => {
40029
- let names2;
40030
- try {
40031
- names2 = (0, import_node_fs69.readdirSync)((0, import_node_path57.join)(root, rel)).sort();
40032
- } catch {
40033
- return;
40034
- }
40035
- for (const name2 of names2) {
40036
- if (parts2.length >= MAX_TREE_ENTRIES) return;
40037
- const relPath = `${rel}/${name2}`;
40038
- let st;
40039
- try {
40040
- st = (0, import_node_fs69.lstatSync)((0, import_node_path57.join)(root, relPath));
40041
- } catch {
40042
- continue;
40043
- }
40044
- if (st.isDirectory()) walk2(relPath);
40045
- else parts2.push(`${relPath}\0${fingerprint(root, relPath)}`);
40046
- }
40047
- };
40048
- walk2(dirRel);
40049
- return (0, import_node_crypto23.createHash)("sha256").update(parts2.join("\n")).digest("hex");
40050
- }
40051
- function snapshotInitFiles(root = process.cwd(), env = process.env) {
40052
- const snap = /* @__PURE__ */ new Map();
40053
- for (const f of REPO_FILES) snap.set(f, fingerprint(root, f));
40054
- snap.set(`${SKILLS_DIR}/`, treeFingerprint(root, SKILLS_DIR));
40055
- snap.set("~/.claude/settings.json", fingerprint(userSettingsDir(env), "settings.json"));
40056
- return snap;
40057
- }
40058
- function changedSince(before, after) {
40059
- return [...after.keys()].filter((k) => before.get(k) !== after.get(k));
40060
- }
40061
-
40062
- // src/lib/init-trace.ts
40063
- var import_node_crypto24 = require("node:crypto");
40064
- var import_node_fs70 = require("node:fs");
40065
- var import_node_path58 = require("node:path");
40066
39933
  var INIT_STEPS = [
40067
39934
  "started",
40068
39935
  "prerequisites",
@@ -40108,9 +39975,9 @@ function traceDisabled(env = process.env) {
40108
39975
  }
40109
39976
  var PENDING_NAME = "init-trace.json";
40110
39977
  var MAX_PENDING_BYTES = 64 * 1024;
40111
- var verityHome2 = () => (0, import_node_path58.join)(process.env.HOME ?? "", ".verity");
39978
+ var verityHome2 = () => (0, import_node_path57.join)(process.env.HOME ?? "", ".verity");
40112
39979
  function pendingTracePath() {
40113
- return (0, import_node_path58.join)(verityHome2(), PENDING_NAME);
39980
+ return (0, import_node_path57.join)(verityHome2(), PENDING_NAME);
40114
39981
  }
40115
39982
  var RUN_ID = /^[0-9a-f]{16}$/;
40116
39983
  var CLI_VERSION = /^[0-9A-Za-z.+-]{1,40}$/;
@@ -40168,10 +40035,10 @@ function writePending(runs) {
40168
40035
  try {
40169
40036
  const kept = runs.filter((r) => r.events.length > 0).slice(-MAX_PENDING_RUNS);
40170
40037
  if (kept.length === 0) {
40171
- (0, import_node_fs70.rmSync)(pendingTracePath(), { force: true });
40038
+ (0, import_node_fs69.rmSync)(pendingTracePath(), { force: true });
40172
40039
  return;
40173
40040
  }
40174
- (0, import_node_fs70.mkdirSync)(verityHome2(), { recursive: true });
40041
+ (0, import_node_fs69.mkdirSync)(verityHome2(), { recursive: true });
40175
40042
  if (!replaceInRoot(verityHome2(), PENDING_NAME, JSON.stringify(kept))) {
40176
40043
  logEvent("init_trace_save_refused", {});
40177
40044
  }
@@ -40214,7 +40081,7 @@ function startInitTrace(opts) {
40214
40081
  if (traceDisabled()) return NOOP;
40215
40082
  const startedAt = Date.now();
40216
40083
  const current = {
40217
- run_id: (0, import_node_crypto24.randomBytes)(8).toString("hex"),
40084
+ run_id: (0, import_node_crypto23.randomBytes)(8).toString("hex"),
40218
40085
  mode: opts.mode,
40219
40086
  interactive: opts.interactive,
40220
40087
  cli_version: cliVersion(),
@@ -40864,194 +40731,32 @@ var Rail = class {
40864
40731
  }
40865
40732
  };
40866
40733
 
40867
- // src/commands/init/ui.ts
40734
+ // src/commands/migrate.ts
40868
40735
  var import_node_fs71 = require("node:fs");
40736
+ var import_node_path61 = require("node:path");
40737
+ var import_node_child_process19 = require("node:child_process");
40738
+
40739
+ // src/lib/telemetry-local.ts
40869
40740
  var import_node_path59 = require("node:path");
40870
- var import_node_os13 = require("node:os");
40871
- var import_node_child_process18 = require("node:child_process");
40872
- function createInitUi(opts) {
40873
- const term = opts.term ?? detectTerm();
40874
- const screen = new Screen(term);
40875
- const rail = new Rail(screen);
40876
- return { term, screen, rail, asking: !opts.defaultsOnly && screen.canAsk() };
40741
+
40742
+ // src/lib/telemetry-settings.ts
40743
+ var import_promises16 = require("node:fs/promises");
40744
+ var import_node_os12 = require("node:os");
40745
+ var import_node_path58 = require("node:path");
40746
+ var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
40747
+ var OTEL_HEADERS_HELPER_CMD = "verity telemetry headers";
40748
+ var LEGACY_HEADER_KEY = "OTEL_EXPORTER_OTLP_HEADERS";
40749
+ var OTLP_ENDPOINT_KEYS = [
40750
+ "OTEL_EXPORTER_OTLP_ENDPOINT",
40751
+ "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
40752
+ "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
40753
+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"
40754
+ ];
40755
+ function claudeConfigDir2() {
40756
+ return process.env.CLAUDE_CONFIG_DIR || (0, import_node_path58.join)((0, import_node_os12.homedir)(), ".claude");
40877
40757
  }
40878
- function installInterrupt(ui) {
40879
- const stop2 = () => {
40880
- ui.rail.stopped();
40881
- ui.screen.release();
40882
- process.exit(130);
40883
- };
40884
- ui.screen.startInput(stop2);
40885
- process.once("SIGINT", stop2);
40886
- }
40887
- var SEEN_FILE = ".init-seen";
40888
- function firstInitOnMachine(home = process.env.HOME || (0, import_node_os13.homedir)()) {
40889
- return !(0, import_node_fs71.existsSync)((0, import_node_path59.join)(home, ".verity", SEEN_FILE));
40890
- }
40891
- function markInitSeen(home = process.env.HOME || (0, import_node_os13.homedir)()) {
40892
- replaceInRoot((0, import_node_path59.join)(home, ".verity"), SEEN_FILE, `${(/* @__PURE__ */ new Date()).toISOString()}
40893
- `);
40894
- }
40895
- function repoLabel() {
40896
- try {
40897
- const url = (0, import_node_child_process18.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
40898
- const parsed = parseRemote(url);
40899
- if (parsed) return terminalText(`${parsed.owner}/${parsed.repo}`, 120);
40900
- } catch {
40901
- }
40902
- return terminalText((0, import_node_path59.basename)(repoRoot()), 120);
40903
- }
40904
-
40905
- // src/lib/prereqs.ts
40906
- var import_node_child_process19 = require("node:child_process");
40907
- var import_node_fs72 = require("node:fs");
40908
- var MIN_NODE_MAJOR = 20;
40909
- function which(bin) {
40910
- return whichSync(bin, { real: false });
40911
- }
40912
- function checkNode() {
40913
- const version = process.version;
40914
- const major = Number.parseInt(version.slice(1), 10);
40915
- const ok = Number.isFinite(major) && major >= MIN_NODE_MAJOR;
40916
- return {
40917
- id: "node",
40918
- label: "Node.js",
40919
- status: ok ? "ok" : "missing",
40920
- detail: version,
40921
- remedy: ok ? void 0 : `Node.js ${MIN_NODE_MAJOR}+ required \u2014 update from https://nodejs.org`,
40922
- required: true
40923
- };
40924
- }
40925
- function checkGit() {
40926
- let detail = "";
40927
- try {
40928
- detail = (0, import_node_child_process19.execSync)("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
40929
- } catch {
40930
- return {
40931
- id: "git",
40932
- label: "git",
40933
- status: "missing",
40934
- detail: "not found",
40935
- remedy: "Install git from https://git-scm.com",
40936
- required: true
40937
- };
40938
- }
40939
- return { id: "git", label: "git", status: "ok", detail, required: true };
40940
- }
40941
- function checkClaude() {
40942
- const path = which("claude");
40943
- return {
40944
- id: "claude",
40945
- label: "Claude Code",
40946
- status: path ? "ok" : "warn",
40947
- detail: path ?? "not found",
40948
- remedy: path ? void 0 : "Hooks are wired but need Claude Code to fire \u2014 https://claude.com/claude-code",
40949
- required: false
40950
- };
40951
- }
40952
- var ANALYSIS_CLI_REMEDY = "Reinstall Verity, which includes it: npm install -g @codacy/verity-cli";
40953
- function checkAnalysisCli(opts = {}) {
40954
- const base = { id: "analysis-cli", label: "@codacy/analysis-cli", required: false };
40955
- const analyzer = resolveAnalyzer(opts);
40956
- const shown = (opts.onPathDisplay ?? (() => which("codacy-analysis")))();
40957
- if (!analyzer) {
40958
- return { ...base, status: "warn", detail: "not found", remedy: `${ANALYSIS_CLI_REMEDY} (static findings are unavailable until then)` };
40959
- }
40960
- if (analyzer.source === "path") {
40961
- return { ...base, status: "ok", detail: `${shown ?? analyzer.path} (on PATH)`, source: "path" };
40962
- }
40963
- const unused = shown && !sameFile(shown, analyzer.path) ? shown : void 0;
40964
- return {
40965
- ...base,
40966
- status: "ok",
40967
- detail: `${analyzer.version} (bundled with Verity)`,
40968
- source: "bundled",
40969
- version: analyzer.version,
40970
- ...unused && { unused }
40971
- };
40972
- }
40973
- function sameFile(a, b) {
40974
- try {
40975
- return (0, import_node_fs72.realpathSync)(a) === (0, import_node_fs72.realpathSync)(b);
40976
- } catch {
40977
- return false;
40978
- }
40979
- }
40980
- var INSTALL_TIMEOUT_MS = 12e4;
40981
- function run3(command, args2, opts = {}) {
40982
- return new Promise((resolve9) => {
40983
- const child = (0, import_node_child_process19.spawn)(command, args2, {
40984
- stdio: opts.inherit ? "inherit" : "pipe",
40985
- timeout: INSTALL_TIMEOUT_MS
40986
- });
40987
- child.on("error", () => resolve9(false));
40988
- child.on("close", (code) => resolve9(code === 0));
40989
- });
40990
- }
40991
- async function installAnalysisCli(opts = {}) {
40992
- const spinner = opts.quiet ? { succeed: () => {
40993
- }, warn: () => {
40994
- }, stop: () => {
40995
- } } : startSpinner("Installing @codacy/analysis-cli");
40996
- const ok = await run3("npm", ["install", "-g", "@codacy/analysis-cli"]);
40997
- if (ok) {
40998
- const check = checkAnalysisCli();
40999
- if (check.status === "ok") {
41000
- spinner.succeed("@codacy/analysis-cli installed");
41001
- return { ...check, justInstalled: true };
41002
- }
41003
- spinner.warn("npm reported success but codacy-analysis is not on PATH");
41004
- return check;
41005
- }
41006
- spinner.stop();
41007
- {
41008
- const failed = {
41009
- id: "analysis-cli",
41010
- label: "@codacy/analysis-cli",
41011
- status: "warn",
41012
- detail: "install failed",
41013
- remedy: ANALYSIS_CLI_REMEDY,
41014
- required: false
41015
- };
41016
- if (opts.sudo === false || !process.stdin.isTTY || !process.stdout.isTTY) return failed;
41017
- process.stderr.write(" Retrying with sudo (you may be asked for your password)\u2026\n");
41018
- const sudoOk = await run3("sudo", ["npm", "install", "-g", "@codacy/analysis-cli"], { inherit: true });
41019
- if (!sudoOk) return failed;
41020
- const check = checkAnalysisCli();
41021
- return check.status === "ok" ? { ...check, justInstalled: true } : check;
41022
- }
41023
- }
41024
- async function checkPrereqs(opts = {}) {
41025
- const checks = [checkNode(), checkGit(), checkClaude()];
41026
- let analysis = checkAnalysisCli();
41027
- if (analysis.status !== "ok" && opts.install) {
41028
- analysis = await installAnalysisCli();
41029
- }
41030
- checks.push(analysis);
41031
- return { checks, blocked: checks.some((c) => c.required && c.status !== "ok") };
41032
- }
41033
-
41034
- // src/lib/telemetry-local.ts
41035
- var import_node_path61 = require("node:path");
41036
-
41037
- // src/lib/telemetry-settings.ts
41038
- var import_promises16 = require("node:fs/promises");
41039
- var import_node_os14 = require("node:os");
41040
- var import_node_path60 = require("node:path");
41041
- var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
41042
- var OTEL_HEADERS_HELPER_CMD = "verity telemetry headers";
41043
- var LEGACY_HEADER_KEY = "OTEL_EXPORTER_OTLP_HEADERS";
41044
- var OTLP_ENDPOINT_KEYS = [
41045
- "OTEL_EXPORTER_OTLP_ENDPOINT",
41046
- "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
41047
- "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
41048
- "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"
41049
- ];
41050
- function claudeConfigDir2() {
41051
- return process.env.CLAUDE_CONFIG_DIR || (0, import_node_path60.join)((0, import_node_os14.homedir)(), ".claude");
41052
- }
41053
- function userSettingsPath() {
41054
- return (0, import_node_path60.join)(claudeConfigDir2(), "settings.json");
40758
+ function userSettingsPath() {
40759
+ return (0, import_node_path58.join)(claudeConfigDir2(), "settings.json");
41055
40760
  }
41056
40761
  function deriveOtlpEndpoint(serviceUrl) {
41057
40762
  return serviceUrl.replace(/\/+$/, "").replace(/\/v1$/, "") + "/v1/otlp";
@@ -41082,7 +40787,7 @@ function envOf(settings) {
41082
40787
  return env && typeof env === "object" && !Array.isArray(env) ? env : {};
41083
40788
  }
41084
40789
  function displayPath(path) {
41085
- const home = (0, import_node_os14.homedir)();
40790
+ const home = (0, import_node_os12.homedir)();
41086
40791
  return home && path.startsWith(home + "/") ? "~" + path.slice(home.length) : path;
41087
40792
  }
41088
40793
  async function readSettingsFile(path) {
@@ -41172,19 +40877,19 @@ async function healLocalTelemetry(root = repoRoot()) {
41172
40877
  if (settings.env && Object.keys(env).length === 0) delete settings.env;
41173
40878
  if (plan.helper) delete settings.otelHeadersHelper;
41174
40879
  const next = nextLocalBytes(local.raw, settings);
41175
- if (next !== null && !replaceInRoot((0, import_node_path61.join)(root, ".claude"), "settings.local.json", next)) {
40880
+ if (next !== null && !replaceInRoot((0, import_node_path59.join)(root, ".claude"), "settings.local.json", next)) {
41176
40881
  return { removed: [] };
41177
40882
  }
41178
40883
  return { removed: [...plan.envKeys, ...plan.helper ? ["otelHeadersHelper"] : []] };
41179
40884
  }
41180
40885
 
41181
40886
  // src/lib/telemetry-gate.ts
41182
- var import_node_child_process20 = require("node:child_process");
41183
- var import_node_fs73 = require("node:fs");
41184
- var import_node_path62 = require("node:path");
40887
+ var import_node_child_process18 = require("node:child_process");
40888
+ var import_node_fs70 = require("node:fs");
40889
+ var import_node_path60 = require("node:path");
41185
40890
  var PROJECT_MARKERS = ["setup.json", "config.json"];
41186
40891
  function writtenOnThisMachine(root, rel) {
41187
- const r = (0, import_node_child_process20.spawnSync)("git", hardenedGitArgv(["ls-files", "--error-unmatch", "--", rel]), {
40892
+ const r = (0, import_node_child_process18.spawnSync)("git", hardenedGitArgv(["ls-files", "--error-unmatch", "--", rel]), {
41188
40893
  cwd: root,
41189
40894
  env: hardenedGitEnv(),
41190
40895
  encoding: "utf-8",
@@ -41198,10 +40903,10 @@ function writtenOnThisMachine(root, rel) {
41198
40903
  function isTelemetryProject() {
41199
40904
  for (const root of [repoRoot(), mainWorktreeRoot()]) {
41200
40905
  if (!root) continue;
41201
- const dir = (0, import_node_path62.join)(root, VERITY_DIR);
40906
+ const dir = (0, import_node_path60.join)(root, VERITY_DIR);
41202
40907
  if (isMachineStore(dir)) continue;
41203
40908
  for (const marker of PROJECT_MARKERS) {
41204
- if ((0, import_node_fs73.existsSync)((0, import_node_path62.join)(dir, marker)) && writtenOnThisMachine(root, `${VERITY_DIR}/${marker}`)) return true;
40909
+ if ((0, import_node_fs70.existsSync)((0, import_node_path60.join)(dir, marker)) && writtenOnThisMachine(root, `${VERITY_DIR}/${marker}`)) return true;
41205
40910
  }
41206
40911
  }
41207
40912
  return false;
@@ -41330,1319 +41035,1301 @@ async function uninstallTelemetry() {
41330
41035
  return { ok: true, data: { removed, settingsPath: path, foreignEndpoint, healedLocalKeys: healed.removed } };
41331
41036
  }
41332
41037
 
41333
- // src/commands/init/steps.ts
41334
- function languageShares(d) {
41335
- const total = d.languages.reduce((n2, l) => n2 + (d.languageCounts[l] ?? 0), 0);
41336
- if (total === 0) return [];
41337
- const shares = d.languages.map((l) => ({ name: displayName(l), pct: Math.round((d.languageCounts[l] ?? 0) / total * 100) }));
41338
- const top = shares.slice(0, 2);
41339
- const rest = 100 - top.reduce((n2, s) => n2 + s.pct, 0);
41340
- return rest > 0 && shares.length > 2 ? [...top, { name: "Other", pct: rest }] : top;
41038
+ // src/commands/migrate.ts
41039
+ var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
41040
+ function defaultNpmRemover(pkg) {
41041
+ (0, import_node_child_process19.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
41341
41042
  }
41342
- var NAMES = {
41343
- typescript: "TypeScript",
41344
- javascript: "JavaScript",
41345
- python: "Python",
41346
- go: "Go",
41347
- java: "Java",
41348
- kotlin: "Kotlin",
41349
- ruby: "Ruby",
41350
- rust: "Rust",
41351
- c: "C",
41352
- cpp: "C++",
41353
- shell: "Shell",
41354
- dockerfile: "Dockerfile",
41355
- csharp: "C#",
41356
- php: "PHP",
41357
- swift: "Swift",
41358
- scala: "Scala",
41359
- sql: "SQL"
41360
- };
41361
- var displayName = (l) => NAMES[l] ?? l.charAt(0).toUpperCase() + l.slice(1);
41362
- var STACK_NAMES = {
41363
- "next.js": "Next.js",
41364
- next: "Next.js",
41365
- react: "React",
41366
- vue: "Vue",
41367
- angular: "Angular",
41368
- svelte: "Svelte",
41369
- express: "Express",
41370
- nestjs: "NestJS",
41371
- fastify: "Fastify",
41372
- prisma: "Prisma",
41373
- django: "Django",
41374
- flask: "Flask",
41375
- fastapi: "FastAPI",
41376
- rails: "Rails",
41377
- spring: "Spring",
41378
- vitest: "Vitest",
41379
- jest: "Jest",
41380
- mocha: "Mocha",
41381
- playwright: "Playwright",
41382
- cypress: "Cypress",
41383
- supabase: "Supabase",
41384
- stripe: "Stripe",
41385
- tailwind: "Tailwind"
41386
- };
41387
- async function repoStep(ui, detected) {
41388
- ui.rail.done("Looked at your repository");
41389
- await ui.rail.languages(languageShares(detected));
41390
- const stack = [...detected.frameworks, detected.buildSystem, detected.testFramework].filter(Boolean).map((n2) => STACK_NAMES[n2.toLowerCase()] ?? n2);
41391
- const files = `${detected.filesScanned.toLocaleString("en-US")} ${detected.filesScanned === 1 ? "file" : "files"}`;
41392
- ui.rail.line([...stack, files].join(" \xB7 "), stack.length ? "plain" : "muted");
41393
- ui.rail.gap();
41394
- ui.rail.settle();
41043
+ function git2(cwd, args2) {
41044
+ (0, import_node_child_process19.execFileSync)("git", hardenedGitArgv(args2), { cwd, stdio: "pipe", env: hardenedGitEnv() });
41395
41045
  }
41396
- async function prerequisitesStep(ui) {
41397
- const report = await checkPrereqs({ install: false });
41398
- const analysis = report.checks.find((c) => c.id === "analysis-cli");
41399
- if (analysis && analysis.status !== "ok") {
41400
- const s = ui.screen.s;
41401
- const installed2 = await ui.screen.spin(`${s.gray}\u2502${s.reset} `, () => "Verity's copy of the Codacy analysis CLI is missing. Installing it (it runs the Trivy security scan)", installAnalysisCli({ quiet: true, sudo: false }));
41402
- report.checks[report.checks.indexOf(analysis)] = installed2;
41403
- if (installed2.status === "ok") ui.rail.ticked("Installed the Codacy analysis CLI");
41046
+ function isGitTracked(cwd, relPath) {
41047
+ try {
41048
+ git2(cwd, ["ls-files", "--error-unmatch", "--", relPath]);
41049
+ return true;
41050
+ } catch {
41051
+ return false;
41404
41052
  }
41405
- for (const c of report.checks) {
41406
- if (c.status === "ok") continue;
41407
- ui.rail.warn(terminalText(`${c.label}: ${c.detail}`, 300));
41408
- if (c.remedy) ui.rail.line(terminalText(c.remedy, 300));
41053
+ }
41054
+ function isGitRepo(cwd) {
41055
+ try {
41056
+ git2(cwd, ["rev-parse", "--is-inside-work-tree"]);
41057
+ return true;
41058
+ } catch {
41059
+ return false;
41409
41060
  }
41410
- ui.rail.settle();
41411
- return !report.checks.some((c) => c.required && c.status !== "ok");
41412
41061
  }
41413
- async function telemetryStep(ui, answer, resumeDeferred, globals, previous) {
41414
- if (answer === "already-on") return "enabled";
41415
- if (answer === "no") return "declined";
41416
- if (answer === "turn-off") {
41417
- const removed = await uninstallTelemetry();
41418
- if (removed.ok) ui.rail.done("Cost & usage telemetry turned off (--telemetry off)");
41419
- else ui.rail.warn(`Could not turn telemetry off: ${terminalText(removed.error, 200)}`);
41420
- ui.rail.settle();
41421
- return "declined";
41062
+ async function runMigration(opts = {}) {
41063
+ const root = opts.cwd ?? repoRoot();
41064
+ const home = opts.home ?? process.env.HOME ?? "";
41065
+ const npmRemover = opts.npmRemover ?? defaultNpmRemover;
41066
+ const actions = [];
41067
+ const movedProjectDir = migrateProjectDir(root, actions);
41068
+ migrateGlobalCredentials(home, actions);
41069
+ await migrateLegacyHooks(root, actions);
41070
+ await migrateClaudeMd(root, actions);
41071
+ migrateStandardFile(root, actions);
41072
+ await migrateLocalTelemetry(root, actions);
41073
+ removeLegacyPackage(movedProjectDir, npmRemover, actions);
41074
+ return { actions, migrated: actions.length > 0 };
41075
+ }
41076
+ function migrateProjectDir(root, actions) {
41077
+ const gateDir = (0, import_node_path61.join)(root, ".gate");
41078
+ const verityDir = (0, import_node_path61.join)(root, ".verity");
41079
+ if ((0, import_node_fs71.existsSync)(gateDir) && !(0, import_node_fs71.existsSync)(verityDir)) {
41080
+ return migrateProjectDirRename(root, gateDir, verityDir, actions);
41422
41081
  }
41423
- if (answer === "not-asked" && !resumeDeferred) {
41424
- if (!ui.asking || previous === "declined") return void 0;
41425
- if (!(await resolveToken(globals.token)).ok) return void 0;
41426
- if ((await checkTelemetry()).enabled) return "enabled";
41427
- if (!await askTelemetry(ui)) {
41428
- ui.rail.line("Telemetry stays off. Turn it on later: verity telemetry install", "muted");
41429
- ui.rail.settle();
41430
- return "declined";
41431
- }
41082
+ if ((0, import_node_fs71.existsSync)(gateDir) && (0, import_node_fs71.existsSync)(verityDir)) {
41083
+ return migrateProjectDirCarry(gateDir, verityDir, actions);
41432
41084
  }
41433
- const token = await resolveToken(globals.token);
41434
- const url = await resolveServiceUrl(globals.serviceUrl);
41435
- if (!token.ok || !url.ok) {
41436
- ui.rail.line("Telemetry needs sign-in. Then run: verity telemetry install", "warn");
41437
- ui.rail.settle();
41438
- return "deferred";
41085
+ return false;
41086
+ }
41087
+ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
41088
+ let moved = false;
41089
+ if (isGitRepo(root) && isGitTracked(root, ".gate")) {
41090
+ if (hasStagedChanges(root)) {
41091
+ printWarn(
41092
+ "Migrating .gate \u2192 .verity with `git mv` while the index has staged changes \u2014 the rename will be staged alongside them. Review `git status` before committing."
41093
+ );
41094
+ }
41095
+ try {
41096
+ git2(root, ["mv", "--", ".gate", ".verity"]);
41097
+ actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
41098
+ moved = true;
41099
+ } catch {
41100
+ }
41439
41101
  }
41440
- const installed2 = await installTelemetry(url.data);
41441
- if (installed2.ok) {
41442
- ui.rail.done("Cost & usage telemetry on");
41443
- ui.rail.line("It starts with your next Claude Code session. See cost and usage at /usage.");
41444
- } else {
41445
- ui.rail.warn(`Could not turn telemetry on: ${terminalText(installed2.error, 200)}`);
41102
+ if (moved) {
41103
+ if ((0, import_node_fs71.existsSync)(gateDir)) {
41104
+ const carried = carryLegacyContents(gateDir, verityDir);
41105
+ if (carried > 0) {
41106
+ actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
41107
+ }
41108
+ try {
41109
+ (0, import_node_fs71.rmSync)(gateDir, { recursive: true, force: true });
41110
+ } catch {
41111
+ }
41112
+ }
41113
+ return true;
41446
41114
  }
41447
- ui.rail.gap();
41448
- ui.rail.settle();
41449
- return installed2.ok ? "enabled" : void 0;
41450
- }
41451
- async function askTelemetry(ui) {
41452
- const { rail, screen } = ui;
41453
- rail.active("Send Claude Code cost and usage from this machine to Verity?");
41454
- rail.line("Token counts, cost, models, tool names and the repository name, from every Claude Code session on this machine. No prompts, no code, no file contents.");
41455
- rail.line("See it on the /usage page. Turn it off any time: verity telemetry uninstall");
41456
- rail.gap();
41457
- rail.keys([["Enter", "yes"], ["n", "no"]]);
41458
- const k = await screen.key(["enter", "n"], "enter", { freshMs: 300 });
41459
- rail.collapse();
41460
- return k !== "n";
41461
- }
41462
- function signInDetail(outcome) {
41463
- if (outcome === "already" || outcome === "new") return ["ok", outcome];
41464
- if (outcome === "declined" || outcome === "unattended") return ["skipped", outcome];
41465
- return ["failed", outcome === "unreachable" ? "unreachable" : "error"];
41466
- }
41467
- function formalDefault(previous, opts = {}) {
41468
- const cfg = readProjectConfig();
41469
- if (previous?.init?.completed_at || opts.setUpBefore || cfg.formal !== void 0 || cfg.formal_refused) return false;
41470
- writeProjectConfig({ formal: "push" });
41115
+ moveDir(gateDir, verityDir);
41116
+ actions.push("Moved .gate/ \u2192 .verity/");
41471
41117
  return true;
41472
41118
  }
41473
- function sayFormalOn(ui) {
41474
- ui.rail.done("Formal verification at push: on");
41475
- ui.rail.line("At push, Verity checks a small model of the riskiest changed function and replays any counterexample on the real code. That function's source is sent to Verity for the replay.");
41476
- ui.rail.line("Only a reproduced bug can block, and most pushes get no model. Turn it off: verity config formal off");
41477
- }
41478
- function backgroundStep(ui, opts) {
41479
- const { rail } = ui;
41480
- const formal = readProjectConfig().formal;
41481
- const said = opts.formalNew === true && formal === "push";
41482
- if (said) sayFormalOn(ui);
41483
- const lean = shouldStartBackgroundInstall({ formal });
41484
- const leanStarted = lean.start && startBackgroundInstall({ cli: opts.cli });
41485
- const trivy = startTrivyPrewarm();
41486
- const parts2 = [
41487
- ...trivy ? ["Trivy security scanner"] : [],
41488
- ...leanStarted ? ["Lean 4 for formal checks at push (800 MB)"] : [],
41489
- ...!lean.start && lean.why === "running" ? ["Lean 4 (already downloading)"] : []
41490
- ];
41491
- if (parts2.length) {
41492
- if (said) rail.gap();
41493
- rail.done("Getting ready in the background");
41494
- rail.line(parts2.join(" \xB7 "));
41495
- if (leanStarted) rail.line(said ? "Progress: verity lean status" : "Progress: verity lean status. Turn formal checks off: verity config formal off");
41496
- } else if (!lean.start && lean.why === "installed" && formal === "push" && !said) {
41497
- rail.done("Formal verification at push: on");
41498
- }
41499
- if (!lean.start && lean.why === "disk") {
41500
- rail.warn("Formal verification is on, but Lean 4 needs more disk space");
41501
- rail.line(`${lean.detail}. Free some space, then run: verity lean install`);
41502
- } else if (!lean.start && lean.why === "no-release" && formal === "push") {
41503
- rail.line("Lean 4 has no release for this machine, so the formal check at push is skipped.");
41119
+ function migrateProjectDirCarry(gateDir, verityDir, actions) {
41120
+ const carried = carryLegacyContents(gateDir, verityDir);
41121
+ if (carried > 0) {
41122
+ actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
41504
41123
  }
41505
- if (parts2.length || said || !lean.start && lean.why === "disk") rail.gap();
41506
- rail.settle();
41507
- }
41508
-
41509
- // src/commands/init/scaffold.ts
41510
- var import_promises17 = require("node:fs/promises");
41511
-
41512
- // src/lib/codacy-legacy.ts
41513
- var import_node_child_process21 = require("node:child_process");
41514
- var VERITY_ESLINT_CONFIG_FILE = ".codacy/tools-configs/eslint.config.mjs";
41515
- var VERITY_ESLINT_LOCAL_PATH = "./.codacy/tools-configs/eslint.config.mjs";
41516
- var VERITY_ESLINT_FIRST_LINE = "// Verity-owned ESLint config for the quality gate.";
41517
- var MAX_BYTES = 256 * 1024;
41518
- function classifyCodacyConfig(root = repoRoot()) {
41519
- const cfg = readRegularFileInRoot(root, CODACY_CONFIG_FILE, MAX_BYTES);
41520
- if (!cfg.ok) return cfg.reason === "missing" ? "none" : "foreign";
41521
- let parsed;
41522
41124
  try {
41523
- parsed = JSON.parse(cfg.content);
41125
+ (0, import_node_fs71.rmSync)(gateDir, { recursive: true, force: true });
41524
41126
  } catch {
41525
- return "foreign";
41526
41127
  }
41527
- const tools = typeof parsed === "object" && parsed !== null ? parsed.tools : void 0;
41528
- const pointsAtVerity2 = Array.isArray(tools) && tools.some((t) => typeof t === "object" && t !== null && t.localConfigurationFile === VERITY_ESLINT_LOCAL_PATH);
41529
- if (!pointsAtVerity2) return "foreign";
41530
- const eslint = readRegularFileInRoot(root, VERITY_ESLINT_CONFIG_FILE, MAX_BYTES);
41531
- return eslint.ok && eslint.content.startsWith(VERITY_ESLINT_FIRST_LINE) ? "verity" : "foreign";
41128
+ return carried > 0;
41532
41129
  }
41533
- function isTracked(root, path) {
41534
- try {
41535
- (0, import_node_child_process21.execFileSync)("git", ["ls-files", "--error-unmatch", "--", path], { cwd: root, stdio: "pipe" });
41536
- return true;
41537
- } catch {
41538
- return false;
41130
+ function migrateGlobalCredentials(home, actions) {
41131
+ if (!home) return;
41132
+ const gateCreds = (0, import_node_path61.join)(home, ".gate", "credentials");
41133
+ const verityCreds = (0, import_node_path61.join)(home, ".verity", "credentials");
41134
+ if (!(0, import_node_fs71.existsSync)(gateCreds)) return;
41135
+ if (!(0, import_node_fs71.existsSync)(verityCreds)) {
41136
+ (0, import_node_fs71.mkdirSync)((0, import_node_path61.join)(home, ".verity"), { recursive: true });
41137
+ moveFile(gateCreds, verityCreds);
41138
+ actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
41139
+ return;
41140
+ }
41141
+ const added = mergeGlobalCredentials(gateCreds, verityCreds);
41142
+ if (added > 0) {
41143
+ actions.push(
41144
+ `Merged ${added} legacy credential line(s) from ~/.gate/credentials into ~/.verity/credentials`
41145
+ );
41539
41146
  }
41540
41147
  }
41541
- function removeVerityCodacyConfig(root = repoRoot()) {
41542
- const removed = [];
41543
- const untracked = [];
41148
+ async function migrateLegacyHooks(root, actions) {
41149
+ const settings = await readSettingsAt(root);
41150
+ const settingsBefore = JSON.stringify(settings);
41151
+ const updatedSettings = removeVerityHooks(settings);
41152
+ if (settingsBefore !== JSON.stringify(updatedSettings)) {
41153
+ await writeSettingsAt(root, updatedSettings);
41154
+ actions.push("Stripped legacy gate hooks from .claude/settings.json");
41155
+ }
41156
+ }
41157
+ async function migrateClaudeMd(root, actions) {
41158
+ const claudeMd = (0, import_node_path61.join)(root, "CLAUDE.md");
41159
+ const hadLegacyBlock = (0, import_node_fs71.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
41160
+ if (!hadLegacyBlock) return;
41544
41161
  try {
41545
- for (const f of [CODACY_CONFIG_FILE, VERITY_ESLINT_CONFIG_FILE]) {
41546
- if (!isTracked(root, f)) {
41547
- untracked.push(f);
41548
- continue;
41549
- }
41550
- (0, import_node_child_process21.execFileSync)("git", ["rm", "--quiet", "--", f], { cwd: root, stdio: "pipe" });
41551
- removed.push(f);
41552
- }
41162
+ await ensureClaudeMdPointer(root);
41163
+ actions.push("Rewrote legacy gate-memory block in CLAUDE.md");
41553
41164
  } catch (err2) {
41554
- return { ok: false, removed, error: err2.message };
41165
+ printWarn(`Could not rewrite CLAUDE.md block: ${err2.message}`);
41555
41166
  }
41556
- return { ok: true, removed, untracked };
41557
41167
  }
41558
-
41559
- // src/commands/init/scaffold.ts
41560
- async function scaffoldStep(ui, given = {}) {
41561
- const { rail } = ui;
41562
- await (0, import_promises17.mkdir)(VERITY_DIR, { recursive: true });
41563
- await ensureMemoryDir();
41564
- const ignore = ensureVerityGitignore();
41565
- if (ignore === "failed") {
41566
- rail.warn(".gitignore: could not write the Verity block");
41567
- rail.line("Add it by hand: .verity/ holds copies of analyzed files, including any secret the gate flagged.");
41568
- } else if (ignore === "conflict") {
41569
- rail.warn(".gitignore: git still ignores .verity/standard.yaml");
41570
- rail.line("A global or nested .gitignore covers it, so the Standard cannot be committed. Check: git check-ignore -v .verity/standard.yaml");
41571
- } else if (ignore === "memory-tracked") {
41572
- rail.warn("This project commits its knowledge graph");
41573
- }
41574
- const trackedMemory = memoryOptOut() ? [] : committedMemoryFiles();
41575
- if (trackedMemory.length > 0) {
41576
- if (given.untrackMemory) {
41577
- const result = fenceMemory();
41578
- if (result.ok) rail.line(`Untracked ${result.untracked} knowledge-graph file(s) (staged). Commit to finish.`);
41579
- else rail.line("Could not untrack the knowledge graph. Run: verity memory untrack", "warn");
41580
- } else {
41581
- rail.warn(`${trackedMemory.length} knowledge-graph file(s) are tracked in git`);
41582
- rail.line("They show up in every diff. Stop with: verity memory untrack (or keep them on purpose: verity memory track)");
41583
- }
41584
- }
41585
- const tracked = committedVerityState();
41586
- if (tracked.length > 0) {
41587
- if (given.untrackState || ui.asking && await askUntrackState(ui, tracked)) {
41588
- const result = untrackVerityState();
41589
- if (result === "untracked") rail.line("Untracked the Verity state files (staged). Commit to finish.");
41590
- else if (result === "failed") rail.line("Could not untrack them. Run: git rm -r --cached .verity && git add .verity/standard.yaml", "warn");
41591
- } else {
41592
- rail.warn(`${tracked.length} Verity state file(s) are tracked in git`);
41593
- rail.line(`For example ${terminalText(tracked[0], 200)}. Fix: git rm -r --cached .verity && git add .verity/standard.yaml`);
41168
+ function migrateStandardFile(root, actions) {
41169
+ const gateMd = (0, import_node_path61.join)(root, "GATE.md");
41170
+ const verityMd = (0, import_node_path61.join)(root, "VERITY.md");
41171
+ if (!(0, import_node_fs71.existsSync)(gateMd) || (0, import_node_fs71.existsSync)(verityMd)) return;
41172
+ let moved = false;
41173
+ if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
41174
+ try {
41175
+ git2(root, ["mv", "--", "GATE.md", "VERITY.md"]);
41176
+ moved = true;
41177
+ } catch {
41594
41178
  }
41595
41179
  }
41596
- retireCodacyConfig(ui);
41180
+ if (!moved) moveFile(gateMd, verityMd);
41181
+ const content = readFileSyncSafe(verityMd);
41182
+ const refreshed = content.split("GATE.md").join("VERITY.md");
41183
+ if (refreshed !== content) (0, import_node_fs71.writeFileSync)(verityMd, refreshed);
41184
+ actions.push("Renamed GATE.md \u2192 VERITY.md");
41185
+ }
41186
+ async function migrateLocalTelemetry(root, actions) {
41187
+ const { removed } = await healLocalTelemetry(root);
41188
+ if (removed.length === 0) return;
41189
+ actions.push(
41190
+ `Removed ${removed.length} old Verity telemetry key(s) from ${SETTINGS_LOCAL_FILE2} (Claude Code ignores them there). Run "verity telemetry install" to export from this machine.`
41191
+ );
41192
+ }
41193
+ function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
41194
+ if (!movedProjectDir) return;
41597
41195
  try {
41598
- await ensureClaudeMdPointer();
41196
+ npmRemover(LEGACY_NPM_PACKAGE);
41197
+ actions.push(`Removed legacy global package ${LEGACY_NPM_PACKAGE}`);
41599
41198
  } catch (err2) {
41600
- rail.line(`Could not update CLAUDE.md: ${err2.message}`, "warn");
41199
+ printWarn(
41200
+ `Could not remove ${LEGACY_NPM_PACKAGE} (${err2.message}). Remove it manually: npm rm -g ${LEGACY_NPM_PACKAGE}`
41201
+ );
41601
41202
  }
41602
- const cfg = readProjectConfig();
41603
- if (cfg.formal_refused) rail.line(`Formal verification: off \u2014 ${formalRefusalMessage(cfg.formal_refused)}`, "warn");
41604
- rail.settle();
41605
41203
  }
41606
- async function askUntrackState(ui, tracked) {
41607
- const { rail, screen } = ui;
41608
- rail.active(`${tracked.length} Verity state file(s) are tracked in git`);
41609
- rail.line(`For example ${terminalText(tracked[0], 200)}. They can hold copies of secrets the gate flagged, and each push shares them.`);
41610
- rail.line("Stop tracking them? The files stay on disk, and the removal is staged for you to commit.");
41611
- rail.line("Copies already pushed stay in the git history: rotate any secret the gate flagged.");
41612
- rail.gap();
41613
- rail.keys([["Enter", "stop tracking them"], ["n", "keep them tracked"]]);
41614
- const k = await screen.key(["enter", "n"], "enter", { freshMs: 300 });
41615
- rail.collapse();
41616
- return k !== "n";
41204
+ function credentialRemoteKey(line2) {
41205
+ const trimmed = line2.trim();
41206
+ if (trimmed === "") return null;
41207
+ const idx = trimmed.indexOf(" token:");
41208
+ return idx === -1 ? trimmed : trimmed.slice(0, idx).trim();
41617
41209
  }
41618
- function retireCodacyConfig(ui) {
41619
- const kind2 = classifyCodacyConfig();
41620
- if (kind2 === "none") return;
41621
- if (kind2 === "foreign") {
41622
- ui.rail.line("Verity no longer reads .codacy/. Remove it if nothing else uses it.", "muted");
41623
- return;
41210
+ function mergeGlobalCredentials(gateCreds, verityCreds) {
41211
+ const verityContent = readFileSyncSafe(verityCreds);
41212
+ const existingKeys = new Set(
41213
+ verityContent.split("\n").map(credentialRemoteKey).filter((k) => k !== null)
41214
+ );
41215
+ const toAppend = [];
41216
+ for (const line2 of readFileSyncSafe(gateCreds).split("\n")) {
41217
+ const key = credentialRemoteKey(line2);
41218
+ if (key === null || existingKeys.has(key)) continue;
41219
+ existingKeys.add(key);
41220
+ toAppend.push(line2.replace(/\r$/, ""));
41624
41221
  }
41625
- const result = removeVerityCodacyConfig();
41626
- if (result.ok) {
41627
- if (result.removed.length > 0) {
41628
- ui.rail.done("Removed the .codacy/ config that an older Verity wrote (staged)");
41629
- ui.rail.line("Verity runs Trivy from .verity/static-analysis.json now. To keep the files: git restore --staged --worktree .codacy");
41630
- }
41631
- if (result.untracked.length > 0) {
41632
- ui.rail.line(`An older Verity wrote ${result.untracked.join(" and ")}, and Verity no longer uses them. Remove them by hand: rm ${result.untracked.join(" ")}`, "warn");
41633
- }
41634
- } else {
41635
- ui.rail.warn("Could not remove the .codacy/ config that an older Verity wrote");
41636
- ui.rail.line(terminalText(result.error, 200));
41222
+ if (toAppend.length > 0) {
41223
+ const sep7 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
41224
+ (0, import_node_fs71.writeFileSync)(verityCreds, verityContent + sep7 + toAppend.join("\n") + "\n");
41637
41225
  }
41226
+ (0, import_node_fs71.rmSync)(gateCreds, { force: true });
41227
+ return toAppend.length;
41638
41228
  }
41639
-
41640
- // src/commands/init/wiring.ts
41641
- var import_node_fs74 = require("node:fs");
41642
- var import_promises18 = require("node:fs/promises");
41643
- var import_node_path63 = require("node:path");
41644
- var DEFAULT_MOMENTS = ["stop", "pre-commit", "pre-push"];
41645
- async function momentsFromInstalledHooks() {
41646
- const status = await checkAllVerityHooks();
41647
- const wired = [];
41648
- if (status.stop) wired.push("stop");
41649
- if (status.guardOn.includes("commit")) wired.push("pre-commit");
41650
- if (status.guardOn.includes("push")) wired.push("pre-push");
41651
- return wired.length > 0 ? wired : DEFAULT_MOMENTS;
41652
- }
41653
- async function askSetupQuestions(_defaultsOnly, previous, given = {}) {
41654
- const moments = given.moments ?? (previous?.moments?.length ? previous.moments : await momentsFromInstalledHooks());
41655
- let telemetry = "not-asked";
41656
- if (given.telemetry !== void 0) {
41657
- const enabled = (await checkTelemetry()).enabled;
41658
- telemetry = given.telemetry ? enabled ? "already-on" : "yes" : enabled ? "turn-off" : "no";
41229
+ function readFileSyncSafe(path) {
41230
+ try {
41231
+ return (0, import_node_fs71.readFileSync)(path, "utf-8");
41232
+ } catch {
41233
+ return "";
41659
41234
  }
41660
- return { moments, telemetry };
41661
- }
41662
- function parseTelemetryAnswer(raw) {
41663
- const v = raw.trim().toLowerCase();
41664
- if (v === "on") return true;
41665
- if (v === "off") return false;
41666
- return null;
41667
- }
41668
- function planMoments(answers, opts) {
41669
- const existing = readProjectConfig();
41670
- const keptUserChoice = !opts.given && existing.git_moments_source === "user";
41671
- const gitMoments = keptUserChoice ? existing.git_moments : [
41672
- ...answers.includes("pre-commit") ? ["commit"] : [],
41673
- ...answers.includes("pre-push") ? ["push"] : []
41674
- ];
41675
- const turnOn = keptUserChoice && existing.turn_review ? existing.turn_review === "on" : answers.includes("stop");
41676
- return {
41677
- momentIds: [
41678
- ...turnOn ? ["stop"] : [],
41679
- ...gitMoments.includes("commit") ? ["pre-commit"] : [],
41680
- ...gitMoments.includes("push") ? ["pre-push"] : []
41681
- ],
41682
- gitMoments,
41683
- turnOn,
41684
- keptUserChoice
41685
- };
41686
41235
  }
41687
- function momentsStep(ui, plan) {
41688
- const said = describeReviewMoments(plan.momentIds);
41689
- if (plan.momentIds.length === 0) {
41690
- ui.rail.warn("Verity reviews nothing");
41691
- ui.rail.line("Code changes are not reviewed. Turn the moments on: verity config moments turn,commit,push", "warn");
41692
- } else {
41693
- ui.rail.done(`Verity reviews ${said}`);
41694
- ui.rail.line("To change it, run verity config moments, or just ask Claude.");
41236
+ function hasStagedChanges(root) {
41237
+ try {
41238
+ git2(root, ["diff", "--cached", "--quiet", ...DIFF_READ_FLAGS]);
41239
+ return false;
41240
+ } catch {
41241
+ return true;
41695
41242
  }
41696
- if (plan.keptUserChoice) {
41697
- ui.rail.line(`Keeping the git moments you set: ${plan.gitMoments.join(", ") || "none"} (this run asked no questions, so it does not overrule them).`);
41243
+ }
41244
+ function moveDir(from, to) {
41245
+ try {
41246
+ (0, import_node_fs71.renameSync)(from, to);
41247
+ } catch (err2) {
41248
+ if (err2.code !== "EXDEV") throw err2;
41249
+ (0, import_node_fs71.cpSync)(from, to, { recursive: true });
41250
+ (0, import_node_fs71.rmSync)(from, { recursive: true, force: true });
41698
41251
  }
41699
- ui.rail.gap();
41700
- ui.rail.settle();
41701
41252
  }
41702
- var SKILLS = [
41703
- "verity-setup",
41704
- "verity-analyze",
41705
- "verity-status",
41706
- "verity-feedback",
41707
- "verity-learn",
41708
- "verity-memory",
41709
- "verity-insights",
41710
- "verity-reflect",
41711
- "verity-init",
41712
- "verity-prove"
41713
- ];
41714
- function resolveDataDir2() {
41715
- const candidates2 = [
41716
- (0, import_node_path63.join)(__dirname, "..", "data"),
41717
- (0, import_node_path63.join)(__dirname, "..", "..", "data"),
41718
- (0, import_node_path63.join)(process.cwd(), "cli", "data")
41719
- ];
41720
- for (const candidate of candidates2) {
41721
- if ((0, import_node_fs74.existsSync)((0, import_node_path63.join)(candidate, "skills"))) return candidate;
41253
+ function moveFile(from, to) {
41254
+ try {
41255
+ (0, import_node_fs71.renameSync)(from, to);
41256
+ } catch (err2) {
41257
+ if (err2.code !== "EXDEV") throw err2;
41258
+ (0, import_node_fs71.cpSync)(from, to);
41259
+ (0, import_node_fs71.rmSync)(from, { force: true });
41722
41260
  }
41723
- throw new Error("Could not find Verity skill data. Reinstall: npm install -g @codacy/verity-cli");
41724
41261
  }
41725
- async function skillIsCurrent(src, dest) {
41726
- const list2 = (dir) => {
41727
- const out2 = [];
41728
- const walk2 = (d, prefix) => {
41729
- for (const e of (0, import_node_fs74.readdirSync)(d, { withFileTypes: true })) {
41730
- const rel = prefix ? `${prefix}/${e.name}` : e.name;
41731
- if (e.isDirectory()) walk2((0, import_node_path63.join)(d, e.name), rel);
41732
- else if (e.isFile()) out2.push(rel);
41262
+ function carryLegacyContents(gateDir, verityDir) {
41263
+ if (!isRealDir(gateDir) || !isRealDir(verityDir)) return 0;
41264
+ let copied = 0;
41265
+ const walk2 = (relDir) => {
41266
+ let entries;
41267
+ try {
41268
+ entries = (0, import_node_fs71.readdirSync)((0, import_node_path61.join)(gateDir, relDir), { withFileTypes: true });
41269
+ } catch {
41270
+ return;
41271
+ }
41272
+ for (const entry of entries) {
41273
+ const rel = relDir ? (0, import_node_path61.join)(relDir, entry.name) : entry.name;
41274
+ if (entry.isSymbolicLink()) continue;
41275
+ if (entry.isDirectory()) {
41276
+ walk2(rel);
41277
+ continue;
41733
41278
  }
41734
- };
41735
- walk2(dir, "");
41736
- return out2.sort();
41279
+ if (!entry.isFile() || !ensureRealDirs(verityDir, relDir)) continue;
41280
+ const opened = openRegularInRoot(gateDir, rel);
41281
+ if (!opened.ok) continue;
41282
+ try {
41283
+ (0, import_node_fs71.writeFileSync)((0, import_node_path61.join)(verityDir, rel), readOpened(opened.fd, opened.size), { flag: "wx", mode: 384 });
41284
+ copied++;
41285
+ } catch {
41286
+ }
41287
+ }
41737
41288
  };
41289
+ walk2("");
41290
+ return copied;
41291
+ }
41292
+ function isRealDir(path) {
41738
41293
  try {
41739
- const shipped = list2(src);
41740
- if (JSON.stringify(shipped) !== JSON.stringify(list2(dest))) return false;
41741
- for (const rel of shipped) {
41742
- if (await (0, import_promises18.readFile)((0, import_node_path63.join)(src, rel), "utf-8") !== await (0, import_promises18.readFile)((0, import_node_path63.join)(dest, rel), "utf-8")) return false;
41743
- }
41744
- return true;
41294
+ const st = (0, import_node_fs71.lstatSync)(path);
41295
+ return st.isDirectory() && !st.isSymbolicLink();
41745
41296
  } catch {
41746
41297
  return false;
41747
41298
  }
41748
41299
  }
41749
- async function installSkills(force) {
41750
- const source2 = (0, import_node_path63.join)(resolveDataDir2(), "skills");
41751
- const missing = [];
41752
- for (const skill of SKILLS) {
41753
- const src = (0, import_node_path63.join)(source2, skill);
41754
- const dest = (0, import_node_path63.join)(".claude/skills", skill);
41755
- if (!(0, import_node_fs74.existsSync)(src)) {
41756
- missing.push(skill);
41757
- continue;
41300
+ function ensureRealDirs(root, relDir) {
41301
+ let current = root;
41302
+ for (const segment of relDir.split(/[\\/]/).filter(Boolean)) {
41303
+ current = (0, import_node_path61.join)(current, segment);
41304
+ try {
41305
+ (0, import_node_fs71.mkdirSync)(current);
41306
+ } catch (e) {
41307
+ if (e.code !== "EEXIST") return false;
41758
41308
  }
41759
- if ((0, import_node_fs74.existsSync)(dest) && !force && await skillIsCurrent(src, dest)) continue;
41760
- await (0, import_promises18.mkdir)(dest, { recursive: true });
41761
- await (0, import_promises18.cp)(src, dest, { recursive: true, force: true });
41309
+ if (!isRealDir(current)) return false;
41762
41310
  }
41763
- return missing;
41311
+ return true;
41764
41312
  }
41765
- async function hooksStep(ui, ctx) {
41766
- const { rail } = ui;
41767
- if (ctx.staleMarker) rail.line(`Cleared a stale plugin marker \u2014 ${ctx.staleMarker} is no longer installed.`);
41768
- if (!ctx.plan.keptUserChoice) {
41769
- writeProjectConfig({ git_moments: ctx.plan.gitMoments, git_moments_source: "init", turn_review: ctx.plan.turnOn ? "on" : "off" });
41770
- }
41771
- if (ctx.pluginMode) {
41772
- rail.done(`Hooks here: the Verity Claude Code plugin${ctx.pluginWhere}`);
41773
- rail.line("Skills and hooks come from the plugin, so this run installs neither, and removes any this project still has from an npm install.");
41774
- rail.line("Want this project to own its own copy instead? Uninstall the plugin, or use --no-plugin.");
41775
- const settings = await readSettings();
41776
- const stripped = removeVerityHooks(settings);
41777
- if (JSON.stringify(settings.hooks ?? {}) !== JSON.stringify(stripped.hooks ?? {})) {
41778
- await writeSettings(stripped);
41779
- rail.line("Removed this project's Verity hooks from .claude/settings.json. The plugin wires them now, so each turn is reviewed once.");
41313
+ async function needsMigration(root = repoRoot()) {
41314
+ const gateDir = (0, import_node_path61.join)(root, ".gate");
41315
+ const verityDir = (0, import_node_path61.join)(root, ".verity");
41316
+ if ((0, import_node_fs71.existsSync)(gateDir) && !(0, import_node_fs71.existsSync)(verityDir)) return true;
41317
+ if ((0, import_node_fs71.existsSync)(gateDir) && (0, import_node_fs71.existsSync)(verityDir)) {
41318
+ if ((0, import_node_fs71.existsSync)((0, import_node_path61.join)(gateDir, "credentials")) && !(0, import_node_fs71.existsSync)((0, import_node_path61.join)(verityDir, "credentials"))) {
41319
+ return true;
41780
41320
  }
41781
- const removed = removeProjectSkills();
41782
- if (removed.length > 0) rail.line(`Removed ${removed.length} project-local skill${removed.length === 1 ? "" : "s"} that the plugin supersedes.`);
41783
- } else {
41784
- if (ctx.overridingPlugin) {
41785
- rail.warn("Hooks here: this project's .claude/settings.json (--no-plugin)");
41786
- rail.line("The Verity plugin is active on this machine and would own them. Both sets fire; the settings.json copy stands down at run time, so each turn is reviewed once.");
41787
- } else {
41788
- rail.done("Hooks here: this project's .claude/settings.json");
41321
+ if ((0, import_node_fs71.existsSync)((0, import_node_path61.join)(gateDir, "memory")) && !(0, import_node_fs71.existsSync)((0, import_node_path61.join)(verityDir, "memory"))) {
41322
+ return true;
41789
41323
  }
41790
- const missing = await installSkills(ctx.force);
41791
- if (missing.length > 0) rail.line(`Skill data not found: ${missing.join(", ")}`, "warn");
41792
- await applyMomentSelection(ctx.plan.momentIds);
41793
41324
  }
41794
- rail.gap();
41795
- rail.settle();
41325
+ const claudeMd = (0, import_node_path61.join)(root, "CLAUDE.md");
41326
+ if ((0, import_node_fs71.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
41327
+ return true;
41328
+ }
41329
+ if ((0, import_node_fs71.existsSync)((0, import_node_path61.join)(root, "GATE.md")) && !(0, import_node_fs71.existsSync)((0, import_node_path61.join)(root, "VERITY.md"))) {
41330
+ return true;
41331
+ }
41332
+ if (await hasLegacyHooksAt(root)) return true;
41333
+ return false;
41334
+ }
41335
+ function registerMigrateCommand(program2) {
41336
+ program2.command("migrate").description("Migrate a legacy GATE.md install to Verity (.gate \u2192 .verity, hooks, CLAUDE.md)").action(async () => {
41337
+ let result;
41338
+ try {
41339
+ result = await runMigration();
41340
+ } catch (err2) {
41341
+ printError(`Migration failed: ${err2.message}`);
41342
+ process.exitCode = 1;
41343
+ return;
41344
+ }
41345
+ if (!result.migrated) {
41346
+ printInfo("Nothing to migrate \u2014 this project is already on Verity.");
41347
+ return;
41348
+ }
41349
+ printInfo("Migration complete:");
41350
+ for (const a of result.actions) printInfo(` ${a} \u2713`);
41351
+ });
41796
41352
  }
41797
41353
 
41798
- // src/commands/init/standard.ts
41799
- var import_node_fs75 = require("node:fs");
41800
-
41801
- // src/commands/init/rules.ts
41802
- async function projectLabel(token, serviceUrl, verbose) {
41354
+ // src/commands/init/ui.ts
41355
+ var import_node_fs72 = require("node:fs");
41356
+ var import_node_path62 = require("node:path");
41357
+ var import_node_os13 = require("node:os");
41358
+ var import_node_child_process20 = require("node:child_process");
41359
+ function createInitUi(opts) {
41360
+ const term = opts.term ?? detectTerm();
41361
+ const screen = new Screen(term);
41362
+ const rail = new Rail(screen);
41363
+ return { term, screen, rail, asking: !opts.defaultsOnly && screen.canAsk() };
41364
+ }
41365
+ function installInterrupt(ui) {
41366
+ const stop2 = () => {
41367
+ ui.rail.stopped();
41368
+ ui.screen.release();
41369
+ process.exit(130);
41370
+ };
41371
+ ui.screen.startInput(stop2);
41372
+ process.once("SIGINT", stop2);
41373
+ }
41374
+ var SEEN_FILE = ".init-seen";
41375
+ function firstInitOnMachine(home = process.env.HOME || (0, import_node_os13.homedir)()) {
41376
+ return !(0, import_node_fs72.existsSync)((0, import_node_path62.join)(home, ".verity", SEEN_FILE));
41377
+ }
41378
+ function markInitSeen(home = process.env.HOME || (0, import_node_os13.homedir)()) {
41379
+ replaceInRoot((0, import_node_path62.join)(home, ".verity"), SEEN_FILE, `${(/* @__PURE__ */ new Date()).toISOString()}
41380
+ `);
41381
+ }
41382
+ function repoLabel() {
41803
41383
  try {
41804
- const who = await whoami(token, serviceUrl, verbose);
41805
- return who.ok ? who.data.project_id : null;
41384
+ const url = (0, import_node_child_process20.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
41385
+ const parsed = parseRemote(url);
41386
+ if (parsed) return terminalText(`${parsed.owner}/${parsed.repo}`, 120);
41806
41387
  } catch {
41807
- return null;
41808
41388
  }
41389
+ return terminalText((0, import_node_path62.basename)(repoRoot()), 120);
41809
41390
  }
41810
- function profilePreview(profile) {
41811
- return JSON.stringify(profile, null, 2).split("\n").slice(0, 12).map((l) => terminalText(l, 400)).concat(["\u2026", `The whole profile: ${PROFILE_FILE}`]);
41812
- }
41813
- async function rulesOffered(ui, globals) {
41814
- const token = await resolveToken(globals.token);
41815
- const url = await resolveServiceUrl(globals.serviceUrl);
41816
- if (!token.ok || !url.ok) return false;
41817
- const status = await generationStatus({ serviceUrl: url.data, token: token.data.token, verbose: globals.verbose });
41818
- if (status === "off") ui.rail.line("Project rules are not switched on for this project yet. The built-in rules apply.");
41819
- if (status === "unsupported") {
41820
- ui.rail.line(`This Verity service (${terminalText(url.data, 200)}) does not write project rules yet: it is older than this CLI. The built-in rules apply.`);
41821
- }
41822
- return status === "on" || status === "unknown";
41391
+
41392
+ // src/lib/prereqs.ts
41393
+ var import_node_child_process21 = require("node:child_process");
41394
+ var import_node_fs73 = require("node:fs");
41395
+ var MIN_NODE_MAJOR = 20;
41396
+ function which(bin) {
41397
+ return whichSync(bin, { real: false });
41823
41398
  }
41824
- async function askToSend(ui, profile) {
41825
- const { rail, screen } = ui;
41826
- const lines = consentLines(profile);
41827
- rail.active("Write review rules for this repository?");
41828
- rail.box(lines.slice(1, -1));
41829
- rail.line(lines[lines.length - 1]);
41830
- rail.gap();
41831
- rail.keys([["Enter", "send"], ["v", "view the profile"], ["n", "skip"]]);
41832
- let allowed = ["enter", "v", "n"];
41833
- for (; ; ) {
41834
- const k = await screen.key(allowed, "n", { freshMs: 300 });
41835
- if (k !== "v") {
41836
- rail.collapse();
41837
- return k === "enter";
41838
- }
41839
- rail.gap();
41840
- for (const l of profilePreview(profile)) rail.line(l);
41841
- rail.gap();
41842
- rail.keys([["Enter", "send"], ["n", "skip"]]);
41843
- allowed = ["enter", "n"];
41844
- }
41399
+ function checkNode() {
41400
+ const version = process.version;
41401
+ const major = Number.parseInt(version.slice(1), 10);
41402
+ const ok = Number.isFinite(major) && major >= MIN_NODE_MAJOR;
41403
+ return {
41404
+ id: "node",
41405
+ label: "Node.js",
41406
+ status: ok ? "ok" : "missing",
41407
+ detail: version,
41408
+ remedy: ok ? void 0 : `Node.js ${MIN_NODE_MAJOR}+ required \u2014 update from https://nodejs.org`,
41409
+ required: true
41410
+ };
41845
41411
  }
41846
- async function generate(ui, globals, profile, replaces) {
41847
- const { rail, screen } = ui;
41848
- const s = screen.s;
41849
- const token = await resolveToken(globals.token);
41850
- const url = await resolveServiceUrl(globals.serviceUrl);
41851
- if (!token.ok || !url.ok) return { status: "fallback" };
41852
- const commits = profile.history.commits.length;
41853
- const fixes = profile.history.commits.filter((c) => c.fix).length;
41854
- const reverts = profile.history.commits.filter((c) => /^revert\b/i.test(c.subject)).length;
41855
- rail.active("Writing review rules");
41856
- await rail.counted(([c, f, r]) => `${s.green}\u2713${s.reset} Read ${c} commits ${s.gray}\xB7${s.reset} ${f} fixes ${s.gray}\xB7${s.reset} ${r} reverts`, [commits, fixes, reverts]);
41857
- if (profile.docs.length) rail.ticked(`Read ${profile.docs.map((d) => terminalText(d.name, 120)).join(" and ")}`);
41858
- if (ui.term.screen && !ui.term.motion) rail.line("Writing rules for this repository\u2026");
41859
- const started = Date.now();
41860
- const base = buildStandard({ detected: detectProject() }, loadCatalog());
41861
- const outcome = await screen.spin(`${s.gray}\u2502${s.reset} `, () => "Writing rules for this repository", generateStandard({
41862
- serviceUrl: url.data,
41863
- token: token.data.token,
41864
- profile,
41865
- base,
41866
- projectId: await projectLabel(token.data.token, url.data, globals.verbose),
41867
- verbose: globals.verbose,
41868
- replaces
41869
- }));
41870
- rail.collapse();
41871
- if (outcome.status === "exists") return { status: "exists" };
41872
- if (outcome.status === "disabled") {
41873
- rail.line("Project rules are not switched on for this project yet. The built-in rules apply.");
41874
- rail.gap();
41875
- return { status: "fallback" };
41876
- }
41877
- if (outcome.status === "unsupported") {
41878
- rail.line(`This Verity service (${terminalText(url.data, 200)}) does not write project rules yet: it is older than this CLI. The built-in rules apply.`);
41879
- rail.gap();
41880
- return { status: "fallback" };
41412
+ function checkGit() {
41413
+ let detail = "";
41414
+ try {
41415
+ detail = (0, import_node_child_process21.execSync)("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
41416
+ } catch {
41417
+ return {
41418
+ id: "git",
41419
+ label: "git",
41420
+ status: "missing",
41421
+ detail: "not found",
41422
+ remedy: "Install git from https://git-scm.com",
41423
+ required: true
41424
+ };
41881
41425
  }
41882
- if (outcome.status === "failed") {
41883
- rail.warn("The rules could not be written this time");
41884
- rail.line(terminalText(outcome.reason, 300));
41885
- rail.line("The built-in rules apply. Try again later: verity standard generate");
41886
- rail.gap();
41887
- return { status: "fallback" };
41426
+ return { id: "git", label: "git", status: "ok", detail, required: true };
41427
+ }
41428
+ function checkClaude() {
41429
+ const path = which("claude");
41430
+ return {
41431
+ id: "claude",
41432
+ label: "Claude Code",
41433
+ status: path ? "ok" : "warn",
41434
+ detail: path ?? "not found",
41435
+ remedy: path ? void 0 : "Hooks are wired but need Claude Code to fire \u2014 https://claude.com/claude-code",
41436
+ required: false
41437
+ };
41438
+ }
41439
+ var ANALYSIS_CLI_REMEDY = "Reinstall Verity, which includes it: npm install -g @codacy/verity-cli";
41440
+ function checkAnalysisCli(opts = {}) {
41441
+ const base = { id: "analysis-cli", label: "@codacy/analysis-cli", required: false };
41442
+ const analyzer = resolveAnalyzer(opts);
41443
+ const shown = (opts.onPathDisplay ?? (() => which("codacy-analysis")))();
41444
+ if (!analyzer) {
41445
+ return { ...base, status: "warn", detail: "not found", remedy: `${ANALYSIS_CLI_REMEDY} (static findings are unavailable until then)` };
41888
41446
  }
41889
- const secs2 = Math.max(1, Math.round((Date.now() - started) / 1e3));
41890
- const docs = profile.docs.length;
41891
- if (outcome.rules.length === 0) {
41892
- rail.warn("No project rules this time");
41893
- rail.line("The proposed rules were too generic, or not safe to use. The built-in rules apply.");
41894
- } else {
41895
- const n2 = outcome.rules.length;
41896
- const from = [`${commits} ${commits === 1 ? "commit" : "commits"}`, ...docs ? [`${docs} ${docs === 1 ? "doc" : "docs"}`] : []].join(" and ");
41897
- rail.done(`${n2} ${n2 === 1 ? "rule" : "rules"} for this repository ${s.gray}\xB7 from ${from} \xB7 ${secs2}s${s.reset}`);
41898
- await rail.rules(outcome.rules);
41899
- if (outcome.rejected > 0) rail.line(`${outcome.rejected} proposed ${outcome.rejected === 1 ? "rule was" : "rules were"} left out: too generic, or not safe to use.`);
41447
+ if (analyzer.source === "path") {
41448
+ return { ...base, status: "ok", detail: `${shown ?? analyzer.path} (on PATH)`, source: "path" };
41900
41449
  }
41901
- rail.gap();
41902
- rail.done(`Standard v${outcome.version} saved`);
41903
- rail.line(`Your team gets the same rules. Edit them in ${STANDARD_FILE}`);
41904
- if (outcome.warning) rail.line(terminalText(outcome.warning, 600), "warn");
41905
- return { status: "created", version: outcome.version };
41450
+ const unused = shown && !sameFile(shown, analyzer.path) ? shown : void 0;
41451
+ return {
41452
+ ...base,
41453
+ status: "ok",
41454
+ detail: `${analyzer.version} (bundled with Verity)`,
41455
+ source: "bundled",
41456
+ version: analyzer.version,
41457
+ ...unused && { unused }
41458
+ };
41906
41459
  }
41907
-
41908
- // src/commands/init/team-standard.ts
41909
- async function lookUpTeamStandard(opts) {
41910
- if (!opts.adopt) return { status: "skipped", why: "no-adopt" };
41911
- const token = await resolveToken(opts.globals.token);
41912
- const url = await resolveServiceUrl(opts.globals.serviceUrl);
41913
- if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
41914
- const found = await fetchRemoteSetup({ token: token.data.token, serviceUrl: url.data, verbose: opts.globals.verbose });
41915
- if (found.status === "unavailable") return { status: "unavailable", reason: terminalText(found.reason, 300) };
41916
- if (found.status === "none") return { status: "none" };
41917
- if (opts.previous?.declinedStandardVersion === found.standard.version) {
41918
- return { status: "declined-before", version: found.standard.version };
41460
+ function sameFile(a, b) {
41461
+ try {
41462
+ return (0, import_node_fs73.realpathSync)(a) === (0, import_node_fs73.realpathSync)(b);
41463
+ } catch {
41464
+ return false;
41919
41465
  }
41920
- return { status: "found", found, version: found.standard.version };
41921
41466
  }
41922
- async function writeTeamStandard(team, opts) {
41923
- const token = await resolveToken(opts.globals.token);
41924
- const url = await resolveServiceUrl(opts.globals.serviceUrl);
41925
- if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
41926
- const found = team.found;
41927
- try {
41928
- const outcome = await adoptRemoteSetup(found, {
41929
- serviceUrl: url.data,
41930
- projectId: await projectLabel(token.data.token, url.data, opts.globals.verbose)
41467
+ var INSTALL_TIMEOUT_MS = 12e4;
41468
+ function run3(command, args2, opts = {}) {
41469
+ return new Promise((resolve9) => {
41470
+ const child = (0, import_node_child_process21.spawn)(command, args2, {
41471
+ stdio: opts.inherit ? "inherit" : "pipe",
41472
+ timeout: INSTALL_TIMEOUT_MS
41931
41473
  });
41932
- if (opts.previous?.declinedStandardVersion != null) {
41933
- await writeSetupState({ declinedStandardVersion: void 0 }).catch(() => {
41934
- });
41474
+ child.on("error", () => resolve9(false));
41475
+ child.on("close", (code) => resolve9(code === 0));
41476
+ });
41477
+ }
41478
+ async function installAnalysisCli(opts = {}) {
41479
+ const spinner = opts.quiet ? { succeed: () => {
41480
+ }, warn: () => {
41481
+ }, stop: () => {
41482
+ } } : startSpinner("Installing @codacy/analysis-cli");
41483
+ const ok = await run3("npm", ["install", "-g", "@codacy/analysis-cli"]);
41484
+ if (ok) {
41485
+ const check = checkAnalysisCli();
41486
+ if (check.status === "ok") {
41487
+ spinner.succeed("@codacy/analysis-cli installed");
41488
+ return { ...check, justInstalled: true };
41935
41489
  }
41936
- return { status: "adopted", version: found.standard.version, summary: describeRemote(found), notes: outcome.notes };
41937
- } catch (err2) {
41938
- return { status: "write-failed", reason: terminalText(err2.message, 300) };
41490
+ spinner.warn("npm reported success but codacy-analysis is not on PATH");
41491
+ return check;
41492
+ }
41493
+ spinner.stop();
41494
+ {
41495
+ const failed = {
41496
+ id: "analysis-cli",
41497
+ label: "@codacy/analysis-cli",
41498
+ status: "warn",
41499
+ detail: "install failed",
41500
+ remedy: ANALYSIS_CLI_REMEDY,
41501
+ required: false
41502
+ };
41503
+ if (opts.sudo === false || !process.stdin.isTTY || !process.stdout.isTTY) return failed;
41504
+ process.stderr.write(" Retrying with sudo (you may be asked for your password)\u2026\n");
41505
+ const sudoOk = await run3("sudo", ["npm", "install", "-g", "@codacy/analysis-cli"], { inherit: true });
41506
+ if (!sudoOk) return failed;
41507
+ const check = checkAnalysisCli();
41508
+ return check.status === "ok" ? { ...check, justInstalled: true } : check;
41939
41509
  }
41940
41510
  }
41941
- async function adoptTeamStandard(opts) {
41942
- const team = await lookUpTeamStandard(opts);
41943
- return team.status === "found" ? writeTeamStandard(team, opts) : team;
41944
- }
41945
- async function askTeamChoice(ui, team) {
41946
- const { rail, screen } = ui;
41947
- rail.active("Your team already has a Standard for this repository");
41948
- rail.line(describeRemote(team.found));
41949
- rail.line(`Use it to review with the same rules as everyone else. A new one becomes v${team.version + 1} for the whole team.`);
41950
- rail.gap();
41951
- rail.keys([["Enter", "use it"], ["n", "create a new one"]]);
41952
- const k = await screen.key(["enter", "n"], "enter", { freshMs: 300 });
41953
- rail.collapse();
41954
- return k === "n" ? "new" : "use";
41955
- }
41956
- function sayAdopted(ui, team) {
41957
- ui.rail.done(`Using your team's Standard v${team.version}`);
41958
- ui.rail.line(terminalText(team.summary, 300));
41959
- ui.rail.line(`Edit it in ${STANDARD_FILE}, then share the change: verity standard push`);
41960
- for (const note of team.notes) ui.rail.line(terminalText(note, 300), "warn");
41511
+ async function checkPrereqs(opts = {}) {
41512
+ const checks = [checkNode(), checkGit(), checkClaude()];
41513
+ let analysis = checkAnalysisCli();
41514
+ if (analysis.status !== "ok" && opts.install) {
41515
+ analysis = await installAnalysisCli();
41516
+ }
41517
+ checks.push(analysis);
41518
+ return { checks, blocked: checks.some((c) => c.required && c.status !== "ok") };
41961
41519
  }
41962
41520
 
41963
- // src/commands/init/standard.ts
41964
- async function deriveFromCatalogue(ui, globals) {
41965
- const url = await resolveServiceUrl(globals.serviceUrl);
41966
- try {
41967
- const result = await runSynthesis({ serviceUrl: url.ok ? url.data : void 0 });
41968
- if ("refused" in result) {
41969
- ui.rail.warn("No Standard was written");
41970
- ui.rail.line(terminalText(result.refused, 300));
41971
- return { ok: false, version: null };
41972
- }
41973
- const pushed = await pushStandard(globals);
41974
- if (pushed.standardVersion !== null) {
41975
- await correctVerityMdVersion({
41976
- standard: result.standard,
41977
- version: pushed.standardVersion,
41978
- tools: result.tools,
41979
- serviceUrl: url.ok ? url.data : void 0
41980
- });
41981
- ui.rail.done(`Standard v${pushed.standardVersion} saved`);
41982
- ui.rail.line(`Your team gets the same rules. Edit them in ${STANDARD_FILE}`);
41983
- } else {
41984
- ui.rail.done("Standard saved on this machine");
41985
- }
41986
- for (const line2 of pushed.lines) {
41987
- if (line2.startsWith("Standard pushed")) continue;
41988
- if (line2.startsWith("Not signed in")) ui.rail.line(`Sign in to share it with your team: verity login, then verity standard push. Edit it in ${STANDARD_FILE}`);
41989
- else ui.rail.line(terminalText(line2, 600), "warn");
41990
- }
41991
- for (const note of result.notes) ui.rail.line(terminalText(note, 300), "warn");
41992
- return { ok: true, version: pushed.standardVersion };
41993
- } catch (err2) {
41994
- ui.rail.warn("No Standard was written");
41995
- ui.rail.line(terminalText(err2.message, 300));
41996
- return { ok: false, version: null };
41997
- }
41521
+ // src/commands/init/steps.ts
41522
+ function languageShares(d) {
41523
+ const total = d.languages.reduce((n2, l) => n2 + (d.languageCounts[l] ?? 0), 0);
41524
+ if (total === 0) return [];
41525
+ const shares = d.languages.map((l) => ({ name: displayName(l), pct: Math.round((d.languageCounts[l] ?? 0) / total * 100) }));
41526
+ const top = shares.slice(0, 2);
41527
+ const rest = 100 - top.reduce((n2, s) => n2 + s.pct, 0);
41528
+ return rest > 0 && shares.length > 2 ? [...top, { name: "Other", pct: rest }] : top;
41998
41529
  }
41999
- async function standardStep(ui, ctx) {
42000
- const { rail } = ui;
42001
- const s = ui.screen.s;
42002
- const notAsked = { outcome: "skipped", detail: "none" };
42003
- const end = (r) => {
42004
- rail.gap();
42005
- rail.settle();
42006
- return r;
42007
- };
42008
- const derived = async (profile2) => {
42009
- const d = await deriveFromCatalogue(ui, ctx.globals);
42010
- return end({ source: d.ok ? "derived" : "none", version: d.version, profile: profile2 });
42011
- };
42012
- if ((0, import_node_fs75.existsSync)(projectPath(STANDARD_FILE))) {
42013
- const sync = await pushLocalStandardIfMissing(ctx.globals);
42014
- rail.done("Kept your Standard");
42015
- rail.line(`${STANDARD_FILE} is already here, so this run leaves it as it is. Edit it there, then share the change: verity standard push`);
42016
- if (sync.status === "pushed") {
42017
- rail.line(sync.version !== null ? `It was only on this machine. It is shared now as v${sync.version}.` : "It is only on this machine:", sync.version !== null ? "muted" : "warn");
42018
- for (const line2 of sync.lines) if (!line2.startsWith("Standard pushed")) rail.line(terminalText(line2, 600), "warn");
42019
- }
42020
- return end({ source: "kept", version: sync.status === "pushed" ? sync.version : null, profile: notAsked });
41530
+ var NAMES = {
41531
+ typescript: "TypeScript",
41532
+ javascript: "JavaScript",
41533
+ python: "Python",
41534
+ go: "Go",
41535
+ java: "Java",
41536
+ kotlin: "Kotlin",
41537
+ ruby: "Ruby",
41538
+ rust: "Rust",
41539
+ c: "C",
41540
+ cpp: "C++",
41541
+ shell: "Shell",
41542
+ dockerfile: "Dockerfile",
41543
+ csharp: "C#",
41544
+ php: "PHP",
41545
+ swift: "Swift",
41546
+ scala: "Scala",
41547
+ sql: "SQL"
41548
+ };
41549
+ var displayName = (l) => NAMES[l] ?? l.charAt(0).toUpperCase() + l.slice(1);
41550
+ var STACK_NAMES = {
41551
+ "next.js": "Next.js",
41552
+ next: "Next.js",
41553
+ react: "React",
41554
+ vue: "Vue",
41555
+ angular: "Angular",
41556
+ svelte: "Svelte",
41557
+ express: "Express",
41558
+ nestjs: "NestJS",
41559
+ fastify: "Fastify",
41560
+ prisma: "Prisma",
41561
+ django: "Django",
41562
+ flask: "Flask",
41563
+ fastapi: "FastAPI",
41564
+ rails: "Rails",
41565
+ spring: "Spring",
41566
+ vitest: "Vitest",
41567
+ jest: "Jest",
41568
+ mocha: "Mocha",
41569
+ playwright: "Playwright",
41570
+ cypress: "Cypress",
41571
+ supabase: "Supabase",
41572
+ stripe: "Stripe",
41573
+ tailwind: "Tailwind"
41574
+ };
41575
+ async function repoStep(ui, detected) {
41576
+ ui.rail.done("Looked at your repository");
41577
+ await ui.rail.languages(languageShares(detected));
41578
+ const stack = [...detected.frameworks, detected.buildSystem, detected.testFramework].filter(Boolean).map((n2) => STACK_NAMES[n2.toLowerCase()] ?? n2);
41579
+ const files = `${detected.filesScanned.toLocaleString("en-US")} ${detected.filesScanned === 1 ? "file" : "files"}`;
41580
+ ui.rail.line([...stack, files].join(" \xB7 "), stack.length ? "plain" : "muted");
41581
+ ui.rail.gap();
41582
+ ui.rail.settle();
41583
+ }
41584
+ async function prerequisitesStep(ui) {
41585
+ const report = await checkPrereqs({ install: false });
41586
+ const analysis = report.checks.find((c) => c.id === "analysis-cli");
41587
+ if (analysis && analysis.status !== "ok") {
41588
+ const s = ui.screen.s;
41589
+ const installed2 = await ui.screen.spin(`${s.gray}\u2502${s.reset} `, () => "Verity's copy of the Codacy analysis CLI is missing. Installing it (it runs the Trivy security scan)", installAnalysisCli({ quiet: true, sudo: false }));
41590
+ report.checks[report.checks.indexOf(analysis)] = installed2;
41591
+ if (installed2.status === "ok") ui.rail.ticked("Installed the Codacy analysis CLI");
42021
41592
  }
42022
- const team = await lookUpTeamStandard(ctx);
42023
- let replaces;
42024
- if (team.status === "found") {
42025
- const choice = ctx.standard ?? (ui.asking ? await askTeamChoice(ui, team) : "use");
42026
- if (choice === "use") {
42027
- const adopted = await writeTeamStandard(team, ctx);
42028
- if (adopted.status === "adopted") {
42029
- sayAdopted(ui, adopted);
42030
- return end({ source: "adopted", version: adopted.version, profile: notAsked });
42031
- }
42032
- rail.warn("Could not write the team's Standard here");
42033
- if (adopted.status === "write-failed") rail.line(terminalText(adopted.reason, 300));
42034
- return derived(notAsked);
42035
- }
42036
- await writeSetupState({ declinedStandardVersion: team.version }).catch(() => {
42037
- });
42038
- rail.done(`Creating a new Standard ${s.gray}\xB7 it becomes v${team.version + 1} for the team${ctx.standard ? " (--standard new)" : ""}${s.reset}`);
42039
- replaces = team.version;
42040
- } else if (team.status === "declined-before") {
42041
- rail.line(`This project turned down the team's Standard v${team.version} before, so this run builds its own.`);
42042
- return derived(notAsked);
41593
+ for (const c of report.checks) {
41594
+ if (c.status === "ok") continue;
41595
+ ui.rail.warn(`${c.label}: ${c.detail}`);
41596
+ if (c.remedy) ui.rail.line(c.remedy);
42043
41597
  }
42044
- if (team.status === "unavailable") {
42045
- rail.warn("Could not ask the Verity service for this repository's Standard");
42046
- rail.line(terminalText(team.reason, 300));
42047
- return derived(notAsked);
41598
+ ui.rail.settle();
41599
+ return !report.checks.some((c) => c.required && c.status !== "ok");
41600
+ }
41601
+ async function telemetryStep(ui, answer, resumeDeferred, globals) {
41602
+ if (answer === "already-on") return "enabled";
41603
+ if (answer === "no") return "declined";
41604
+ if (answer === "turn-off") {
41605
+ const removed = await uninstallTelemetry();
41606
+ if (removed.ok) ui.rail.done("Cost & usage telemetry turned off (--telemetry off)");
41607
+ else ui.rail.warn(`Could not turn telemetry off: ${terminalText(removed.error, 200)}`);
41608
+ ui.rail.settle();
41609
+ return "declined";
42048
41610
  }
42049
- if (team.status === "skipped") return derived(notAsked);
42050
- if (!await rulesOffered(ui, ctx.globals)) return derived(notAsked);
42051
- const profile = buildRepoProfile();
42052
- try {
42053
- writeRepoFile(PROFILE_FILE, JSON.stringify(profile, null, 2) + "\n");
42054
- } catch (err2) {
42055
- rail.warn(`Could not write ${PROFILE_FILE} for you to read, so nothing was sent`);
42056
- rail.line(terminalText(err2.message, 200));
42057
- return derived({ outcome: "failed", detail: "error" });
41611
+ if (answer !== "yes" && !resumeDeferred) return void 0;
41612
+ const token = await resolveToken(globals.token);
41613
+ const url = await resolveServiceUrl(globals.serviceUrl);
41614
+ if (!token.ok || !url.ok) {
41615
+ ui.rail.line("Telemetry needs sign-in. Then run: verity telemetry install", "warn");
41616
+ ui.rail.settle();
41617
+ return "deferred";
42058
41618
  }
42059
- let detail;
42060
- let send;
42061
- if (ctx.profile) {
42062
- send = ctx.profile === "send";
42063
- detail = "given";
42064
- } else if (ui.asking) {
42065
- send = await askToSend(ui, profile);
42066
- detail = send ? "asked" : "declined";
41619
+ const installed2 = await installTelemetry(url.data);
41620
+ if (installed2.ok) {
41621
+ ui.rail.done("Cost & usage telemetry on");
41622
+ ui.rail.line("It starts with your next Claude Code session. See cost and usage at /usage.");
42067
41623
  } else {
42068
- send = false;
42069
- detail = "unattended";
41624
+ ui.rail.warn(`Could not turn telemetry on: ${terminalText(installed2.error, 200)}`);
41625
+ }
41626
+ ui.rail.gap();
41627
+ ui.rail.settle();
41628
+ return installed2.ok ? "enabled" : "deferred";
41629
+ }
41630
+ function signInDetail(outcome) {
41631
+ if (outcome === "already" || outcome === "new") return ["ok", outcome];
41632
+ if (outcome === "declined" || outcome === "unattended") return ["skipped", outcome];
41633
+ return ["failed", outcome === "unreachable" ? "unreachable" : "error"];
41634
+ }
41635
+ function formalDefault(previous, opts = {}) {
41636
+ const cfg = readProjectConfig();
41637
+ if (previous?.init?.completed_at || opts.setUpBefore || cfg.formal !== void 0 || cfg.formal_refused) return false;
41638
+ writeProjectConfig({ formal: "push" });
41639
+ return true;
41640
+ }
41641
+ function sayFormalOn(ui) {
41642
+ ui.rail.done("Formal verification at push: on");
41643
+ ui.rail.line("At push, Verity checks a small model of the riskiest changed function and replays any counterexample on the real code. That function's source is sent to Verity for the replay.");
41644
+ ui.rail.line("Only a reproduced bug can block, and most pushes get no model. Turn it off: verity config formal off");
41645
+ }
41646
+ function backgroundStep(ui, opts) {
41647
+ const { rail } = ui;
41648
+ const formal = readProjectConfig().formal;
41649
+ const said = opts.formalNew === true && formal === "push";
41650
+ if (said) sayFormalOn(ui);
41651
+ const lean = shouldStartBackgroundInstall({ formal });
41652
+ const leanStarted = lean.start && startBackgroundInstall({ cli: opts.cli });
41653
+ const trivy = startTrivyPrewarm();
41654
+ const parts2 = [
41655
+ ...trivy ? ["Trivy security scanner"] : [],
41656
+ ...leanStarted ? ["Lean 4 for formal checks at push (800 MB)"] : [],
41657
+ ...!lean.start && lean.why === "running" ? ["Lean 4 (already downloading)"] : []
41658
+ ];
41659
+ if (parts2.length) {
41660
+ if (said) rail.gap();
41661
+ rail.done("Getting ready in the background");
41662
+ rail.line(parts2.join(" \xB7 "));
41663
+ if (leanStarted) rail.line(said ? "Progress: verity lean status" : "Progress: verity lean status. Turn formal checks off: verity config formal off");
41664
+ } else if (!lean.start && lean.why === "installed" && formal === "push" && !said) {
41665
+ rail.done("Formal verification at push: on");
42070
41666
  }
42071
- if (!send) {
42072
- rail.warn("Profile not sent");
42073
- rail.line(detail === "unattended" ? "Nobody was asked, so nothing was sent. Your Standard has the built-in rules only." : "Your Standard has the built-in rules only.");
42074
- rail.line(`Add project rules later: verity standard generate (read ${PROFILE_FILE} first)`);
42075
- rail.gap();
42076
- return derived({ outcome: "skipped", detail });
41667
+ if (!lean.start && lean.why === "disk") {
41668
+ rail.warn("Formal verification is on, but Lean 4 needs more disk space");
41669
+ rail.line(`${lean.detail}. Free some space, then run: verity lean install`);
41670
+ } else if (!lean.start && lean.why === "no-release" && formal === "push") {
41671
+ rail.line("Lean 4 has no release for this machine, so the formal check at push is skipped.");
42077
41672
  }
42078
- if (detail === "given") rail.done(`Profile sent ${s.gray}\xB7 no source code (--profile send)${s.reset}`);
42079
- else rail.done(`Profile sent ${s.gray}\xB7 no source code${s.reset}`);
42080
- rail.gap();
41673
+ if (parts2.length || said || !lean.start && lean.why === "disk") rail.gap();
42081
41674
  rail.settle();
42082
- const generated = await generate(ui, ctx.globals, profile, replaces);
42083
- if (generated.status === "created") {
42084
- return end({ source: "generated", version: generated.version, profile: { outcome: "ok", detail } });
42085
- }
42086
- if (generated.status === "exists") {
42087
- const again = await adoptTeamStandard(ctx);
42088
- if (again.status === "adopted") {
42089
- sayAdopted(ui, again);
42090
- return end({ source: "adopted", version: again.version, profile: { outcome: "ok", detail } });
42091
- }
42092
- }
42093
- return derived({ outcome: "ok", detail });
42094
41675
  }
42095
41676
 
42096
- // src/commands/init/finish.ts
42097
- async function finishRun(ui, ctx) {
41677
+ // src/commands/init/scaffold.ts
41678
+ var import_promises17 = require("node:fs/promises");
41679
+ async function scaffoldStep(ui, given = {}) {
42098
41680
  const { rail } = ui;
42099
- const s = ui.screen.s;
42100
- const secs2 = Math.max(1, Math.round((Date.now() - ctx.started) / 1e3));
42101
- if (!ctx.haveStandard) {
42102
- sayChanged(ui, ctx.before);
42103
- rail.end(`${s.yellow}Not finished:${s.reset} no Standard was written, so the gate cannot review.`, "Fix the error above, then run verity init again.");
42104
- return;
41681
+ await (0, import_promises17.mkdir)(VERITY_DIR, { recursive: true });
41682
+ await ensureMemoryDir();
41683
+ const ignore = ensureVerityGitignore();
41684
+ if (ignore === "failed") {
41685
+ rail.warn(".gitignore: could not write the Verity block");
41686
+ rail.line("Add it by hand: .verity/ holds copies of analyzed files, including any secret the gate flagged.");
41687
+ } else if (ignore === "conflict") {
41688
+ rail.warn(".gitignore: git still ignores .verity/standard.yaml");
41689
+ rail.line("A global or nested .gitignore covers it, so the Standard cannot be committed. Check: git check-ignore -v .verity/standard.yaml");
41690
+ } else if (ignore === "memory-tracked") {
41691
+ rail.warn("This project commits its knowledge graph");
42105
41692
  }
42106
- if (ctx.pluginMode) {
42107
- const version = activePluginVersion();
42108
- if (version) await writeSetupState({ pluginSetup: { version, completed_at: (/* @__PURE__ */ new Date()).toISOString() }, wiring: void 0 });
42109
- } else if (ctx.noPlugin) {
42110
- await writeSetupState({ wiring: "project" });
41693
+ const trackedMemory = memoryOptOut() ? [] : committedMemoryFiles();
41694
+ if (trackedMemory.length > 0) {
41695
+ if (given.untrackMemory) {
41696
+ const result = fenceMemory();
41697
+ if (result.ok) rail.line(`Untracked ${result.untracked} knowledge-graph file(s) (staged). Commit to finish.`);
41698
+ else rail.line("Could not untrack the knowledge graph. Run: verity memory untrack", "warn");
41699
+ } else {
41700
+ rail.warn(`${trackedMemory.length} knowledge-graph file(s) are tracked in git`);
41701
+ rail.line("They show up in every diff. Stop with: verity memory untrack (or keep them on purpose: verity memory track)");
41702
+ }
42111
41703
  }
42112
- sayChanged(ui, ctx.before);
42113
- const next = ctx.momentIds.length === 0 ? "Verity reviews nothing until you turn a moment on: verity config moments turn,commit,push" : ctx.pluginMode ? ctx.signedIn ? "Verity reviews from your next turn." : "The security scan runs from your next turn. Sign in for AI review: verity login" : ctx.signedIn ? "Start a new Claude Code session. Verity reviews from its first turn." : "Start a new Claude Code session. The security scan runs from the first turn.";
42114
- rail.end(`${s.green}${s.bold}Ready${s.reset} ${s.gray}in ${secs2}s.${s.reset}`, next);
42115
- }
42116
- function sayChanged(ui, before) {
42117
- const changed = changedSince(before, snapshotInitFiles());
42118
- if (changed.length === 0) return;
42119
- ui.rail.line(`Changed: ${changed.join(", ")}. Details: verity doctor`, "muted");
42120
- }
42121
-
42122
- // src/commands/migrate.ts
42123
- var import_node_fs76 = require("node:fs");
42124
- var import_node_path64 = require("node:path");
42125
- var import_node_child_process22 = require("node:child_process");
42126
- var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
42127
- function defaultNpmRemover(pkg) {
42128
- (0, import_node_child_process22.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
42129
- }
42130
- function git2(cwd, args2) {
42131
- (0, import_node_child_process22.execFileSync)("git", hardenedGitArgv(args2), { cwd, stdio: "pipe", env: hardenedGitEnv() });
42132
- }
42133
- function isGitTracked(cwd, relPath) {
42134
- try {
42135
- git2(cwd, ["ls-files", "--error-unmatch", "--", relPath]);
42136
- return true;
42137
- } catch {
42138
- return false;
41704
+ const tracked = committedVerityState();
41705
+ if (tracked.length > 0) {
41706
+ if (given.untrackState) {
41707
+ const result = untrackVerityState();
41708
+ if (result === "untracked") rail.line("Untracked the Verity state files (staged). Commit to finish.");
41709
+ else if (result === "failed") rail.line("Could not untrack them. Run: git rm -r --cached .verity && git add .verity/standard.yaml", "warn");
41710
+ } else {
41711
+ rail.warn(`${tracked.length} Verity state file(s) are tracked in git`);
41712
+ rail.line(`For example ${tracked[0]}. Fix: git rm -r --cached .verity && git add .verity/standard.yaml`);
41713
+ }
42139
41714
  }
42140
- }
42141
- function isGitRepo(cwd) {
42142
41715
  try {
42143
- git2(cwd, ["rev-parse", "--is-inside-work-tree"]);
42144
- return true;
42145
- } catch {
42146
- return false;
41716
+ await ensureClaudeMdPointer();
41717
+ } catch (err2) {
41718
+ rail.line(`Could not update CLAUDE.md: ${err2.message}`, "warn");
42147
41719
  }
41720
+ const cfg = readProjectConfig();
41721
+ if (cfg.formal_refused) rail.line(`Formal verification: off \u2014 ${formalRefusalMessage(cfg.formal_refused)}`, "warn");
41722
+ rail.settle();
42148
41723
  }
42149
- async function runMigration(opts = {}) {
42150
- const root = opts.cwd ?? repoRoot();
42151
- const home = opts.home ?? process.env.HOME ?? "";
42152
- const npmRemover = opts.npmRemover ?? defaultNpmRemover;
42153
- const actions = [];
42154
- const movedProjectDir = migrateProjectDir(root, actions);
42155
- migrateGlobalCredentials(home, actions);
42156
- await migrateLegacyHooks(root, actions);
42157
- await migrateClaudeMd(root, actions);
42158
- migrateStandardFile(root, actions);
42159
- await migrateLocalTelemetry(root, actions);
42160
- removeLegacyPackage(movedProjectDir, npmRemover, actions);
42161
- return { actions, migrated: actions.length > 0 };
41724
+
41725
+ // src/commands/init/wiring.ts
41726
+ var import_node_fs74 = require("node:fs");
41727
+ var import_promises18 = require("node:fs/promises");
41728
+ var import_node_path63 = require("node:path");
41729
+ var DEFAULT_MOMENTS = ["stop", "pre-commit", "pre-push"];
41730
+ async function momentsFromInstalledHooks() {
41731
+ const status = await checkAllVerityHooks();
41732
+ const wired = [];
41733
+ if (status.stop) wired.push("stop");
41734
+ if (status.guardOn.includes("commit")) wired.push("pre-commit");
41735
+ if (status.guardOn.includes("push")) wired.push("pre-push");
41736
+ return wired.length > 0 ? wired : DEFAULT_MOMENTS;
42162
41737
  }
42163
- function migrateProjectDir(root, actions) {
42164
- const gateDir = (0, import_node_path64.join)(root, ".gate");
42165
- const verityDir = (0, import_node_path64.join)(root, ".verity");
42166
- if ((0, import_node_fs76.existsSync)(gateDir) && !(0, import_node_fs76.existsSync)(verityDir)) {
42167
- return migrateProjectDirRename(root, gateDir, verityDir, actions);
42168
- }
42169
- if ((0, import_node_fs76.existsSync)(gateDir) && (0, import_node_fs76.existsSync)(verityDir)) {
42170
- return migrateProjectDirCarry(gateDir, verityDir, actions);
41738
+ async function askSetupQuestions(_defaultsOnly, previous, given = {}) {
41739
+ const moments = given.moments ?? (previous?.moments?.length ? previous.moments : await momentsFromInstalledHooks());
41740
+ let telemetry = "not-asked";
41741
+ if (given.telemetry !== void 0) {
41742
+ const enabled = (await checkTelemetry()).enabled;
41743
+ telemetry = given.telemetry ? enabled ? "already-on" : "yes" : enabled ? "turn-off" : "no";
42171
41744
  }
42172
- return false;
41745
+ return { moments, telemetry };
42173
41746
  }
42174
- function migrateProjectDirRename(root, gateDir, verityDir, actions) {
42175
- let moved = false;
42176
- if (isGitRepo(root) && isGitTracked(root, ".gate")) {
42177
- if (hasStagedChanges(root)) {
42178
- printWarn(
42179
- "Migrating .gate \u2192 .verity with `git mv` while the index has staged changes \u2014 the rename will be staged alongside them. Review `git status` before committing."
42180
- );
42181
- }
42182
- try {
42183
- git2(root, ["mv", "--", ".gate", ".verity"]);
42184
- actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
42185
- moved = true;
42186
- } catch {
42187
- }
42188
- }
42189
- if (moved) {
42190
- if ((0, import_node_fs76.existsSync)(gateDir)) {
42191
- const carried = carryLegacyContents(gateDir, verityDir);
42192
- if (carried > 0) {
42193
- actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
42194
- }
42195
- try {
42196
- (0, import_node_fs76.rmSync)(gateDir, { recursive: true, force: true });
42197
- } catch {
42198
- }
42199
- }
42200
- return true;
42201
- }
42202
- moveDir(gateDir, verityDir);
42203
- actions.push("Moved .gate/ \u2192 .verity/");
42204
- return true;
41747
+ function parseTelemetryAnswer(raw) {
41748
+ const v = raw.trim().toLowerCase();
41749
+ if (v === "on") return true;
41750
+ if (v === "off") return false;
41751
+ return null;
42205
41752
  }
42206
- function migrateProjectDirCarry(gateDir, verityDir, actions) {
42207
- const carried = carryLegacyContents(gateDir, verityDir);
42208
- if (carried > 0) {
42209
- actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
42210
- }
42211
- try {
42212
- (0, import_node_fs76.rmSync)(gateDir, { recursive: true, force: true });
42213
- } catch {
42214
- }
42215
- return carried > 0;
41753
+ function planMoments(answers, opts) {
41754
+ const existing = readProjectConfig();
41755
+ const keptUserChoice = !opts.given && existing.git_moments_source === "user";
41756
+ const gitMoments = keptUserChoice ? existing.git_moments : [
41757
+ ...answers.includes("pre-commit") ? ["commit"] : [],
41758
+ ...answers.includes("pre-push") ? ["push"] : []
41759
+ ];
41760
+ const turnOn = keptUserChoice && existing.turn_review ? existing.turn_review === "on" : answers.includes("stop");
41761
+ return {
41762
+ momentIds: [
41763
+ ...turnOn ? ["stop"] : [],
41764
+ ...gitMoments.includes("commit") ? ["pre-commit"] : [],
41765
+ ...gitMoments.includes("push") ? ["pre-push"] : []
41766
+ ],
41767
+ gitMoments,
41768
+ turnOn,
41769
+ keptUserChoice
41770
+ };
42216
41771
  }
42217
- function migrateGlobalCredentials(home, actions) {
42218
- if (!home) return;
42219
- const gateCreds = (0, import_node_path64.join)(home, ".gate", "credentials");
42220
- const verityCreds = (0, import_node_path64.join)(home, ".verity", "credentials");
42221
- if (!(0, import_node_fs76.existsSync)(gateCreds)) return;
42222
- if (!(0, import_node_fs76.existsSync)(verityCreds)) {
42223
- (0, import_node_fs76.mkdirSync)((0, import_node_path64.join)(home, ".verity"), { recursive: true });
42224
- moveFile(gateCreds, verityCreds);
42225
- actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
42226
- return;
41772
+ function momentsStep(ui, plan) {
41773
+ const said = describeReviewMoments(plan.momentIds);
41774
+ if (plan.momentIds.length === 0) {
41775
+ ui.rail.warn("Verity reviews nothing");
41776
+ ui.rail.line("Code changes are not reviewed. Turn the moments on: verity config moments turn,commit,push", "warn");
41777
+ } else {
41778
+ ui.rail.done(`Verity reviews ${said}`);
41779
+ ui.rail.line("To change it, run verity config moments, or just ask Claude.");
42227
41780
  }
42228
- const added = mergeGlobalCredentials(gateCreds, verityCreds);
42229
- if (added > 0) {
42230
- actions.push(
42231
- `Merged ${added} legacy credential line(s) from ~/.gate/credentials into ~/.verity/credentials`
42232
- );
41781
+ if (plan.keptUserChoice) {
41782
+ ui.rail.line(`Keeping the git moments you set: ${plan.gitMoments.join(", ") || "none"} (this run asked no questions, so it does not overrule them).`);
42233
41783
  }
41784
+ ui.rail.gap();
41785
+ ui.rail.settle();
42234
41786
  }
42235
- async function migrateLegacyHooks(root, actions) {
42236
- const settings = await readSettingsAt(root);
42237
- const settingsBefore = JSON.stringify(settings);
42238
- const updatedSettings = removeVerityHooks(settings);
42239
- if (settingsBefore !== JSON.stringify(updatedSettings)) {
42240
- await writeSettingsAt(root, updatedSettings);
42241
- actions.push("Stripped legacy gate hooks from .claude/settings.json");
41787
+ var SKILLS = [
41788
+ "verity-setup",
41789
+ "verity-analyze",
41790
+ "verity-status",
41791
+ "verity-feedback",
41792
+ "verity-learn",
41793
+ "verity-memory",
41794
+ "verity-insights",
41795
+ "verity-reflect",
41796
+ "verity-init",
41797
+ "verity-prove"
41798
+ ];
41799
+ function resolveDataDir2() {
41800
+ const candidates2 = [
41801
+ (0, import_node_path63.join)(__dirname, "..", "data"),
41802
+ (0, import_node_path63.join)(__dirname, "..", "..", "data"),
41803
+ (0, import_node_path63.join)(process.cwd(), "cli", "data")
41804
+ ];
41805
+ for (const candidate of candidates2) {
41806
+ if ((0, import_node_fs74.existsSync)((0, import_node_path63.join)(candidate, "skills"))) return candidate;
42242
41807
  }
41808
+ throw new Error("Could not find Verity skill data. Reinstall: npm install -g @codacy/verity-cli");
42243
41809
  }
42244
- async function migrateClaudeMd(root, actions) {
42245
- const claudeMd = (0, import_node_path64.join)(root, "CLAUDE.md");
42246
- const hadLegacyBlock = (0, import_node_fs76.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
42247
- if (!hadLegacyBlock) return;
41810
+ async function skillIsCurrent(src, dest) {
41811
+ const list2 = (dir) => {
41812
+ const out2 = [];
41813
+ const walk2 = (d, prefix) => {
41814
+ for (const e of (0, import_node_fs74.readdirSync)(d, { withFileTypes: true })) {
41815
+ const rel = prefix ? `${prefix}/${e.name}` : e.name;
41816
+ if (e.isDirectory()) walk2((0, import_node_path63.join)(d, e.name), rel);
41817
+ else if (e.isFile()) out2.push(rel);
41818
+ }
41819
+ };
41820
+ walk2(dir, "");
41821
+ return out2.sort();
41822
+ };
42248
41823
  try {
42249
- await ensureClaudeMdPointer(root);
42250
- actions.push("Rewrote legacy gate-memory block in CLAUDE.md");
42251
- } catch (err2) {
42252
- printWarn(`Could not rewrite CLAUDE.md block: ${err2.message}`);
41824
+ const shipped = list2(src);
41825
+ if (JSON.stringify(shipped) !== JSON.stringify(list2(dest))) return false;
41826
+ for (const rel of shipped) {
41827
+ if (await (0, import_promises18.readFile)((0, import_node_path63.join)(src, rel), "utf-8") !== await (0, import_promises18.readFile)((0, import_node_path63.join)(dest, rel), "utf-8")) return false;
41828
+ }
41829
+ return true;
41830
+ } catch {
41831
+ return false;
42253
41832
  }
42254
41833
  }
42255
- function migrateStandardFile(root, actions) {
42256
- const gateMd = (0, import_node_path64.join)(root, "GATE.md");
42257
- const verityMd = (0, import_node_path64.join)(root, "VERITY.md");
42258
- if (!(0, import_node_fs76.existsSync)(gateMd) || (0, import_node_fs76.existsSync)(verityMd)) return;
42259
- let moved = false;
42260
- if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
42261
- try {
42262
- git2(root, ["mv", "--", "GATE.md", "VERITY.md"]);
42263
- moved = true;
42264
- } catch {
41834
+ async function installSkills(force) {
41835
+ const source2 = (0, import_node_path63.join)(resolveDataDir2(), "skills");
41836
+ const missing = [];
41837
+ for (const skill of SKILLS) {
41838
+ const src = (0, import_node_path63.join)(source2, skill);
41839
+ const dest = (0, import_node_path63.join)(".claude/skills", skill);
41840
+ if (!(0, import_node_fs74.existsSync)(src)) {
41841
+ missing.push(skill);
41842
+ continue;
42265
41843
  }
41844
+ if ((0, import_node_fs74.existsSync)(dest) && !force && await skillIsCurrent(src, dest)) continue;
41845
+ await (0, import_promises18.mkdir)(dest, { recursive: true });
41846
+ await (0, import_promises18.cp)(src, dest, { recursive: true, force: true });
42266
41847
  }
42267
- if (!moved) moveFile(gateMd, verityMd);
42268
- const content = readFileSyncSafe(verityMd);
42269
- const refreshed = content.split("GATE.md").join("VERITY.md");
42270
- if (refreshed !== content) (0, import_node_fs76.writeFileSync)(verityMd, refreshed);
42271
- actions.push("Renamed GATE.md \u2192 VERITY.md");
42272
- }
42273
- async function migrateLocalTelemetry(root, actions) {
42274
- const { removed } = await healLocalTelemetry(root);
42275
- if (removed.length === 0) return;
42276
- actions.push(
42277
- `Removed ${removed.length} old Verity telemetry key(s) from ${SETTINGS_LOCAL_FILE2} (Claude Code ignores them there). Run "verity telemetry install" to export from this machine.`
42278
- );
41848
+ return missing;
42279
41849
  }
42280
- function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
42281
- if (!movedProjectDir) return;
42282
- try {
42283
- npmRemover(LEGACY_NPM_PACKAGE);
42284
- actions.push(`Removed legacy global package ${LEGACY_NPM_PACKAGE}`);
42285
- } catch (err2) {
42286
- printWarn(
42287
- `Could not remove ${LEGACY_NPM_PACKAGE} (${err2.message}). Remove it manually: npm rm -g ${LEGACY_NPM_PACKAGE}`
42288
- );
41850
+ async function hooksStep(ui, ctx) {
41851
+ const { rail } = ui;
41852
+ if (ctx.staleMarker) rail.line(`Cleared a stale plugin marker \u2014 ${ctx.staleMarker} is no longer installed.`);
41853
+ if (!ctx.plan.keptUserChoice) {
41854
+ writeProjectConfig({ git_moments: ctx.plan.gitMoments, git_moments_source: "init", turn_review: ctx.plan.turnOn ? "on" : "off" });
42289
41855
  }
41856
+ if (ctx.pluginMode) {
41857
+ rail.done(`Hooks here: the Verity Claude Code plugin${ctx.pluginWhere}`);
41858
+ rail.line("Skills and hooks come from the plugin, so this run installs neither, and removes any this project still has from an npm install.");
41859
+ rail.line("Want this project to own its own copy instead? Uninstall the plugin, or use --no-plugin.");
41860
+ const settings = await readSettings();
41861
+ const stripped = removeVerityHooks(settings);
41862
+ if (JSON.stringify(settings.hooks ?? {}) !== JSON.stringify(stripped.hooks ?? {})) {
41863
+ await writeSettings(stripped);
41864
+ rail.line("Removed this project's Verity hooks from .claude/settings.json. The plugin wires them now, so each turn is reviewed once.");
41865
+ }
41866
+ const removed = removeProjectSkills();
41867
+ if (removed.length > 0) rail.line(`Removed ${removed.length} project-local skill${removed.length === 1 ? "" : "s"} that the plugin supersedes.`);
41868
+ } else {
41869
+ if (ctx.overridingPlugin) {
41870
+ rail.warn("Hooks here: this project's .claude/settings.json (--no-plugin)");
41871
+ rail.line("The Verity plugin is active on this machine and would own them. Both sets fire; the settings.json copy stands down at run time, so each turn is reviewed once.");
41872
+ } else {
41873
+ rail.done("Hooks here: this project's .claude/settings.json");
41874
+ }
41875
+ const missing = await installSkills(ctx.force);
41876
+ if (missing.length > 0) rail.line(`Skill data not found: ${missing.join(", ")}`, "warn");
41877
+ await applyMomentSelection(ctx.plan.momentIds);
41878
+ }
41879
+ rail.gap();
41880
+ rail.settle();
42290
41881
  }
42291
- function credentialRemoteKey(line2) {
42292
- const trimmed = line2.trim();
42293
- if (trimmed === "") return null;
42294
- const idx = trimmed.indexOf(" token:");
42295
- return idx === -1 ? trimmed : trimmed.slice(0, idx).trim();
42296
- }
42297
- function mergeGlobalCredentials(gateCreds, verityCreds) {
42298
- const verityContent = readFileSyncSafe(verityCreds);
42299
- const existingKeys = new Set(
42300
- verityContent.split("\n").map(credentialRemoteKey).filter((k) => k !== null)
42301
- );
42302
- const toAppend = [];
42303
- for (const line2 of readFileSyncSafe(gateCreds).split("\n")) {
42304
- const key = credentialRemoteKey(line2);
42305
- if (key === null || existingKeys.has(key)) continue;
42306
- existingKeys.add(key);
42307
- toAppend.push(line2.replace(/\r$/, ""));
41882
+
41883
+ // src/commands/init/sign-in.ts
41884
+ var import_node_child_process22 = require("node:child_process");
41885
+ async function classifyExistingLogin(serviceUrl, remote, opts) {
41886
+ const existing = await resolveToken(opts.token);
41887
+ if (!existing.ok) return { kind: "none" };
41888
+ const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
41889
+ if (who.ok && who.data.logged_in) {
41890
+ return {
41891
+ kind: "signed-in",
41892
+ identity: terminalText(who.data.email ?? `user #${who.data.user_id}`, 120),
41893
+ // Being logged in does not mean THIS repo is covered (VRT-83): grants come
41894
+ // from the login sweep. grant_status is non-null only when a grant exists.
41895
+ covered: remote ? who.data.grant_status != null : null,
41896
+ nudge: reverifyNudge(who.data)
41897
+ };
42308
41898
  }
42309
- if (toAppend.length > 0) {
42310
- const sep7 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
42311
- (0, import_node_fs76.writeFileSync)(verityCreds, verityContent + sep7 + toAppend.join("\n") + "\n");
41899
+ if (!who.ok) {
41900
+ const denial = authDenialRemedy(who.error);
41901
+ if (denial) return { kind: "rejected", code: denial.code, remedy: denial.remedy };
41902
+ const identity = existing.data.userId != null ? terminalText(existing.data.email ?? `user #${existing.data.userId}`, 120) : null;
41903
+ return { kind: "cached", identity, detail: terminalText(who.error, 200) };
42312
41904
  }
42313
- (0, import_node_fs76.rmSync)(gateCreds, { force: true });
42314
- return toAppend.length;
41905
+ return { kind: "anonymous" };
42315
41906
  }
42316
- function readFileSyncSafe(path) {
41907
+ function gitRemote() {
42317
41908
  try {
42318
- return (0, import_node_fs76.readFileSync)(path, "utf-8");
41909
+ return (0, import_node_child_process22.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
42319
41910
  } catch {
42320
41911
  return "";
42321
41912
  }
42322
41913
  }
42323
- function hasStagedChanges(root) {
42324
- try {
42325
- git2(root, ["diff", "--cached", "--quiet", ...DIFF_READ_FLAGS]);
42326
- return false;
42327
- } catch {
42328
- return true;
42329
- }
42330
- }
42331
- function moveDir(from, to) {
42332
- try {
42333
- (0, import_node_fs76.renameSync)(from, to);
42334
- } catch (err2) {
42335
- if (err2.code !== "EXDEV") throw err2;
42336
- (0, import_node_fs76.cpSync)(from, to, { recursive: true });
42337
- (0, import_node_fs76.rmSync)(from, { recursive: true, force: true });
42338
- }
41914
+ function sayWhatASkipCosts(ui) {
41915
+ ui.rail.line("Without sign-in, only the local security scan runs. AI review needs sign-in.");
41916
+ ui.rail.line("Sign in later: verity login");
42339
41917
  }
42340
- function moveFile(from, to) {
42341
- try {
42342
- (0, import_node_fs76.renameSync)(from, to);
42343
- } catch (err2) {
42344
- if (err2.code !== "EXDEV") throw err2;
42345
- (0, import_node_fs76.cpSync)(from, to);
42346
- (0, import_node_fs76.rmSync)(from, { force: true });
41918
+ function coverageLines(ui, repo, covered) {
41919
+ if (covered === null) {
41920
+ ui.rail.line("This folder has no git remote, so there is no project to sync to.");
41921
+ } else if (covered) {
41922
+ ui.rail.line(`${repo} is covered.`);
41923
+ } else {
41924
+ ui.rail.line(`${repo} is not covered by your Verity access, so nothing syncs.`, "warn");
41925
+ ui.rail.line("Give the Verity GitHub App access to it, then run: verity login");
42347
41926
  }
42348
41927
  }
42349
- function carryLegacyContents(gateDir, verityDir) {
42350
- if (!isRealDir(gateDir) || !isRealDir(verityDir)) return 0;
42351
- let copied = 0;
42352
- const walk2 = (relDir) => {
42353
- let entries;
42354
- try {
42355
- entries = (0, import_node_fs76.readdirSync)((0, import_node_path64.join)(gateDir, relDir), { withFileTypes: true });
42356
- } catch {
42357
- return;
41928
+ async function signInStep(ui, resolution, opts) {
41929
+ const { rail, screen } = ui;
41930
+ const s = screen.s;
41931
+ const end = (outcome) => {
41932
+ rail.gap();
41933
+ rail.settle();
41934
+ return outcome;
41935
+ };
41936
+ let named = false;
41937
+ const nameTarget = () => {
41938
+ if (named) return;
41939
+ named = true;
41940
+ rail.note(`Verity service: ${terminalText(resolution.url, 200)}${resolution.source === "default" ? " (the default)" : ` (from ${resolution.source})`}`);
41941
+ };
41942
+ if ((await resolveToken(opts.token)).ok) nameTarget();
41943
+ const heal = await maybeHealServiceUrl(resolution, opts.verbose);
41944
+ const serviceUrl = heal.serviceUrl;
41945
+ const remote = gitRemote();
41946
+ if (heal.healed) {
41947
+ rail.warn("The saved Verity service no longer answers");
41948
+ rail.line("Sign in again to repair ~/.verity/credentials.");
41949
+ } else {
41950
+ const state = await classifyExistingLogin(serviceUrl, remote, opts);
41951
+ if (state.kind === "signed-in") {
41952
+ rail.done(`Signed in as ${s.bold}${state.identity}${s.reset}`);
41953
+ coverageLines(ui, opts.repo, state.covered);
41954
+ if (state.nudge) rail.line(state.nudge, "warn");
41955
+ return end("already");
42358
41956
  }
42359
- for (const entry of entries) {
42360
- const rel = relDir ? (0, import_node_path64.join)(relDir, entry.name) : entry.name;
42361
- if (entry.isSymbolicLink()) continue;
42362
- if (entry.isDirectory()) {
42363
- walk2(rel);
42364
- continue;
42365
- }
42366
- if (!entry.isFile() || !ensureRealDirs(verityDir, relDir)) continue;
42367
- const opened = openRegularInRoot(gateDir, rel);
42368
- if (!opened.ok) continue;
42369
- try {
42370
- (0, import_node_fs76.writeFileSync)((0, import_node_path64.join)(verityDir, rel), readOpened(opened.fd, opened.size), { flag: "wx", mode: 384 });
42371
- copied++;
42372
- } catch {
42373
- }
41957
+ if (state.kind === "cached") {
41958
+ rail.done(state.identity ? `Signed in as ${s.bold}${state.identity}${s.reset} (cached)` : "Using your existing Verity token");
41959
+ rail.line(`Could not reach the Verity service to confirm it: ${state.detail}`);
41960
+ return end("unreachable");
41961
+ }
41962
+ if (state.kind === "rejected") {
41963
+ rail.warn(`Your Verity sign-in was rejected (${state.code})`);
41964
+ rail.line(state.remedy);
41965
+ } else if (state.kind === "anonymous") {
41966
+ rail.warn("This project has only an anonymous Verity token");
41967
+ rail.line("The gate runs, but no runs are saved and Verity keeps no memory of this project.");
41968
+ }
41969
+ rail.settle();
41970
+ }
41971
+ if (!ui.asking) {
41972
+ rail.warn(ui.term.screen ? "Sign-in skipped (--yes)" : "Sign-in skipped (no terminal to ask on)");
41973
+ sayWhatASkipCosts(ui);
41974
+ return end("unattended");
41975
+ }
41976
+ rail.active("Sign in with GitHub");
41977
+ rail.line("One sign-in covers every repository on this machine.");
41978
+ rail.line("Signing in does not give Verity access to the contents of your repositories.");
41979
+ rail.gap();
41980
+ rail.keys([["Enter", "open the browser"], ["s", "skip"]]);
41981
+ const choice = await screen.key(["enter", "s"], "s");
41982
+ if (choice === "s") {
41983
+ rail.collapse();
41984
+ rail.warn("Sign-in skipped");
41985
+ sayWhatASkipCosts(ui);
41986
+ return end("declined");
41987
+ }
41988
+ const failed = (why) => {
41989
+ rail.collapse();
41990
+ rail.warn("Sign-in did not complete");
41991
+ rail.line(terminalText(why, 300));
41992
+ sayWhatASkipCosts(ui);
41993
+ return end("failed");
41994
+ };
41995
+ nameTarget();
41996
+ const code = await requestDeviceCode();
41997
+ if (!code.ok) return failed(code.error);
41998
+ const dc = code.data;
41999
+ const copied = copyToClipboard(dc.user_code);
42000
+ const opened = openInBrowser(dc.verification_uri);
42001
+ rail.line(opened ? `Opened ${dc.verification_uri} in your browser. If no window appeared, open it and enter the code.` : `Open ${dc.verification_uri} and enter the code.`, opened ? "muted" : "plain");
42002
+ const prefix = `${s.gray}\u2502${s.reset} `;
42003
+ const polled = await screen.breathe(
42004
+ prefix,
42005
+ () => `Waiting for you in the browser \xB7 code ${s.bold}${dc.user_code}${s.reset}${copied ? ` ${s.gray}(copied)${s.reset}` : ""}`,
42006
+ pollDeviceToken(dc, { codeExpiresAt: Date.now() + dc.expires_in * 1e3, quiet: true })
42007
+ );
42008
+ if (!polled.ok) return failed(polled.error);
42009
+ let gaveUp = null;
42010
+ const appInstallUi = {
42011
+ wait: async (ctx, visible) => {
42012
+ rail.line(`The Verity GitHub App cannot see ${ctx.owner}/${ctx.repo} yet. Install it${ctx.opened ? " (opened in your browser)" : ""}:`, "warn");
42013
+ rail.line(ctx.installUrl, "plain");
42014
+ return screen.breathe(prefix, () => `Waiting for the install on ${ctx.owner}`, visible);
42015
+ },
42016
+ gaveUp: (message) => {
42017
+ gaveUp = message;
42374
42018
  }
42375
42019
  };
42376
- walk2("");
42377
- return copied;
42020
+ const login = await screen.spin(prefix, () => "Signing in to Verity", loginOnce({
42021
+ serviceUrl,
42022
+ remote: remote || void 0,
42023
+ verbose: opts.verbose,
42024
+ providerToken: polled.data,
42025
+ appInstallUi
42026
+ }));
42027
+ if (!login.ok) return failed(login.error);
42028
+ const report = await describeLoginOutcome(login.data, { remote: remote || void 0, verbose: opts.verbose });
42029
+ rail.collapse();
42030
+ const identity = terminalText(login.data.email ?? (login.data.userId != null ? `user #${login.data.userId}` : "your account"), 120);
42031
+ rail.done(`Signed in as ${s.bold}${identity}${s.reset}`);
42032
+ if (gaveUp) rail.line(terminalText(gaveUp, 300), "warn");
42033
+ for (const line2 of report.slice(1)) {
42034
+ if (line2.level === "verbose") continue;
42035
+ rail.line(line2.text.trim().replace(/^✓\s*/, ""), line2.level === "warn" ? "warn" : "muted");
42036
+ }
42037
+ return end("new");
42378
42038
  }
42379
- function isRealDir(path) {
42039
+
42040
+ // src/commands/init/standard.ts
42041
+ var import_node_fs75 = require("node:fs");
42042
+
42043
+ // src/commands/init/rules.ts
42044
+ async function projectLabel(token, serviceUrl, verbose) {
42380
42045
  try {
42381
- const st = (0, import_node_fs76.lstatSync)(path);
42382
- return st.isDirectory() && !st.isSymbolicLink();
42046
+ const who = await whoami(token, serviceUrl, verbose);
42047
+ return who.ok ? who.data.project_id : null;
42383
42048
  } catch {
42384
- return false;
42049
+ return null;
42385
42050
  }
42386
42051
  }
42387
- function ensureRealDirs(root, relDir) {
42388
- let current = root;
42389
- for (const segment of relDir.split(/[\\/]/).filter(Boolean)) {
42390
- current = (0, import_node_path64.join)(current, segment);
42391
- try {
42392
- (0, import_node_fs76.mkdirSync)(current);
42393
- } catch (e) {
42394
- if (e.code !== "EEXIST") return false;
42052
+ function profilePreview(profile) {
42053
+ return JSON.stringify(profile, null, 2).split("\n").slice(0, 12).map((l) => terminalText(l, 400)).concat(["\u2026", `The whole profile: ${PROFILE_FILE}`]);
42054
+ }
42055
+ async function askToSend(ui, profile) {
42056
+ const { rail, screen } = ui;
42057
+ const lines = consentLines(profile);
42058
+ rail.active("Write review rules for this repository?");
42059
+ rail.box(lines.slice(1, -1));
42060
+ rail.line(lines[lines.length - 1]);
42061
+ rail.gap();
42062
+ rail.keys([["Enter", "send"], ["v", "view the profile"], ["n", "skip"]]);
42063
+ let allowed = ["enter", "v", "n"];
42064
+ for (; ; ) {
42065
+ const k = await screen.key(allowed, "n", { freshMs: 300 });
42066
+ if (k !== "v") {
42067
+ rail.collapse();
42068
+ return k === "enter";
42395
42069
  }
42396
- if (!isRealDir(current)) return false;
42070
+ rail.gap();
42071
+ for (const l of profilePreview(profile)) rail.line(l);
42072
+ rail.gap();
42073
+ rail.keys([["Enter", "send"], ["n", "skip"]]);
42074
+ allowed = ["enter", "n"];
42397
42075
  }
42398
- return true;
42399
42076
  }
42400
- async function needsMigration(root = repoRoot()) {
42401
- const gateDir = (0, import_node_path64.join)(root, ".gate");
42402
- const verityDir = (0, import_node_path64.join)(root, ".verity");
42403
- if ((0, import_node_fs76.existsSync)(gateDir) && !(0, import_node_fs76.existsSync)(verityDir)) return true;
42404
- if ((0, import_node_fs76.existsSync)(gateDir) && (0, import_node_fs76.existsSync)(verityDir)) {
42405
- if ((0, import_node_fs76.existsSync)((0, import_node_path64.join)(gateDir, "credentials")) && !(0, import_node_fs76.existsSync)((0, import_node_path64.join)(verityDir, "credentials"))) {
42406
- return true;
42407
- }
42408
- if ((0, import_node_fs76.existsSync)((0, import_node_path64.join)(gateDir, "memory")) && !(0, import_node_fs76.existsSync)((0, import_node_path64.join)(verityDir, "memory"))) {
42409
- return true;
42410
- }
42077
+ async function generate(ui, globals, profile, replaces) {
42078
+ const { rail, screen } = ui;
42079
+ const s = screen.s;
42080
+ const token = await resolveToken(globals.token);
42081
+ const url = await resolveServiceUrl(globals.serviceUrl);
42082
+ if (!token.ok || !url.ok) return { status: "fallback" };
42083
+ const commits = profile.history.commits.length;
42084
+ const fixes = profile.history.commits.filter((c) => c.fix).length;
42085
+ const reverts = profile.history.commits.filter((c) => /^revert\b/i.test(c.subject)).length;
42086
+ rail.active("Writing review rules");
42087
+ await rail.counted(([c, f, r]) => `${s.green}\u2713${s.reset} Read ${c} commits ${s.gray}\xB7${s.reset} ${f} fixes ${s.gray}\xB7${s.reset} ${r} reverts`, [commits, fixes, reverts]);
42088
+ if (profile.docs.length) rail.ticked(`Read ${profile.docs.map((d) => terminalText(d.name, 120)).join(" and ")}`);
42089
+ if (ui.term.screen && !ui.term.motion) rail.line("Writing rules for this repository\u2026");
42090
+ const started = Date.now();
42091
+ const base = buildStandard({ detected: detectProject() }, loadCatalog());
42092
+ const outcome = await screen.spin(`${s.gray}\u2502${s.reset} `, () => "Writing rules for this repository", generateStandard({
42093
+ serviceUrl: url.data,
42094
+ token: token.data.token,
42095
+ profile,
42096
+ base,
42097
+ projectId: await projectLabel(token.data.token, url.data, globals.verbose),
42098
+ verbose: globals.verbose,
42099
+ replaces
42100
+ }));
42101
+ rail.collapse();
42102
+ if (outcome.status === "exists") return { status: "exists" };
42103
+ if (outcome.status === "disabled") {
42104
+ rail.line("Project rules are not switched on for this project yet. The built-in rules apply.");
42105
+ rail.gap();
42106
+ return { status: "fallback" };
42411
42107
  }
42412
- const claudeMd = (0, import_node_path64.join)(root, "CLAUDE.md");
42413
- if ((0, import_node_fs76.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
42414
- return true;
42108
+ if (outcome.status === "unsupported") {
42109
+ rail.line(`This Verity service (${terminalText(url.data, 200)}) does not write project rules yet: it is older than this CLI. The built-in rules apply.`);
42110
+ rail.gap();
42111
+ return { status: "fallback" };
42415
42112
  }
42416
- if ((0, import_node_fs76.existsSync)((0, import_node_path64.join)(root, "GATE.md")) && !(0, import_node_fs76.existsSync)((0, import_node_path64.join)(root, "VERITY.md"))) {
42417
- return true;
42113
+ if (outcome.status === "failed") {
42114
+ rail.warn("The rules could not be written this time");
42115
+ rail.line(terminalText(outcome.reason, 300));
42116
+ rail.line("The built-in rules apply. Try again later: verity standard generate");
42117
+ rail.gap();
42118
+ return { status: "fallback" };
42418
42119
  }
42419
- if (await hasLegacyHooksAt(root)) return true;
42420
- return false;
42421
- }
42422
- function registerMigrateCommand(program2) {
42423
- program2.command("migrate").description("Migrate a legacy GATE.md install to Verity (.gate \u2192 .verity, hooks, CLAUDE.md)").action(async () => {
42424
- let result;
42425
- try {
42426
- result = await runMigration();
42427
- } catch (err2) {
42428
- printError(`Migration failed: ${err2.message}`);
42429
- process.exitCode = 1;
42430
- return;
42431
- }
42432
- if (!result.migrated) {
42433
- printInfo("Nothing to migrate \u2014 this project is already on Verity.");
42434
- return;
42435
- }
42436
- printInfo("Migration complete:");
42437
- for (const a of result.actions) printInfo(` ${a} \u2713`);
42438
- });
42120
+ const secs2 = Math.max(1, Math.round((Date.now() - started) / 1e3));
42121
+ const docs = profile.docs.length;
42122
+ if (outcome.rules.length === 0) {
42123
+ rail.warn("No project rules this time");
42124
+ rail.line("The proposed rules were too generic, or not safe to use. The built-in rules apply.");
42125
+ } else {
42126
+ const n2 = outcome.rules.length;
42127
+ const from = [`${commits} ${commits === 1 ? "commit" : "commits"}`, ...docs ? [`${docs} ${docs === 1 ? "doc" : "docs"}`] : []].join(" and ");
42128
+ rail.done(`${n2} ${n2 === 1 ? "rule" : "rules"} for this repository ${s.gray}\xB7 from ${from} \xB7 ${secs2}s${s.reset}`);
42129
+ await rail.rules(outcome.rules);
42130
+ if (outcome.rejected > 0) rail.line(`${outcome.rejected} proposed ${outcome.rejected === 1 ? "rule was" : "rules were"} left out: too generic, or not safe to use.`);
42131
+ }
42132
+ rail.gap();
42133
+ rail.done(`Standard v${outcome.version} saved`);
42134
+ rail.line(`Your team gets the same rules. Edit them in ${STANDARD_FILE}`);
42135
+ if (outcome.warning) rail.line(terminalText(outcome.warning, 600), "warn");
42136
+ return { status: "created", version: outcome.version };
42439
42137
  }
42440
42138
 
42441
- // src/commands/init/sign-in.ts
42442
- var import_node_child_process23 = require("node:child_process");
42443
- async function classifyExistingLogin(serviceUrl, remote, opts) {
42444
- const existing = await resolveToken(opts.token);
42445
- if (!existing.ok) return { kind: "none" };
42446
- const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
42447
- if (who.ok && who.data.logged_in) {
42448
- return {
42449
- kind: "signed-in",
42450
- identity: terminalText(who.data.email ?? `user #${who.data.user_id}`, 120),
42451
- // Being logged in does not mean THIS repo is covered (VRT-83): grants come
42452
- // from the login sweep. grant_status is non-null only when a grant exists.
42453
- covered: remote ? who.data.grant_status != null : null,
42454
- nudge: reverifyNudge(who.data)
42455
- };
42456
- }
42457
- if (!who.ok) {
42458
- const denial = authDenialRemedy(who.error);
42459
- if (denial) return { kind: "rejected", code: denial.code, remedy: denial.remedy };
42460
- const identity = existing.data.userId != null ? terminalText(existing.data.email ?? `user #${existing.data.userId}`, 120) : null;
42461
- return { kind: "cached", identity, detail: terminalText(who.error, 200) };
42139
+ // src/commands/init/standard.ts
42140
+ async function lookUpTeamStandard(opts) {
42141
+ if (!opts.adopt) return { status: "skipped", why: "no-adopt" };
42142
+ const token = await resolveToken(opts.globals.token);
42143
+ const url = await resolveServiceUrl(opts.globals.serviceUrl);
42144
+ if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
42145
+ const found = await fetchRemoteSetup({ token: token.data.token, serviceUrl: url.data, verbose: opts.globals.verbose });
42146
+ if (found.status === "unavailable") return { status: "unavailable", reason: terminalText(found.reason, 300) };
42147
+ if (found.status === "none") return { status: "none" };
42148
+ if (opts.previous?.declinedStandardVersion === found.standard.version) {
42149
+ return { status: "declined-before", version: found.standard.version };
42462
42150
  }
42463
- return { kind: "anonymous" };
42151
+ return { status: "found", found, version: found.standard.version };
42464
42152
  }
42465
- function gitRemote() {
42153
+ async function writeTeamStandard(team, opts) {
42154
+ const token = await resolveToken(opts.globals.token);
42155
+ const url = await resolveServiceUrl(opts.globals.serviceUrl);
42156
+ if (!token.ok || !url.ok) return { status: "skipped", why: "not-signed-in" };
42157
+ const found = team.found;
42466
42158
  try {
42467
- return (0, import_node_child_process23.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
42468
- } catch {
42469
- return "";
42159
+ const outcome = await adoptRemoteSetup(found, {
42160
+ serviceUrl: url.data,
42161
+ projectId: await projectLabel(token.data.token, url.data, opts.globals.verbose)
42162
+ });
42163
+ if (opts.previous?.declinedStandardVersion != null) {
42164
+ await writeSetupState({ declinedStandardVersion: void 0 }).catch(() => {
42165
+ });
42166
+ }
42167
+ return { status: "adopted", version: found.standard.version, summary: describeRemote(found), notes: outcome.notes };
42168
+ } catch (err2) {
42169
+ return { status: "write-failed", reason: terminalText(err2.message, 300) };
42470
42170
  }
42471
42171
  }
42472
- function sayWhatASkipCosts(ui) {
42473
- ui.rail.line("Without sign-in, only the local security scan runs. AI review needs sign-in.");
42474
- ui.rail.line("Sign in later: verity login");
42172
+ async function adoptTeamStandard(opts) {
42173
+ const team = await lookUpTeamStandard(opts);
42174
+ return team.status === "found" ? writeTeamStandard(team, opts) : team;
42475
42175
  }
42476
- function coverageLines(ui, repo, covered) {
42477
- if (covered === null) {
42478
- ui.rail.line("This folder has no git remote, so there is no project to sync to.");
42479
- } else if (covered) {
42480
- ui.rail.line(`${repo} is covered.`);
42481
- } else {
42482
- ui.rail.line(`${repo} is not covered by your Verity access, so nothing syncs.`, "warn");
42483
- ui.rail.line("Give the Verity GitHub App access to it, then run: verity login");
42176
+ async function askTeamChoice(ui, team) {
42177
+ const { rail, screen } = ui;
42178
+ rail.active("Your team already has a Standard for this repository");
42179
+ rail.line(describeRemote(team.found));
42180
+ rail.line(`Use it to review with the same rules as everyone else. A new one becomes v${team.version + 1} for the whole team.`);
42181
+ rail.gap();
42182
+ rail.keys([["Enter", "use it"], ["n", "create a new one"]]);
42183
+ const k = await screen.key(["enter", "n"], "enter", { freshMs: 300 });
42184
+ rail.collapse();
42185
+ return k === "n" ? "new" : "use";
42186
+ }
42187
+ function sayAdopted(ui, team) {
42188
+ ui.rail.done(`Using your team's Standard v${team.version}`);
42189
+ ui.rail.line(terminalText(team.summary, 300));
42190
+ for (const note of team.notes) ui.rail.line(terminalText(note, 300), "warn");
42191
+ }
42192
+ async function deriveFromCatalogue(ui, globals) {
42193
+ const url = await resolveServiceUrl(globals.serviceUrl);
42194
+ try {
42195
+ const result = await runSynthesis({ serviceUrl: url.ok ? url.data : void 0 });
42196
+ if ("refused" in result) {
42197
+ ui.rail.warn("No Standard was written");
42198
+ ui.rail.line(result.refused);
42199
+ return { ok: false, version: null };
42200
+ }
42201
+ const pushed = await pushStandard(globals);
42202
+ if (pushed.standardVersion !== null) {
42203
+ await correctVerityMdVersion({
42204
+ standard: result.standard,
42205
+ version: pushed.standardVersion,
42206
+ tools: result.tools,
42207
+ serviceUrl: url.ok ? url.data : void 0
42208
+ });
42209
+ ui.rail.done(`Standard v${pushed.standardVersion} saved`);
42210
+ ui.rail.line(`Your team gets the same rules. Edit them in ${STANDARD_FILE}`);
42211
+ } else {
42212
+ ui.rail.done("Standard saved on this machine");
42213
+ }
42214
+ for (const line2 of pushed.lines) {
42215
+ if (line2.startsWith("Standard pushed")) continue;
42216
+ if (line2.startsWith("Not signed in")) ui.rail.line(`Sign in to share it with your team: verity login, then verity standard push. Edit it in ${STANDARD_FILE}`);
42217
+ else ui.rail.line(line2, "warn");
42218
+ }
42219
+ for (const note of result.notes) ui.rail.line(note, "warn");
42220
+ return { ok: true, version: pushed.standardVersion };
42221
+ } catch (err2) {
42222
+ ui.rail.warn("No Standard was written");
42223
+ ui.rail.line(terminalText(err2.message, 300));
42224
+ return { ok: false, version: null };
42484
42225
  }
42485
42226
  }
42486
- async function signInStep(ui, resolution, opts) {
42487
- const { rail, screen } = ui;
42488
- const s = screen.s;
42489
- const end = (outcome) => {
42227
+ async function standardStep(ui, ctx) {
42228
+ const { rail } = ui;
42229
+ const s = ui.screen.s;
42230
+ const notAsked = { outcome: "skipped", detail: "none" };
42231
+ const end = (r) => {
42490
42232
  rail.gap();
42491
42233
  rail.settle();
42492
- return outcome;
42234
+ return r;
42493
42235
  };
42494
- let named = false;
42495
- const nameTarget = () => {
42496
- if (named) return;
42497
- named = true;
42498
- rail.note(`Verity service: ${terminalText(resolution.url, 200)}${resolution.source === "default" ? " (the default)" : ` (from ${resolution.source})`}`);
42236
+ const derived = async (profile2) => {
42237
+ const d = await deriveFromCatalogue(ui, ctx.globals);
42238
+ return end({ source: d.ok ? "derived" : "none", version: d.version, profile: profile2 });
42499
42239
  };
42500
- if ((await resolveToken(opts.token)).ok) nameTarget();
42501
- const heal = await maybeHealServiceUrl(resolution, opts.verbose);
42502
- const serviceUrl = heal.serviceUrl;
42503
- const remote = gitRemote();
42504
- if (heal.healed) {
42505
- rail.warn("The saved Verity service no longer answers");
42506
- rail.line("Sign in again to repair ~/.verity/credentials.");
42507
- } else {
42508
- const state = await classifyExistingLogin(serviceUrl, remote, opts);
42509
- if (state.kind === "signed-in") {
42510
- rail.done(`Signed in as ${s.bold}${state.identity}${s.reset}`);
42511
- coverageLines(ui, opts.repo, state.covered);
42512
- if (state.nudge) rail.line(state.nudge, "warn");
42513
- return end("already");
42514
- }
42515
- if (state.kind === "cached") {
42516
- rail.done(state.identity ? `Signed in as ${s.bold}${state.identity}${s.reset} (cached)` : "Using your existing Verity token");
42517
- rail.line(`Could not reach the Verity service to confirm it: ${state.detail}`);
42518
- return end("unreachable");
42519
- }
42520
- if (state.kind === "rejected") {
42521
- rail.warn(`Your Verity sign-in was rejected (${state.code})`);
42522
- rail.line(state.remedy);
42523
- } else if (state.kind === "anonymous") {
42524
- rail.warn("This project has only an anonymous Verity token");
42525
- rail.line("The gate runs, but no runs are saved and Verity keeps no memory of this project.");
42240
+ if ((0, import_node_fs75.existsSync)(projectPath(STANDARD_FILE))) {
42241
+ const sync = await pushLocalStandardIfMissing(ctx.globals);
42242
+ rail.done("Kept your Standard");
42243
+ rail.line(`${STANDARD_FILE} is already here, so this run leaves it as it is.`);
42244
+ if (sync.status === "pushed") {
42245
+ 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");
42246
+ for (const line2 of sync.lines) if (!line2.startsWith("Standard pushed")) rail.line(line2, "warn");
42526
42247
  }
42527
- rail.settle();
42528
- }
42529
- if (!ui.asking) {
42530
- rail.warn(ui.term.screen ? "Sign-in skipped (--yes)" : "Sign-in skipped (no terminal to ask on)");
42531
- sayWhatASkipCosts(ui);
42532
- return end("unattended");
42533
- }
42534
- rail.active("Sign in with GitHub");
42535
- rail.line("One sign-in covers every repository on this machine.");
42536
- rail.line("Signing in does not give Verity access to the contents of your repositories.");
42537
- rail.gap();
42538
- rail.keys([["Enter", "open the browser"], ["s", "skip"]]);
42539
- const choice = await screen.key(["enter", "s"], "s");
42540
- if (choice === "s") {
42541
- rail.collapse();
42542
- rail.warn("Sign-in skipped");
42543
- sayWhatASkipCosts(ui);
42544
- return end("declined");
42248
+ return end({ source: "kept", version: sync.status === "pushed" ? sync.version : null, profile: notAsked });
42545
42249
  }
42546
- const failed = (why) => {
42547
- rail.collapse();
42548
- rail.warn("Sign-in did not complete");
42549
- rail.line(terminalText(why, 300));
42550
- sayWhatASkipCosts(ui);
42551
- return end("failed");
42552
- };
42553
- nameTarget();
42554
- const code = await requestDeviceCode();
42555
- if (!code.ok) return failed(code.error);
42556
- const dc = code.data;
42557
- const copied = copyToClipboard(dc.user_code);
42558
- const opened = openInBrowser(dc.verification_uri);
42559
- rail.line(opened ? `Opened ${dc.verification_uri} in your browser. If no window appeared, open it and enter the code.` : `Open ${dc.verification_uri} and enter the code.`, opened ? "muted" : "plain");
42560
- const prefix = `${s.gray}\u2502${s.reset} `;
42561
- const polled = await screen.breathe(
42562
- prefix,
42563
- () => `Waiting for you in the browser \xB7 code ${s.bold}${dc.user_code}${s.reset}${copied ? ` ${s.gray}(copied)${s.reset}` : ""}`,
42564
- pollDeviceToken(dc, { codeExpiresAt: Date.now() + dc.expires_in * 1e3, quiet: true })
42565
- );
42566
- if (!polled.ok) return failed(polled.error);
42567
- let gaveUp = null;
42568
- const appInstallUi = {
42569
- wait: async (ctx, visible) => {
42570
- rail.line(`The Verity GitHub App cannot see ${ctx.owner}/${ctx.repo} yet. Install it${ctx.opened ? " (opened in your browser)" : ""}:`, "warn");
42571
- rail.line(ctx.installUrl, "plain");
42572
- return screen.breathe(prefix, () => `Waiting for the install on ${ctx.owner}`, visible);
42573
- },
42574
- gaveUp: (message) => {
42575
- gaveUp = message;
42250
+ const team = await lookUpTeamStandard(ctx);
42251
+ let replaces;
42252
+ if (team.status === "found") {
42253
+ const choice = ctx.standard ?? (ui.asking ? await askTeamChoice(ui, team) : "use");
42254
+ if (choice === "use") {
42255
+ const adopted = await writeTeamStandard(team, ctx);
42256
+ if (adopted.status === "adopted") {
42257
+ sayAdopted(ui, adopted);
42258
+ return end({ source: "adopted", version: adopted.version, profile: notAsked });
42259
+ }
42260
+ rail.warn("Could not write the team's Standard here");
42261
+ if (adopted.status === "write-failed") rail.line(adopted.reason);
42262
+ return derived(notAsked);
42576
42263
  }
42577
- };
42578
- const login = await screen.spin(prefix, () => "Signing in to Verity", loginOnce({
42579
- serviceUrl,
42580
- remote: remote || void 0,
42581
- verbose: opts.verbose,
42582
- providerToken: polled.data,
42583
- appInstallUi
42584
- }));
42585
- if (!login.ok) return failed(login.error);
42586
- const report = await describeLoginOutcome(login.data, { remote: remote || void 0, verbose: opts.verbose });
42587
- rail.collapse();
42588
- const identity = terminalText(login.data.email ?? (login.data.userId != null ? `user #${login.data.userId}` : "your account"), 120);
42589
- rail.done(`Signed in as ${s.bold}${identity}${s.reset}`);
42590
- if (gaveUp) rail.line(terminalText(gaveUp, 300), "warn");
42591
- for (const line2 of report.slice(1)) {
42592
- if (line2.level === "verbose") continue;
42593
- rail.line(line2.text.trim().replace(/^✓\s*/, ""), line2.level === "warn" ? "warn" : "muted");
42264
+ await writeSetupState({ declinedStandardVersion: team.version }).catch(() => {
42265
+ });
42266
+ rail.done(`Creating a new Standard ${s.gray}\xB7 it becomes v${team.version + 1} for the team${ctx.standard ? " (--standard new)" : ""}${s.reset}`);
42267
+ replaces = team.version;
42268
+ } else if (team.status === "declined-before") {
42269
+ rail.line(`This project turned down the team's Standard v${team.version} before, so this run builds its own.`);
42270
+ return derived(notAsked);
42594
42271
  }
42595
- return end("new");
42596
- }
42597
-
42598
- // src/commands/init/phases.ts
42599
- async function moveGateInstall(ui) {
42600
- if (!await needsMigration()) return;
42601
- try {
42602
- const result = await runMigration();
42603
- ui.rail.done("Moved a GATE.md install to Verity");
42604
- for (const a of result.actions) ui.rail.line(a);
42605
- } catch (err2) {
42606
- ui.rail.warn(`The GATE.md migration failed: ${terminalText(err2.message, 200)}`);
42272
+ if (team.status === "unavailable") {
42273
+ rail.warn("Could not ask the Verity service for this repository's Standard");
42274
+ rail.line(team.reason);
42275
+ return derived(notAsked);
42607
42276
  }
42608
- ui.rail.settle();
42609
- }
42610
- async function signInSafely(ui, globals) {
42277
+ if (team.status === "skipped") return derived(notAsked);
42278
+ const profile = buildRepoProfile();
42611
42279
  try {
42612
- return await signInStep(ui, await resolveServiceUrlForAuth(globals.serviceUrl), { token: globals.token, verbose: globals.verbose, repo: repoLabel() });
42280
+ writeRepoFile(PROFILE_FILE, JSON.stringify(profile, null, 2) + "\n");
42613
42281
  } catch (err2) {
42614
- ui.rail.collapse();
42615
- ui.rail.warn(`Sign-in did not complete: ${terminalText(err2.message, 200)}`);
42616
- ui.rail.gap();
42617
- ui.rail.settle();
42618
- return "failed";
42282
+ rail.warn(`Could not write ${PROFILE_FILE} for you to read, so nothing was sent`);
42283
+ rail.line(terminalText(err2.message, 200));
42284
+ return derived({ outcome: "failed", detail: "error" });
42619
42285
  }
42620
- }
42621
- function traceTelemetry(trace, telemetry, answer) {
42622
- if (telemetry === "enabled") trace.mark("telemetry", "ok", answer === "already-on" ? "already" : "new");
42623
- else if (telemetry === "declined") trace.mark("telemetry", "skipped", "declined");
42624
- else if (telemetry === "deferred") trace.mark("telemetry", "failed", "none");
42625
- else trace.mark("telemetry", "skipped", "unattended");
42626
- }
42627
- async function recordAnswers(ui, momentIds, telemetry) {
42628
- try {
42629
- await writeSetupState({
42630
- moments: momentIds,
42631
- ...telemetry ? { telemetry } : {},
42632
- init: { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.35.0-experimental.3a13a5e" : "dev" }
42633
- });
42634
- } catch (err2) {
42635
- ui.rail.line(`Could not record the setup answers: ${terminalText(err2.message, 200)}`, "warn");
42286
+ let detail;
42287
+ let send;
42288
+ if (ctx.profile) {
42289
+ send = ctx.profile === "send";
42290
+ detail = "given";
42291
+ } else if (ui.asking) {
42292
+ send = await askToSend(ui, profile);
42293
+ detail = send ? "asked" : "declined";
42294
+ } else {
42295
+ send = false;
42296
+ detail = "unattended";
42297
+ }
42298
+ if (!send) {
42299
+ rail.warn("Profile not sent");
42300
+ rail.line(detail === "unattended" ? "Nobody was asked, so nothing was sent. Your Standard has the built-in rules only." : "Your Standard has the built-in rules only.");
42301
+ rail.line(`Add project rules later: verity standard generate (read ${PROFILE_FILE} first)`);
42302
+ rail.gap();
42303
+ return derived({ outcome: "skipped", detail });
42304
+ }
42305
+ if (detail === "given") rail.done(`Profile sent ${s.gray}\xB7 no source code (--profile send)${s.reset}`);
42306
+ else rail.done(`Profile sent ${s.gray}\xB7 no source code${s.reset}`);
42307
+ rail.gap();
42308
+ rail.settle();
42309
+ const generated = await generate(ui, ctx.globals, profile, replaces);
42310
+ if (generated.status === "created") {
42311
+ return end({ source: "generated", version: generated.version, profile: { outcome: "ok", detail } });
42312
+ }
42313
+ if (generated.status === "exists") {
42314
+ const again = await adoptTeamStandard(ctx);
42315
+ if (again.status === "adopted") {
42316
+ sayAdopted(ui, again);
42317
+ return end({ source: "adopted", version: again.version, profile: { outcome: "ok", detail } });
42318
+ }
42636
42319
  }
42320
+ return derived({ outcome: "ok", detail });
42637
42321
  }
42638
42322
 
42639
42323
  // src/commands/init/index.ts
42640
- function parseInitFlags(opts) {
42324
+ async function runInit(program2, opts) {
42325
+ const started = Date.now();
42326
+ const globals = program2.opts();
42327
+ const defaultsOnly = (opts.yes ?? false) || !interactive();
42641
42328
  let givenMoments;
42642
42329
  if (opts.moments != null) {
42643
42330
  const parsed = parseMomentSelection(String(opts.moments));
42644
42331
  if ("error" in parsed) {
42645
- printError(terminalText(parsed.error, 300));
42332
+ printError(parsed.error);
42646
42333
  process.exit(1);
42647
42334
  }
42648
42335
  givenMoments = parsed.moments;
@@ -42651,7 +42338,7 @@ function parseInitFlags(opts) {
42651
42338
  if (opts.telemetry != null) {
42652
42339
  const parsed = parseTelemetryAnswer(String(opts.telemetry));
42653
42340
  if (parsed === null) {
42654
- printError(`--telemetry: unrecognised "${terminalText(String(opts.telemetry), 60)}". Valid: on, off.`);
42341
+ printError(`--telemetry: unrecognised "${opts.telemetry}". Valid: on, off.`);
42655
42342
  process.exit(1);
42656
42343
  }
42657
42344
  givenTelemetry = parsed;
@@ -42660,7 +42347,7 @@ function parseInitFlags(opts) {
42660
42347
  if (opts.profile != null) {
42661
42348
  const v = String(opts.profile).trim().toLowerCase();
42662
42349
  if (v !== "send" && v !== "skip") {
42663
- printError(`--profile: unrecognised "${terminalText(String(opts.profile), 60)}". Valid: send, skip.`);
42350
+ printError(`--profile: unrecognised "${opts.profile}". Valid: send, skip.`);
42664
42351
  process.exit(1);
42665
42352
  }
42666
42353
  profileAnswer = v;
@@ -42669,24 +42356,16 @@ function parseInitFlags(opts) {
42669
42356
  if (opts.standard != null) {
42670
42357
  const v = String(opts.standard).trim().toLowerCase();
42671
42358
  if (v !== "use" && v !== "new") {
42672
- printError(`--standard: unrecognised "${terminalText(String(opts.standard), 60)}". Valid: use, new.`);
42359
+ printError(`--standard: unrecognised "${opts.standard}". Valid: use, new.`);
42673
42360
  process.exit(1);
42674
42361
  }
42675
42362
  standardAnswer = v;
42676
42363
  }
42677
- return { givenMoments, givenTelemetry, profileAnswer, standardAnswer };
42678
- }
42679
- async function runInit(program2, opts) {
42680
- const started = Date.now();
42681
- const globals = program2.opts();
42682
- const defaultsOnly = (opts.yes ?? false) || !interactive();
42683
- const { givenMoments, givenTelemetry, profileAnswer, standardAnswer } = parseInitFlags(opts);
42684
42364
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
42685
- if (!projectMarkers.some((m) => (0, import_node_fs77.existsSync)(m))) {
42365
+ if (!projectMarkers.some((m) => (0, import_node_fs76.existsSync)(m))) {
42686
42366
  printError('No project detected in the current directory. Run "verity init" from your project root.');
42687
42367
  process.exit(1);
42688
42368
  }
42689
- const before = snapshotInitFiles();
42690
42369
  const stale = clearStalePluginMarker();
42691
42370
  const pluginMode = opts.plugin === false ? false : opts.pluginMode ?? pluginActiveHere();
42692
42371
  const install = pluginMode ? activePluginInstall() : null;
@@ -42699,7 +42378,16 @@ async function runInit(program2, opts) {
42699
42378
  await wordmark(ui.screen, { reveal: firstInitOnMachine() });
42700
42379
  rail.open(repoLabel());
42701
42380
  if (opts.intensity != null) rail.line("--intensity is ignored: static analysis is Trivy only.", "warn");
42702
- await moveGateInstall(ui);
42381
+ if (await needsMigration()) {
42382
+ try {
42383
+ const result = await runMigration();
42384
+ rail.done("Moved a GATE.md install to Verity");
42385
+ for (const a of result.actions) rail.line(a);
42386
+ } catch (err2) {
42387
+ rail.warn(`The GATE.md migration failed: ${terminalText(err2.message, 200)}`);
42388
+ }
42389
+ rail.settle();
42390
+ }
42703
42391
  if (!await prerequisitesStep(ui)) {
42704
42392
  rail.end("Stopped: a required tool is missing.", "Install it, then run verity init again.");
42705
42393
  trace.mark("prerequisites", "failed", "blocked");
@@ -42710,10 +42398,10 @@ async function runInit(program2, opts) {
42710
42398
  trace.mark("prerequisites", "ok");
42711
42399
  await scaffoldStep(ui, { untrackMemory: opts.untrackMemory === true, untrackState: opts.untrackState === true });
42712
42400
  trace.mark("scaffold", "ok");
42713
- await (0, import_promises19.mkdir)((0, import_node_path65.join)(process.env.HOME ?? "", ".verity"), { recursive: true });
42401
+ await (0, import_promises19.mkdir)((0, import_node_path64.join)(process.env.HOME ?? "", ".verity"), { recursive: true });
42714
42402
  await repoStep(ui, detectProject());
42715
42403
  const previous = await readSetupState();
42716
- const setUpBefore = (0, import_node_fs77.existsSync)(projectPath(STANDARD_FILE));
42404
+ const setUpBefore = (0, import_node_fs76.existsSync)(projectPath(STANDARD_FILE));
42717
42405
  const answers = await askSetupQuestions(defaultsOnly, previous, { moments: givenMoments, telemetry: givenTelemetry });
42718
42406
  trace.mark("questions", "ok", givenMoments || givenTelemetry !== void 0 || profileAnswer || standardAnswer ? "asked" : "defaults");
42719
42407
  const plan = planMoments(answers.moments, { given: givenMoments !== void 0, defaultsOnly });
@@ -42729,40 +42417,66 @@ async function runInit(program2, opts) {
42729
42417
  force: opts.force ?? false
42730
42418
  });
42731
42419
  trace.mark("hooks", "ok", pluginMode ? "plugin" : "project");
42732
- const signIn = await signInSafely(ui, globals);
42420
+ let signIn;
42421
+ try {
42422
+ signIn = await signInStep(ui, await resolveServiceUrlForAuth(globals.serviceUrl), { token: globals.token, verbose: globals.verbose, repo: repoLabel() });
42423
+ } catch (err2) {
42424
+ rail.collapse();
42425
+ rail.warn(`Sign-in did not complete: ${terminalText(err2.message, 200)}`);
42426
+ rail.gap();
42427
+ rail.settle();
42428
+ signIn = "failed";
42429
+ }
42733
42430
  trace.mark("sign_in", ...signInDetail(signIn));
42734
42431
  const signedIn = signIn === "already" || signIn === "new" || signIn === "unreachable";
42735
- const telemetry = await telemetryStep(ui, answers.telemetry, answers.telemetry === "not-asked" && previous?.telemetry === "deferred", globals, previous?.telemetry);
42736
- traceTelemetry(trace, telemetry, answers.telemetry);
42432
+ const telemetry = await telemetryStep(ui, answers.telemetry, answers.telemetry === "not-asked" && previous?.telemetry === "deferred", globals);
42433
+ if (telemetry === "enabled") trace.mark("telemetry", "ok", answers.telemetry === "already-on" ? "already" : "new");
42434
+ else if (telemetry === "declined") trace.mark("telemetry", "skipped", "declined");
42435
+ else if (telemetry === "deferred") trace.mark("telemetry", "failed", "none");
42436
+ else trace.mark("telemetry", "skipped", "unattended");
42737
42437
  const standard = await standardStep(ui, { globals, previous, adopt: opts.adopt !== false, profile: profileAnswer, standard: standardAnswer });
42738
42438
  trace.mark("profile", standard.profile.outcome, standard.profile.detail);
42739
42439
  const haveStandard = standard.source !== "none";
42740
42440
  trace.mark("standard", haveStandard ? "ok" : "failed", standard.source);
42741
42441
  const formalNew = formalDefault(previous, { setUpBefore });
42742
42442
  backgroundStep(ui, { cli: process.argv[1], formalNew });
42743
- await recordAnswers(ui, plan.momentIds, telemetry);
42443
+ try {
42444
+ await writeSetupState({
42445
+ // The effective answer: the next run starts from this record.
42446
+ moments: plan.momentIds,
42447
+ ...telemetry ? { telemetry } : {},
42448
+ init: { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.35.0-experimental.7c5e75b" : "dev" }
42449
+ });
42450
+ } catch (err2) {
42451
+ rail.line(`Could not record the setup answers: ${terminalText(err2.message, 200)}`, "warn");
42452
+ }
42744
42453
  trace.mark("completed", haveStandard ? "ok" : "failed", haveStandard ? void 0 : "none");
42745
42454
  await trace.settle();
42746
- await finishRun(ui, {
42747
- started,
42748
- before,
42749
- haveStandard,
42750
- pluginMode,
42751
- noPlugin: opts.plugin === false,
42752
- signedIn,
42753
- momentIds: plan.momentIds
42754
- });
42455
+ const secs2 = Math.max(1, Math.round((Date.now() - started) / 1e3));
42456
+ const s = ui.screen.s;
42457
+ if (!haveStandard) {
42458
+ rail.end(`${s.yellow}Not finished:${s.reset} this project has no Standard, so the gate cannot review.`, "Run verity init again, or /verity-setup in Claude Code.");
42459
+ } else {
42460
+ if (pluginMode) {
42461
+ const version = activePluginVersion();
42462
+ if (version) await writeSetupState({ pluginSetup: { version, completed_at: (/* @__PURE__ */ new Date()).toISOString() }, wiring: void 0 });
42463
+ } else if (opts.plugin === false) {
42464
+ await writeSetupState({ wiring: "project" });
42465
+ }
42466
+ const next = plan.momentIds.length === 0 ? "Verity reviews nothing until you turn a moment on: verity config moments turn,commit,push" : pluginMode ? signedIn ? "Verity reviews from your next turn." : "The security scan runs from your next turn. Sign in for AI review: verity login" : signedIn ? "Start a new Claude Code session. Verity reviews from its first turn." : "Start a new Claude Code session. The security scan runs from the first turn.";
42467
+ rail.end(`${s.green}${s.bold}Ready${s.reset} ${s.gray}in ${secs2}s.${s.reset}`, next);
42468
+ }
42755
42469
  markInitSeen();
42756
42470
  } finally {
42757
42471
  ui.screen.release();
42758
42472
  }
42759
42473
  }
42760
42474
  function registerInitCommand(program2) {
42761
- program2.command("init").alias("setup").description("Set up Verity in this project: sign in, telemetry, and the Standard; every other choice is a stated default").option("--force", "Reinstall the skills even when they are up to date (hooks are always reconciled)").option("-y, --yes", "Ask nothing: skip sign-in unless already signed in, and send the profile only with --profile send").option("--profile <send|skip>", "Answer the repo-profile question without asking (the plugin asks in the session)").option("--standard <use|new>", "When the team already has a Standard: use it, or create a new one (the plugin asks in the session; unattended runs use it)").option("--no-setup", "Ignored: init no longer hands off to /verity-setup (kept for old scripts)").option("--plugin-mode", "The Claude Code plugin owns the skills and hooks: install neither, and remove any this project already has").option("--no-plugin", "Ignore any Claude Code plugin here and wire this project's own skills and hooks").option("--no-adopt", "Don't use this repository's Standard from the service; build a new one").option("--moments <list>", 'Set when Verity reviews without the default: stop,pre-commit,pre-push \u2014 or "none"').option("--intensity <level>", "Ignored: static analysis is Trivy only (kept so older plugin skills keep working)").option("--telemetry <on|off>", "Answer the telemetry question without asking: send cost & usage from this machine, or not").option("--untrack-memory", "Stop tracking the knowledge graph in git (init does not ask)").option("--untrack-state", "Untrack committed Verity state without asking (a terminal run asks when it finds some)").action((opts) => runInit(program2, opts));
42475
+ program2.command("init").alias("setup").description("Set up Verity in this project: two questions (sign in, and send a repo profile); every other choice is a stated default").option("--force", "Reinstall the skills even when they are up to date (hooks are always reconciled)").option("-y, --yes", "Ask nothing: skip sign-in unless already signed in, and send the profile only with --profile send").option("--profile <send|skip>", "Answer the repo-profile question without asking (the plugin asks in the session)").option("--standard <use|new>", "When the team already has a Standard: use it, or create a new one (the plugin asks in the session; unattended runs use it)").option("--no-setup", "Ignored: init no longer hands off to /verity-setup (kept for old scripts)").option("--plugin-mode", "The Claude Code plugin owns the skills and hooks: install neither, and remove any this project already has").option("--no-plugin", "Ignore any Claude Code plugin here and wire this project's own skills and hooks").option("--no-adopt", "Don't use this repository's Standard from the service; build a new one").option("--moments <list>", 'Set when Verity reviews without the default: stop,pre-commit,pre-push \u2014 or "none"').option("--intensity <level>", "Ignored: static analysis is Trivy only (kept so older plugin skills keep working)").option("--telemetry <on|off>", "Turn cost & usage telemetry on or off (init does not ask)").option("--untrack-memory", "Stop tracking the knowledge graph in git (init does not ask)").option("--untrack-state", "Untrack committed Verity state (init does not ask)").action((opts) => runInit(program2, opts));
42762
42476
  }
42763
42477
 
42764
42478
  // src/commands/doctor.ts
42765
- var import_node_fs78 = require("node:fs");
42479
+ var import_node_fs77 = require("node:fs");
42766
42480
  async function buildReport() {
42767
42481
  const prereqs = await checkPrereqs({ install: false });
42768
42482
  const state = await readSetupState();
@@ -42771,8 +42485,8 @@ async function buildReport() {
42771
42485
  const provenance = cliProvenance();
42772
42486
  const telemetry = await checkTelemetry();
42773
42487
  const artifacts = {
42774
- standard: (0, import_node_fs78.existsSync)(projectPath(STANDARD_FILE)),
42775
- verityMd: (0, import_node_fs78.existsSync)(projectPath(VERITY_MD_FILE))
42488
+ standard: (0, import_node_fs77.existsSync)(projectPath(STANDARD_FILE)),
42489
+ verityMd: (0, import_node_fs77.existsSync)(projectPath(VERITY_MD_FILE))
42776
42490
  };
42777
42491
  const next = [];
42778
42492
  for (const c of prereqs.checks) {
@@ -42931,8 +42645,8 @@ function registerDoctorCommand(program2) {
42931
42645
  }
42932
42646
 
42933
42647
  // src/commands/uninstall.ts
42934
- var import_node_fs79 = require("node:fs");
42935
- var import_node_path66 = require("node:path");
42648
+ var import_node_fs78 = require("node:fs");
42649
+ var import_node_path65 = require("node:path");
42936
42650
  function registerUninstallCommand(program2) {
42937
42651
  program2.command("uninstall").description("Remove Verity from this project (skills, hooks, .verity/, VERITY.md)").option("--dry-run", "Show what would be removed without doing it").option("--purge-global", "Also remove ~/.verity/ (deletes saved tokens \u2014 reconnect requires re-registration)").option("--keep-verity-md", "Keep the project root VERITY.md file").action(async (opts) => {
42938
42652
  const dryRun = opts.dryRun ?? false;
@@ -42941,11 +42655,11 @@ function registerUninstallCommand(program2) {
42941
42655
  const actions = [];
42942
42656
  const skillsRoot = projectPath(".claude/skills");
42943
42657
  for (const name2 of PROJECT_SKILL_NAMES) {
42944
- const dir = (0, import_node_path66.join)(skillsRoot, name2);
42945
- if ((0, import_node_fs79.existsSync)(dir)) {
42658
+ const dir = (0, import_node_path65.join)(skillsRoot, name2);
42659
+ if ((0, import_node_fs78.existsSync)(dir)) {
42946
42660
  actions.push({
42947
42661
  label: `Remove .claude/skills/${name2}/`,
42948
- apply: () => (0, import_node_fs79.rmSync)(dir, { recursive: true, force: true })
42662
+ apply: () => (0, import_node_fs78.rmSync)(dir, { recursive: true, force: true })
42949
42663
  });
42950
42664
  }
42951
42665
  }
@@ -42959,24 +42673,24 @@ function registerUninstallCommand(program2) {
42959
42673
  });
42960
42674
  }
42961
42675
  const verityDir = projectPath(VERITY_DIR);
42962
- if ((0, import_node_fs79.existsSync)(verityDir)) {
42676
+ if ((0, import_node_fs78.existsSync)(verityDir)) {
42963
42677
  actions.push({
42964
42678
  label: `Remove ${VERITY_DIR}/`,
42965
- apply: () => (0, import_node_fs79.rmSync)(verityDir, { recursive: true, force: true })
42679
+ apply: () => (0, import_node_fs78.rmSync)(verityDir, { recursive: true, force: true })
42966
42680
  });
42967
42681
  }
42968
42682
  if (!keepVerityMd) {
42969
42683
  const verityMd = projectPath(VERITY_MD_FILE);
42970
- if ((0, import_node_fs79.existsSync)(verityMd)) {
42684
+ if ((0, import_node_fs78.existsSync)(verityMd)) {
42971
42685
  actions.push({
42972
42686
  label: `Remove ${VERITY_MD_FILE}`,
42973
- apply: () => (0, import_node_fs79.rmSync)(verityMd, { force: true })
42687
+ apply: () => (0, import_node_fs78.rmSync)(verityMd, { force: true })
42974
42688
  });
42975
42689
  }
42976
42690
  }
42977
42691
  const cleanupEmptyDir = (path) => {
42978
- if ((0, import_node_fs79.existsSync)(path) && (0, import_node_fs79.statSync)(path).isDirectory() && (0, import_node_fs79.readdirSync)(path).length === 0) {
42979
- (0, import_node_fs79.rmdirSync)(path);
42692
+ if ((0, import_node_fs78.existsSync)(path) && (0, import_node_fs78.statSync)(path).isDirectory() && (0, import_node_fs78.readdirSync)(path).length === 0) {
42693
+ (0, import_node_fs78.rmdirSync)(path);
42980
42694
  }
42981
42695
  };
42982
42696
  actions.push({
@@ -42987,11 +42701,11 @@ function registerUninstallCommand(program2) {
42987
42701
  }
42988
42702
  });
42989
42703
  const home = process.env.HOME ?? "";
42990
- const globalVerityDir = (0, import_node_path66.join)(home, ".verity");
42991
- if (purgeGlobal && (0, import_node_fs79.existsSync)(globalVerityDir)) {
42704
+ const globalVerityDir = (0, import_node_path65.join)(home, ".verity");
42705
+ if (purgeGlobal && (0, import_node_fs78.existsSync)(globalVerityDir)) {
42992
42706
  actions.push({
42993
42707
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
42994
- apply: () => (0, import_node_fs79.rmSync)(globalVerityDir, { recursive: true, force: true })
42708
+ apply: () => (0, import_node_fs78.rmSync)(globalVerityDir, { recursive: true, force: true })
42995
42709
  });
42996
42710
  }
42997
42711
  if (actions.length === 0) {
@@ -43210,8 +42924,8 @@ function registerTaskCommands(program2) {
43210
42924
  }
43211
42925
 
43212
42926
  // src/commands/reset.ts
43213
- var import_node_fs80 = require("node:fs");
43214
- var import_node_path67 = require("node:path");
42927
+ var import_node_fs79 = require("node:fs");
42928
+ var import_node_path66 = require("node:path");
43215
42929
  function registerResetCommand(program2) {
43216
42930
  program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.verity/.logs/)").action(async (opts) => {
43217
42931
  const globals = program2.opts();
@@ -43251,18 +42965,18 @@ function registerResetCommand(program2) {
43251
42965
  } catch {
43252
42966
  }
43253
42967
  }
43254
- const purged = purgeVerityDir((0, import_node_path67.basename)(CACHE_DIR), (name2) => name2.startsWith("pending-"));
42968
+ const purged = purgeVerityDir((0, import_node_path66.basename)(CACHE_DIR), (name2) => name2.startsWith("pending-"));
43255
42969
  if (purged === null) printError(`Did not purge ${CACHE_DIR}: it is not a directory inside ${VERITY_DIR}.`);
43256
42970
  const stateDir2 = projectPath(VERITY_DIR);
43257
42971
  const filesToClear = [
43258
- (0, import_node_path67.basename)(INTENT_FILE),
43259
- (0, import_node_path67.basename)(HASH_FILE),
43260
- (0, import_node_path67.basename)(BASELINE_SHA_FILE),
42972
+ (0, import_node_path66.basename)(INTENT_FILE),
42973
+ (0, import_node_path66.basename)(HASH_FILE),
42974
+ (0, import_node_path66.basename)(BASELINE_SHA_FILE),
43261
42975
  ".iteration-count",
43262
42976
  ".last-analysis"
43263
42977
  ];
43264
42978
  for (const name2 of filesToClear) {
43265
- if (lstatOrNull((0, import_node_path67.join)(stateDir2, name2)) && !replaceInRoot(stateDir2, name2, "")) {
42979
+ if (lstatOrNull((0, import_node_path66.join)(stateDir2, name2)) && !replaceInRoot(stateDir2, name2, "")) {
43266
42980
  printError(`Did not clear ${VERITY_DIR}/${name2}: it is not a regular file inside ${VERITY_DIR}.`);
43267
42981
  }
43268
42982
  }
@@ -43277,29 +42991,29 @@ function registerResetCommand(program2) {
43277
42991
  }
43278
42992
  function lstatOrNull(path) {
43279
42993
  try {
43280
- return (0, import_node_fs80.lstatSync)(path);
42994
+ return (0, import_node_fs79.lstatSync)(path);
43281
42995
  } catch {
43282
42996
  return null;
43283
42997
  }
43284
42998
  }
43285
42999
  function purgeVerityDir(child, select) {
43286
43000
  const verityDir = projectPath(VERITY_DIR);
43287
- const dir = (0, import_node_path67.join)(verityDir, child);
43001
+ const dir = (0, import_node_path66.join)(verityDir, child);
43288
43002
  const parent = lstatOrNull(verityDir);
43289
43003
  const st = lstatOrNull(dir);
43290
43004
  if (!st) return 0;
43291
43005
  if (!parent || parent.isSymbolicLink() || !parent.isDirectory()) return null;
43292
43006
  if (st.isSymbolicLink() || !st.isDirectory()) return null;
43293
43007
  try {
43294
- if ((0, import_node_fs80.realpathSync)(dir) !== (0, import_node_path67.join)((0, import_node_fs80.realpathSync)(verityDir), child)) return null;
43008
+ if ((0, import_node_fs79.realpathSync)(dir) !== (0, import_node_path66.join)((0, import_node_fs79.realpathSync)(verityDir), child)) return null;
43295
43009
  } catch {
43296
43010
  return null;
43297
43011
  }
43298
43012
  let removed = 0;
43299
- for (const entry of (0, import_node_fs80.readdirSync)(dir, { withFileTypes: true })) {
43013
+ for (const entry of (0, import_node_fs79.readdirSync)(dir, { withFileTypes: true })) {
43300
43014
  if (entry.isDirectory() || !select(entry.name)) continue;
43301
43015
  try {
43302
- (0, import_node_fs80.unlinkSync)((0, import_node_path67.join)(dir, entry.name));
43016
+ (0, import_node_fs79.unlinkSync)((0, import_node_path66.join)(dir, entry.name));
43303
43017
  removed++;
43304
43018
  } catch {
43305
43019
  }
@@ -43308,7 +43022,7 @@ function purgeVerityDir(child, select) {
43308
43022
  }
43309
43023
 
43310
43024
  // src/commands/reflect.ts
43311
- var import_node_fs81 = require("node:fs");
43025
+ var import_node_fs80 = require("node:fs");
43312
43026
 
43313
43027
  // src/lib/reflection-globs.ts
43314
43028
  var MAX_GLOBS = 6;
@@ -43346,7 +43060,7 @@ async function writeNodeToDisk(args2) {
43346
43060
  { treePaths: listTrackedFiles(), recordBaseline: false }
43347
43061
  );
43348
43062
  if (written === 0) return false;
43349
- return (0, import_node_fs81.existsSync)(projectPath(`${VERITY_DIR}/memory/${args2.filePath}`));
43063
+ return (0, import_node_fs80.existsSync)(projectPath(`${VERITY_DIR}/memory/${args2.filePath}`));
43350
43064
  } catch {
43351
43065
  return false;
43352
43066
  }
@@ -43783,7 +43497,7 @@ function registerRunCommand(program2) {
43783
43497
  }
43784
43498
 
43785
43499
  // src/commands/telemetry.ts
43786
- var import_node_fs82 = require("node:fs");
43500
+ var import_node_fs81 = require("node:fs");
43787
43501
  var SCOPE = "every Claude Code session on this machine";
43788
43502
  function declineHeaders(why) {
43789
43503
  process.stderr.write(`Verity telemetry is off here: ${why} Nothing is exported from this session.
@@ -43791,7 +43505,7 @@ function declineHeaders(why) {
43791
43505
  process.exitCode = 1;
43792
43506
  }
43793
43507
  async function recordTelemetryChoice(choice) {
43794
- if (!(0, import_node_fs82.existsSync)(projectPath(SETUP_STATE_FILE))) return;
43508
+ if (!(0, import_node_fs81.existsSync)(projectPath(SETUP_STATE_FILE))) return;
43795
43509
  try {
43796
43510
  if ((await readSetupState())?.telemetry === choice) return;
43797
43511
  await writeSetupState({ telemetry: choice });
@@ -44041,8 +43755,8 @@ function registerUsageCommand(program2) {
44041
43755
  }
44042
43756
 
44043
43757
  // src/commands/prove.ts
44044
- var import_node_fs83 = require("node:fs");
44045
- var import_node_path68 = require("node:path");
43758
+ var import_node_fs82 = require("node:fs");
43759
+ var import_node_path67 = require("node:path");
44046
43760
 
44047
43761
  // src/lib/prove/hint-pool.ts
44048
43762
  var STOP2 = /* @__PURE__ */ new Set([
@@ -44184,7 +43898,7 @@ function registerProveCommand(program2) {
44184
43898
  process.exit(2);
44185
43899
  }
44186
43900
  const limit = Math.max(1, Math.min(50, Number(opts.limit) || 10));
44187
- let signals = collectRiskSignals(root, { since: opts.since, limit, paths: paths.map((p) => (0, import_node_path68.relative)(root, (0, import_node_path68.resolve)(p)) || ".") });
43901
+ let signals = collectRiskSignals(root, { since: opts.since, limit, paths: paths.map((p) => (0, import_node_path67.relative)(root, (0, import_node_path67.resolve)(p)) || ".") });
44188
43902
  if (opts.hint && paths.length === 0) {
44189
43903
  const hit = hintMatches(root, opts.hint);
44190
43904
  if (hit.files.length > 0) {
@@ -44238,7 +43952,7 @@ function registerProveCommand(program2) {
44238
43952
  });
44239
43953
  prove.command("check [model]").description("Elaborate a model (or every model) with the local Lean toolchain and report each property").option("--timeout <ms>", "Per-model timeout in ms", "120000").option("--json", "Machine-readable output").action((model, opts) => {
44240
43954
  const root = repoRoot();
44241
- if (!(0, import_node_fs83.existsSync)((0, import_node_path68.join)(root, PROOFS_DIR, "lakefile.toml"))) {
43955
+ if (!(0, import_node_fs82.existsSync)((0, import_node_path67.join)(root, PROOFS_DIR, "lakefile.toml"))) {
44242
43956
  printError(`No ${PROOFS_DIR}/ project here. Run \`verity prove init\` first.`);
44243
43957
  process.exit(2);
44244
43958
  }
@@ -44262,7 +43976,7 @@ ${build.stderr.split("\n").filter((l) => !l.startsWith("trace:")).slice(0, 20).j
44262
43976
  const reports = [];
44263
43977
  for (const f of files) {
44264
43978
  const report = checkModel(tc, root, f, { timeoutMs: Math.max(1e3, Number(opts.timeout) || 12e4) });
44265
- report.file = (0, import_node_path68.relative)(root, f);
43979
+ report.file = (0, import_node_path67.relative)(root, f);
44266
43980
  reports.push(report);
44267
43981
  try {
44268
43982
  writeResult(root, report);
@@ -44300,7 +44014,7 @@ ${build.stderr.split("\n").filter((l) => !l.startsWith("trace:")).slice(0, 20).j
44300
44014
  printError("Not logged in \u2014 replays run on the Verity service. Run `verity login` first.");
44301
44015
  process.exit(2);
44302
44016
  }
44303
- if (!(0, import_node_fs83.existsSync)((0, import_node_path68.join)(root, PROOFS_DIR, "lakefile.toml"))) {
44017
+ if (!(0, import_node_fs82.existsSync)((0, import_node_path67.join)(root, PROOFS_DIR, "lakefile.toml"))) {
44304
44018
  printError(`No ${PROOFS_DIR}/ project here. Run \`verity prove init\` first.`);
44305
44019
  process.exit(2);
44306
44020
  }
@@ -44331,7 +44045,7 @@ ${report.error ?? ""}`);
44331
44045
  printInfo(opts.property ? `${model} ${opts.property} has no counterexample to replay` : `${model} has no counterexample to replay`);
44332
44046
  return;
44333
44047
  }
44334
- const modelSource = readRegularFileInRoot(root, (0, import_node_path68.relative)(root, file), 512 * 1024);
44048
+ const modelSource = readRegularFileInRoot(root, (0, import_node_path67.relative)(root, file), 512 * 1024);
44335
44049
  if (!opts.json) printInfo(`replaying ${targets.length} counterexample(s) of ${model} in network-less containers (${Math.min(targets.length, REPLAY_CONCURRENCY)} at a time)\u2026`);
44336
44050
  const outcomes = await mapPool(targets, REPLAY_CONCURRENCY, async (p) => {
44337
44051
  const brief = buildReplayBrief(root, report, p, modelSource.ok ? modelSource.content : "");
@@ -44351,7 +44065,7 @@ ${report.error ?? ""}`);
44351
44065
  });
44352
44066
  prove.command("auto [files...]").description("The formal phase the pre-push guard runs: select one risky symbol among changed files, generate a model, check it, replay counterexamples (default files: the commits ahead of upstream)").option("--deadline <ms>", "Budget for the whole phase", "170000").option("--json", "Machine-readable output").action(async (paths, opts) => {
44353
44067
  const root = repoRoot();
44354
- let files = paths.map((p) => (0, import_node_path68.relative)(root, (0, import_node_path68.resolve)(p)));
44068
+ let files = paths.map((p) => (0, import_node_path67.relative)(root, (0, import_node_path67.resolve)(p)));
44355
44069
  if (files.length === 0) {
44356
44070
  const ahead = runGitSync(root, ["diff", "--name-only", "@{upstream}...HEAD"]);
44357
44071
  const fallback = ahead.ok ? ahead : runGitSync(root, ["diff", "--name-only", "HEAD~1..HEAD"]);
@@ -44425,7 +44139,7 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
44425
44139
  let revise;
44426
44140
  if (opts.revise) {
44427
44141
  const existing = resolveModelFile(root, opts.revise);
44428
- const src = existing ? readRegularFileInRoot(root, (0, import_node_path68.relative)(root, existing), 64 * 1024) : null;
44142
+ const src = existing ? readRegularFileInRoot(root, (0, import_node_path67.relative)(root, existing), 64 * 1024) : null;
44429
44143
  if (!src || !src.ok || !opts.note) {
44430
44144
  printError("--revise needs an existing model under Models/ and --note - with what the replay showed on stdin");
44431
44145
  process.exit(2);
@@ -44465,8 +44179,8 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
44465
44179
  });
44466
44180
  prove.command("waive <file>").description("Dismiss the remembered formal finding(s) on a file for its current bytes \u2014 the accepted-risk path for a formal block").requiredOption("--reason <text>", "The human disposition this records (reviewer finding, ADR, \u2026)").action((file, opts) => {
44467
44181
  const root = repoRoot();
44468
- const rel0 = (0, import_node_path68.relative)(root, (0, import_node_path68.isAbsolute)(file) ? file : (0, import_node_path68.join)(root, file)).replace(/\\/g, "/");
44469
- const rel = rel0.startsWith("..") || (0, import_node_path68.isAbsolute)(rel0) ? null : rel0;
44182
+ const rel0 = (0, import_node_path67.relative)(root, (0, import_node_path67.isAbsolute)(file) ? file : (0, import_node_path67.join)(root, file)).replace(/\\/g, "/");
44183
+ const rel = rel0.startsWith("..") || (0, import_node_path67.isAbsolute)(rel0) ? null : rel0;
44470
44184
  if (!rel) {
44471
44185
  printError(`${file} is not inside this repository`);
44472
44186
  process.exit(2);
@@ -44482,8 +44196,8 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
44482
44196
  });
44483
44197
  prove.command("hash <file>").description("Print the sha256 of a file, for a model header (Verity.model \u2026 sha256). Path is REPO-relative, as in the header").action((file) => {
44484
44198
  const root = repoRoot();
44485
- const rel = (0, import_node_path68.relative)(root, (0, import_node_path68.isAbsolute)(file) ? file : (0, import_node_path68.join)(root, file));
44486
- const digest = rel.startsWith("..") || (0, import_node_path68.isAbsolute)(rel) ? null : sha256InRoot(root, rel);
44199
+ const rel = (0, import_node_path67.relative)(root, (0, import_node_path67.isAbsolute)(file) ? file : (0, import_node_path67.join)(root, file));
44200
+ const digest = rel.startsWith("..") || (0, import_node_path67.isAbsolute)(rel) ? null : sha256InRoot(root, rel);
44487
44201
  if (!digest) {
44488
44202
  printError(`Cannot hash ${file}: it must be a regular file inside this repository (not a symlink)`);
44489
44203
  process.exit(2);
@@ -44517,10 +44231,10 @@ function refreshScaffold(root) {
44517
44231
  }
44518
44232
 
44519
44233
  // src/commands/lean.ts
44520
- var import_node_fs84 = require("node:fs");
44521
- var import_node_child_process24 = require("node:child_process");
44522
- var import_node_os15 = require("node:os");
44523
- var import_node_path69 = require("node:path");
44234
+ var import_node_fs83 = require("node:fs");
44235
+ var import_node_child_process23 = require("node:child_process");
44236
+ var import_node_os14 = require("node:os");
44237
+ var import_node_path68 = require("node:path");
44524
44238
  function registerLeanCommand(program2) {
44525
44239
  const lean = program2.command("lean").description("The Lean 4 toolchain formal verification needs (managed download, pinned by this CLI)");
44526
44240
  lean.command("status").option("--json", "Machine-readable output").description("Which toolchain would run, and whether it matches the pinned version").action((opts) => {
@@ -44528,7 +44242,7 @@ function registerLeanCommand(program2) {
44528
44242
  let version = null;
44529
44243
  if (tc) {
44530
44244
  try {
44531
- version = (0, import_node_child_process24.execFileSync)(tc.lake.replace(/lake(\.exe)?$/, "lean$1"), ["--version"], { env: tc.env, encoding: "utf-8", timeout: 1e4 }).trim();
44245
+ version = (0, import_node_child_process23.execFileSync)(tc.lake.replace(/lake(\.exe)?$/, "lean$1"), ["--version"], { env: tc.env, encoding: "utf-8", timeout: 1e4 }).trim();
44532
44246
  } catch {
44533
44247
  version = null;
44534
44248
  }
@@ -44539,7 +44253,7 @@ function registerLeanCommand(program2) {
44539
44253
  const bg = readInstallState();
44540
44254
  const background = bg ? { ...bg, running: bg.state === "running" && installRunning() } : null;
44541
44255
  if (opts.json) {
44542
- printJson({ pinned: LEAN_TOOLCHAIN, found: tc?.lake ?? null, version, matches_pin: matches, managed_dir: managed, managed_installed: (0, import_node_fs84.existsSync)((0, import_node_path69.join)(managed, "bin")), asset: leanAssetName(), background });
44256
+ printJson({ pinned: LEAN_TOOLCHAIN, found: tc?.lake ?? null, version, matches_pin: matches, managed_dir: managed, managed_installed: (0, import_node_fs83.existsSync)((0, import_node_path68.join)(managed, "bin")), asset: leanAssetName(), background });
44543
44257
  return;
44544
44258
  }
44545
44259
  printInfo(`pinned: ${LEAN_TOOLCHAIN}`);
@@ -44556,11 +44270,11 @@ function registerLeanCommand(program2) {
44556
44270
  if (opts.background) process.exit(await runBackgroundInstall());
44557
44271
  const asset = leanAssetName();
44558
44272
  if (!asset) {
44559
- printError(`no Lean release for ${(0, import_node_os15.platform)()}/${(0, import_node_os15.arch)()}`);
44273
+ printError(`no Lean release for ${(0, import_node_os14.platform)()}/${(0, import_node_os14.arch)()}`);
44560
44274
  process.exit(2);
44561
44275
  }
44562
44276
  const dir = pinnedVersionDir();
44563
- if ((0, import_node_fs84.existsSync)((0, import_node_path69.join)(dir, "bin", "lake")) && !opts.force) {
44277
+ if ((0, import_node_fs83.existsSync)((0, import_node_path68.join)(dir, "bin", "lake")) && !opts.force) {
44564
44278
  printInfo(`already installed at ${dir}`);
44565
44279
  return;
44566
44280
  }
@@ -44596,20 +44310,20 @@ function registerLeanCommand(program2) {
44596
44310
  });
44597
44311
  lean.command("remove").description("Delete the Verity-managed toolchain for the pinned version").action(() => {
44598
44312
  const dir = pinnedVersionDir();
44599
- if (!(0, import_node_fs84.existsSync)(dir)) {
44313
+ if (!(0, import_node_fs83.existsSync)(dir)) {
44600
44314
  printInfo("nothing to remove");
44601
44315
  return;
44602
44316
  }
44603
- (0, import_node_fs84.rmSync)(dir, { recursive: true, force: true });
44317
+ (0, import_node_fs83.rmSync)(dir, { recursive: true, force: true });
44604
44318
  printInfo(`removed ${dir}`);
44605
44319
  });
44606
44320
  }
44607
44321
 
44608
44322
  // src/cli.ts
44609
- program.name("verity").description("CLI for Verity quality gate service").version("0.35.0-experimental.3a13a5e").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
44323
+ program.name("verity").description("CLI for Verity quality gate service").version("0.35.0-experimental.7c5e75b").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
44610
44324
  const isTelemetryHelper = actionCommand.name() === "headers" && actionCommand.parent?.name() === "telemetry";
44611
44325
  if (!isTelemetryHelper) {
44612
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.35.0-experimental.3a13a5e");
44326
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.35.0-experimental.7c5e75b");
44613
44327
  }
44614
44328
  setUserNamedServiceUrl(program.opts().serviceUrl);
44615
44329
  try {