@codacy/verity-cli 0.32.3-experimental.e9605e1 → 0.32.4-experimental.e769665

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -64,6 +64,73 @@ installs are untouched.
64
64
  which is the ordinary case for someone whose first Verity install *is* the
65
65
  plugin.
66
66
 
67
+ ## [0.32.4] — 2026-09-11
68
+
69
+ **Setup asks you how you want Verity to work.** Until now, installing the Claude
70
+ Code plugin and running setup silently chose **stop-only** review for you — no
71
+ commit gate, no push gate — because the CLI asks its questions on a terminal and
72
+ a skill has none. You were never asked, and nothing said so.
73
+
74
+ ### 🎯 `/verity:init` — the one command after installing
75
+
76
+ ```
77
+ /plugin install verity@verity
78
+ /verity:init
79
+ ```
80
+
81
+ It asks two questions in a single prompt — **when** Verity should review (after
82
+ every turn, before commit, before push) and **how deeply** — then does the whole
83
+ install with your answers. It says plainly that choosing stop-only means your
84
+ commits and pushes are not gated.
85
+
86
+ It also repairs what an install cannot: the plugin has no post-install hook, so
87
+ only a command you run can remove a project's superseded `.claude/skills/verity-*`
88
+ copies, stand down duplicate hooks, and derive the Standard.
89
+
90
+ **`verity init` takes the answers directly too**, for a terminal or CI:
91
+
92
+ ```bash
93
+ verity init --moments stop,pre-commit --intensity balanced
94
+ ```
95
+
96
+ An unrecognised moment is refused rather than dropped — a typo cannot quietly
97
+ remove a gate.
98
+
99
+ **Signing in stays yours.** `verity login` is a device flow that waits on your
100
+ browser, so the skill asks you to run it in your own terminal instead of hanging
101
+ on a tool call.
102
+
103
+ ### 📋 `/verity:setup` keeps the job only a model can do
104
+
105
+ Project-specific rules — *"every route under `api/` uses the auth middleware"* —
106
+ which the CLI deliberately leaves empty. It no longer runs the install, because a
107
+ skill that quietly answers the setup questions is the thing this release fixes.
108
+
109
+ ### 🔍 Small projects get static analysis again
110
+
111
+ A service whose whole source was one file derived **no languages at all**, so no
112
+ `.codacy/` config was written and static analysis ran nothing — reported as "No
113
+ static-analysis tool matched this codebase", as though it were a fact about the
114
+ project. The file-count floor exists to stop one stray `.py` turning on Ruff for
115
+ a TypeScript repo; it now applies to a *minority* language rather than to whether
116
+ the project has one.
117
+
118
+ ### 👀 Verity says what it is doing, where you can see it
119
+
120
+ The notices added in the previous release went to the agent's channel rather than
121
+ yours, so Verity said "this project is not set up" to Claude, silently. They now
122
+ reach your screen. Verity also asks for `/verity:init` once per plugin version —
123
+ returning after an update, quiet in between.
124
+
125
+ ### 🔧 Fixed
126
+
127
+ - `git add … && git commit` was never reviewed: the hook runs before the command,
128
+ so the index it read had no staged files. It now works out what the command
129
+ will stage.
130
+ - On a plugin-only machine, every skill's `verity …` command was `command not
131
+ found`; Verity now tells the agent how to reach the CLI it ships.
132
+ - A verdict the CLI does not understand is reported as unreviewed, not as a pass.
133
+
67
134
  ## [0.32.3] — 2026-09-10
68
135
 
69
136
  **Three things the first five minutes got wrong.** All reported from the field
package/bin/verity.js CHANGED
@@ -12936,6 +12936,11 @@ function pluginCliInvocation() {
12936
12936
  if (verityOnPath()) return null;
12937
12937
  return `node ${JSON.stringify((0, import_node_path7.join)(root, "scripts", "verity.mjs"))}`;
12938
12938
  }
12939
+ function activePluginVersion() {
12940
+ const fromEnv = process.env.VERITY_PLUGIN_VERSION;
12941
+ if (fromEnv) return fromEnv;
12942
+ return activePluginInstall()?.version ?? null;
12943
+ }
12939
12944
  var VERITY_MARKETPLACE = "verity";
12940
12945
  var VERITY_MARKETPLACE_REPO = "codacy/verity";
12941
12946
  function marketplaceConflict() {
@@ -13125,8 +13130,8 @@ async function resolveHookWiring() {
13125
13130
  // src/commands/hooks.ts
13126
13131
  var ALL_MOMENTS = ["stop", "pre-commit", "pre-push"];
13127
13132
  function parseMomentSelection(raw) {
13128
- const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
13129
- if (parts.length === 1 && parts[0].toLowerCase() === "none") return { moments: [] };
13133
+ const parts = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
13134
+ if (parts.length === 1 && parts[0] === "none") return { moments: [] };
13130
13135
  if (parts.length === 0) {
13131
13136
  return { error: `--moments was empty. Pass one or more of ${ALL_MOMENTS.join(", ")} \u2014 or "none" to gate nothing.` };
13132
13137
  }
@@ -17080,6 +17085,7 @@ var TOOLED_LANGUAGES = /* @__PURE__ */ new Set([
17080
17085
  "shell",
17081
17086
  "dockerfile"
17082
17087
  ]);
17088
+ var ACCESSORY_LANGUAGES = /* @__PURE__ */ new Set(["shell", "dockerfile"]);
17083
17089
  var MIN_FILES_FOR_LANGUAGE = 3;
17084
17090
  var IGNORED_SEGMENTS = [
17085
17091
  "node_modules",
@@ -17329,7 +17335,12 @@ function detectProject(root = repoRoot()) {
17329
17335
  if (!lang) continue;
17330
17336
  languageCounts[lang] = (languageCounts[lang] ?? 0) + 1;
17331
17337
  }
17332
- const languages = Object.entries(languageCounts).filter(([lang, count]) => TOOLED_LANGUAGES.has(lang) && count >= MIN_FILES_FOR_LANGUAGE).sort((a, b) => b[1] - a[1]).map(([lang]) => lang);
17338
+ const tooled = Object.entries(languageCounts).filter(([lang]) => TOOLED_LANGUAGES.has(lang)).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
17339
+ let languages = tooled.filter(([, count]) => count >= MIN_FILES_FOR_LANGUAGE).map(([lang]) => lang);
17340
+ if (languages.length === 0) {
17341
+ const primary = tooled.filter(([lang]) => !ACCESSORY_LANGUAGES.has(lang));
17342
+ if (primary.length > 0) languages = [primary[0][0]];
17343
+ }
17333
17344
  const dependencies = declaredDependencies(root, files);
17334
17345
  const existingToolConfigs = [];
17335
17346
  for (const [tool, markers] of TOOL_CONFIG_MARKERS) {
@@ -20937,7 +20948,7 @@ function channelSilence(input) {
20937
20948
  // src/lib/cli-version.ts
20938
20949
  function cliVersion() {
20939
20950
  try {
20940
- return true ? "0.32.3-experimental.e9605e1" : "dev";
20951
+ return true ? "0.32.4-experimental.e769665" : "dev";
20941
20952
  } catch {
20942
20953
  return "dev";
20943
20954
  }
@@ -24167,7 +24178,8 @@ var PROJECT_SKILL_NAMES = [
24167
24178
  "verity-learn",
24168
24179
  "verity-memory",
24169
24180
  "verity-insights",
24170
- "verity-reflect"
24181
+ "verity-reflect",
24182
+ "verity-init"
24171
24183
  ];
24172
24184
  var LEGACY_SKILL_NAMES = [
24173
24185
  "gate-setup",
@@ -24204,16 +24216,37 @@ function registerBaselineCommands(program2) {
24204
24216
  process.chdir(repoRoot());
24205
24217
  } catch {
24206
24218
  }
24219
+ let sessionId = opts.sessionId;
24220
+ let source = opts.source;
24221
+ if (!process.stdin.isTTY) {
24222
+ const input = (await readStdin()).trim();
24223
+ if (input) {
24224
+ try {
24225
+ const event = JSON.parse(input);
24226
+ sessionId = sessionId ?? event.session_id;
24227
+ source = source ?? event.source;
24228
+ } catch {
24229
+ }
24230
+ }
24231
+ }
24207
24232
  const cli = pluginCliInvocation();
24208
24233
  const cliLine = cli ? `Verity CLI: this machine has no \`verity\` on PATH \u2014 the plugin carries its own copy. When a Verity skill tells you to run \`verity <args>\`, run \`${cli} <args>\` instead. Everything else about the command is identical.` : null;
24209
24234
  if (!verityConfigured()) {
24210
- const how = isPluginInvocation() ? "Offer to run /verity:setup for the user." : "Offer to run `verity init` (or /verity-setup) for the user.";
24235
+ const how = isPluginInvocation() ? "Tell the user to run /verity:init \u2014 they type it; you cannot invoke it." : "Tell the user to run `verity init` in their terminal.";
24211
24236
  const lines = [
24212
24237
  `Verity is installed but this project is not set up yet, so the quality gate will not review anything here. ${how}`
24213
24238
  ];
24214
24239
  if (cliLine) lines.push(cliLine);
24215
24240
  process.stdout.write(
24216
24241
  JSON.stringify({
24242
+ // ⚠ BOTH FIELDS, AND THEY ARE NOT THE SAME AUDIENCE.
24243
+ // `additionalContext` reaches the AGENT; `systemMessage` reaches
24244
+ // the USER. Emitting only the first is what made this notice
24245
+ // invisible: the agent was told, said nothing unprompted, and a
24246
+ // user who restarted Claude Code specifically to see it saw an
24247
+ // empty screen. guard.ts has carried that distinction in a comment
24248
+ // since 0.30 — the same lesson, unapplied here until someone hit it.
24249
+ systemMessage: isPluginInvocation() ? "Verity: this project is not set up yet, so nothing is being reviewed here. Run /verity:init to configure it." : "Verity: this project is not set up yet, so nothing is being reviewed here. Run `verity init` to configure it.",
24217
24250
  hookSpecificOutput: {
24218
24251
  hookEventName: "SessionStart",
24219
24252
  additionalContext: lines.join("\n")
@@ -24222,21 +24255,32 @@ function registerBaselineCommands(program2) {
24222
24255
  );
24223
24256
  process.exit(0);
24224
24257
  }
24225
- if (isPluginInvocation()) {
24258
+ const realStart = source === void 0 || source === "startup" || source === "clear";
24259
+ if (isPluginInvocation() && realStart) {
24260
+ const installed2 = activePluginVersion();
24261
+ const state = await readSetupState().catch(() => null);
24262
+ const stale = staleProjectSkills();
24226
24263
  const notices = [];
24227
24264
  if (cliLine) notices.push(cliLine);
24228
- const stale = staleProjectSkills();
24229
- if (stale.length > 0) {
24230
- notices.push(
24231
- "Verity is already set up in this project, so /verity:setup is NOT being run \u2014 tell the user that, and that they can run it themselves if they want to change the Standard, the analysis tools or the review moments."
24232
- );
24265
+ const neverRan = !state?.pluginSetup;
24266
+ const ownsItsWiring = state?.wiring === "project";
24267
+ const needsSetup = !ownsItsWiring && (neverRan || stale.length > 0) && !!installed2;
24268
+ let userMsg = null;
24269
+ if (needsSetup) {
24270
+ userMsg = stale.length > 0 ? `Verity: the plugin is installed \u2014 run /verity:init to finish setting it up here (it will also clear ${stale.length} duplicate skills).` : "Verity: the plugin is installed \u2014 run /verity:init to finish setting it up here.";
24233
24271
  notices.push(
24234
- `This project also still carries ${stale.length} of its own Verity skills (${stale.slice(0, 3).join(", ")}${stale.length > 3 ? ", \u2026" : ""}), left from an npm install. The plugin supersedes them, which is why the slash-command list shows each one twice. Offer to run \`verity init --plugin-mode --yes\` to remove the duplicates; the plugin keeps providing the /verity:* set.`
24272
+ "The Verity plugin is installed here but /verity:init has not been run for this project. Installing the plugin cannot repair a project on its own \u2014 Claude Code has no post-install hook \u2014 so tell the user to run /verity:init (they type it; it is not a skill you can invoke). It asks how they want reviews to run, then does the repair."
24235
24273
  );
24274
+ if (stale.length > 0) {
24275
+ notices.push(
24276
+ `Specifically: this project still carries ${stale.length} of its own Verity skills (${stale.slice(0, 3).join(", ")}${stale.length > 3 ? ", \u2026" : ""}) from an npm install, which is why the slash-command list shows each one twice. /verity:init removes them.`
24277
+ );
24278
+ }
24236
24279
  }
24237
24280
  if (notices.length > 0) {
24238
24281
  process.stdout.write(
24239
24282
  JSON.stringify({
24283
+ ...userMsg ? { systemMessage: userMsg } : {},
24240
24284
  hookSpecificOutput: {
24241
24285
  hookEventName: "SessionStart",
24242
24286
  additionalContext: notices.join("\n")
@@ -24245,19 +24289,6 @@ function registerBaselineCommands(program2) {
24245
24289
  );
24246
24290
  }
24247
24291
  }
24248
- let sessionId = opts.sessionId;
24249
- let source = opts.source;
24250
- if (!process.stdin.isTTY) {
24251
- const input = (await readStdin()).trim();
24252
- if (input) {
24253
- try {
24254
- const event = JSON.parse(input);
24255
- sessionId = sessionId ?? event.session_id;
24256
- source = source ?? event.source;
24257
- } catch {
24258
- }
24259
- }
24260
- }
24261
24292
  if (deferredToPlugin("baseline capture", sessionId ?? process.env.CLAUDE_SESSION_ID ?? null)) {
24262
24293
  process.exit(0);
24263
24294
  }
@@ -26296,7 +26327,8 @@ var SKILLS = [
26296
26327
  "verity-learn",
26297
26328
  "verity-memory",
26298
26329
  "verity-insights",
26299
- "verity-reflect"
26330
+ "verity-reflect",
26331
+ "verity-init"
26300
26332
  ];
26301
26333
  var INTENSITY_CHOICES = [
26302
26334
  { id: "lightweight", label: "lightweight", hint: "critical security only, fastest (~3s)" },
@@ -26317,21 +26349,23 @@ async function momentsFromInstalledHooks() {
26317
26349
  if (status.guardOn.includes("push")) wired.push("pre-push");
26318
26350
  return wired.length > 0 ? wired : DEFAULT_MOMENTS;
26319
26351
  }
26320
- async function askSetupQuestions(defaultsOnly, previous) {
26321
- const intensityDefault = previous?.intensity ?? "balanced";
26322
- const momentsDefault = previous?.moments?.length ? previous.moments : await momentsFromInstalledHooks();
26323
- if (defaultsOnly) {
26352
+ async function askSetupQuestions(defaultsOnly, previous, given = {}) {
26353
+ const intensityDefault = given.intensity ?? previous?.intensity ?? "balanced";
26354
+ const momentsDefault = given.moments ?? (previous?.moments?.length ? previous.moments : await momentsFromInstalledHooks());
26355
+ const askIntensity = !given.intensity;
26356
+ const askMoments = !given.moments;
26357
+ if (defaultsOnly || !askIntensity && !askMoments) {
26324
26358
  return { intensity: intensityDefault, moments: momentsDefault, telemetry: "not-asked" };
26325
26359
  }
26326
26360
  if (previous?.intensity || previous?.moments) {
26327
26361
  printInfo(`Current: ${intensityDefault} \xB7 ${momentsDefault.join(", ")} \u2014 press Enter to keep either.`);
26328
26362
  }
26329
- const intensity = await promptChoice(
26363
+ const intensity = given.intensity ?? await promptChoice(
26330
26364
  "Analysis intensity \u2014 how deeply should Verity review?",
26331
26365
  INTENSITY_CHOICES,
26332
26366
  intensityDefault
26333
26367
  );
26334
- const moments = await promptMultiSelect(
26368
+ const moments = given.moments ?? await promptMultiSelect(
26335
26369
  "When should Verity review your code?",
26336
26370
  MOMENT_CHOICES,
26337
26371
  momentsDefault
@@ -26671,7 +26705,13 @@ function registerInitCommand(program2) {
26671
26705
  ).option(
26672
26706
  "--no-plugin",
26673
26707
  "Ignore any Claude Code plugin here and wire this project's own skills and hooks"
26674
- ).option("--no-adopt", "Don't offer this repository's existing Standard from the service; synthesize a new one").action(async (opts) => {
26708
+ ).option("--no-adopt", "Don't offer this repository's existing Standard from the service; synthesize a new one").option(
26709
+ "--moments <list>",
26710
+ 'Answer the review-moments question without asking: stop,pre-commit,pre-push \u2014 or "none"'
26711
+ ).option(
26712
+ "--intensity <level>",
26713
+ "Answer the analysis-intensity question without asking: lightweight | balanced | thorough"
26714
+ ).action(async (opts) => {
26675
26715
  const force = opts.force ?? false;
26676
26716
  const wantsHandoff = opts.setup !== false;
26677
26717
  const wantsAdopt = opts.adopt !== false;
@@ -26739,7 +26779,31 @@ function registerInitCommand(program2) {
26739
26779
  }
26740
26780
  step(defaultsOnly ? "Setup answers (defaults)" : "Your setup answers");
26741
26781
  const previous = await readSetupState();
26742
- const answers = await askSetupQuestions(defaultsOnly, previous);
26782
+ let givenMoments;
26783
+ if (opts.moments != null) {
26784
+ const parsed = parseMomentSelection(String(opts.moments));
26785
+ if ("error" in parsed) {
26786
+ printError(parsed.error);
26787
+ process.exit(1);
26788
+ }
26789
+ givenMoments = parsed.moments;
26790
+ }
26791
+ let givenIntensity;
26792
+ if (opts.intensity != null) {
26793
+ const want = String(opts.intensity).trim().toLowerCase();
26794
+ const match = INTENSITY_CHOICES.find((c) => c.id === want);
26795
+ if (!match) {
26796
+ printError(
26797
+ `--intensity: unrecognised "${opts.intensity}". Valid: ${INTENSITY_CHOICES.map((c) => c.id).join(", ")}.`
26798
+ );
26799
+ process.exit(1);
26800
+ }
26801
+ givenIntensity = match.id;
26802
+ }
26803
+ const answers = await askSetupQuestions(defaultsOnly, previous, {
26804
+ intensity: givenIntensity,
26805
+ moments: givenMoments
26806
+ });
26743
26807
  const { intensity, moments } = answers;
26744
26808
  if (defaultsOnly) {
26745
26809
  printInfo(` intensity: ${intensity} \xB7 moments: ${moments.join(", ") || "none"} (no questions asked)`);
@@ -26754,7 +26818,7 @@ function registerInitCommand(program2) {
26754
26818
  ...moments.includes("pre-push") ? ["push"] : []
26755
26819
  ];
26756
26820
  const existingMoments = readProjectConfig();
26757
- const keepExisting = defaultsOnly && existingMoments.git_moments_source === "user";
26821
+ const keepExisting = !givenMoments && defaultsOnly && existingMoments.git_moments_source === "user";
26758
26822
  if (keepExisting) {
26759
26823
  printInfo(
26760
26824
  ` Keeping the git moments you set: ${existingMoments.git_moments.join(", ") || "none"} (this run asked no questions, so it does not overrule them).`
@@ -26763,10 +26827,15 @@ function registerInitCommand(program2) {
26763
26827
  writeProjectConfig({ git_moments: gitMoments, git_moments_source: "init" });
26764
26828
  }
26765
26829
  const effectiveMoments = keepExisting ? existingMoments.git_moments : gitMoments;
26830
+ const effectiveMomentIds = [
26831
+ ...moments.includes("stop") ? ["stop"] : [],
26832
+ ...effectiveMoments.includes("commit") ? ["pre-commit"] : [],
26833
+ ...effectiveMoments.includes("push") ? ["pre-push"] : []
26834
+ ];
26766
26835
  if (pluginMode) {
26767
- await adoptPluginWiring(effectiveMoments, moments);
26836
+ await adoptPluginWiring(effectiveMoments, effectiveMomentIds);
26768
26837
  } else {
26769
- await reconcileOwnWiring(moments);
26838
+ await reconcileOwnWiring(effectiveMomentIds);
26770
26839
  }
26771
26840
  console.log("");
26772
26841
  step("Sign in to Verity (optional)");
@@ -26832,11 +26901,14 @@ function registerInitCommand(program2) {
26832
26901
  try {
26833
26902
  await writeSetupState({
26834
26903
  intensity,
26835
- moments,
26904
+ // The effective answer, not this run's: the next interactive init
26905
+ // pre-fills from this record, so a stale "stop" here offers "press
26906
+ // Enter to keep" over a commit gate that Enter would remove.
26907
+ moments: effectiveMomentIds,
26836
26908
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
26837
26909
  init: {
26838
26910
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
26839
- cli_version: true ? "0.32.3-experimental.e9605e1" : "dev"
26911
+ cli_version: true ? "0.32.4-experimental.e769665" : "dev"
26840
26912
  }
26841
26913
  });
26842
26914
  } catch (err) {
@@ -26863,6 +26935,17 @@ function registerInitCommand(program2) {
26863
26935
  console.log(` Intensity: ${intensity} Moments: ${moments.join(", ") || "none"}`);
26864
26936
  console.log("");
26865
26937
  if (haveStandard) {
26938
+ if (pluginMode) {
26939
+ const pluginVersion = activePluginVersion();
26940
+ if (pluginVersion) {
26941
+ await writeSetupState({
26942
+ pluginSetup: { version: pluginVersion, completed_at: (/* @__PURE__ */ new Date()).toISOString() },
26943
+ wiring: void 0
26944
+ });
26945
+ }
26946
+ } else if (opts.plugin === false) {
26947
+ await writeSetupState({ wiring: "project" });
26948
+ }
26866
26949
  printInfo("Setup complete \u2014 the gate is live on your next Claude Code session.");
26867
26950
  printInfo(' Add project-specific rules any time: edit .verity/standard.yaml, then "verity standard push".');
26868
26951
  printInfo(" Or have a model propose them: /verity-setup --force in Claude Code.");
@@ -27506,8 +27589,8 @@ function registerTelemetryCommands(program2) {
27506
27589
  }
27507
27590
 
27508
27591
  // src/cli.ts
27509
- program.name("verity").description("CLI for Verity quality gate service").version("0.32.3-experimental.e9605e1").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) => {
27510
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.3-experimental.e9605e1");
27592
+ program.name("verity").description("CLI for Verity quality gate service").version("0.32.4-experimental.e769665").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) => {
27593
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.4-experimental.e769665");
27511
27594
  setUserNamedServiceUrl(program.opts().serviceUrl);
27512
27595
  try {
27513
27596
  await foldLegacyLocalCredential();
@@ -0,0 +1,113 @@
1
+ ---
2
+ description: >-
3
+ Set Verity up in this project: ask the two setup questions, run the deterministic
4
+ install, and repair a project the Claude Code plugin has just been added to. Use
5
+ right after installing the Verity plugin, or when Verity says this project is not
6
+ set up.
7
+ disable-model-invocation: true
8
+ ---
9
+ # /verity-init — set Verity up in this project
10
+
11
+ You are running the setup a plugin install cannot do for itself.
12
+
13
+ **Why this skill exists.** Claude Code has no post-install hook and a plugin
14
+ cannot invoke a skill, so installing Verity repairs nothing: it cannot remove a
15
+ project's superseded skills, stand down duplicate hooks, or derive a Standard.
16
+ All of that needs a command someone runs — this one.
17
+
18
+ **`verity init` prompts on a terminal, and you are not one.** That is the whole
19
+ problem this skill solves. Left to itself the CLI would take `--yes` and silently
20
+ choose **stop-only** review — no pre-commit gate, no pre-push gate — for every
21
+ plugin user, which is exactly the outcome we treat as a serious bug when it
22
+ happens by accident. So **you** ask, in the session, where the user actually is.
23
+
24
+ ---
25
+
26
+ ## Step 1: Ask both questions in ONE prompt
27
+
28
+ Use `AskUserQuestion` with **both** questions in a single call. Do not ask them
29
+ one at a time, and do not ask them in prose — this is the only interruption the
30
+ setup gets, so spend it once.
31
+
32
+ **Question 1 — "When should Verity review your code?"** (multi-select)
33
+
34
+ | Option | Description |
35
+ |---|---|
36
+ | `After every turn (recommended)` | Reviews what the agent just wrote, as you work. Fast feedback, never blocks a commit. |
37
+ | `Before commit` | Reviews the staged diff and blocks the commit on a failing review. |
38
+ | `Before push / PR` | Reviews the commits about to be pushed and blocks on a failing review. |
39
+
40
+ Pre-select **After every turn**. Say plainly in the question text that choosing
41
+ only the first means **git commits and pushes are not gated**.
42
+
43
+ **Question 2 — "How deeply should Verity review?"** (single-select)
44
+
45
+ | Option | Description |
46
+ |---|---|
47
+ | `Balanced (recommended)` | Security + quality, about 8s per review. |
48
+ | `Lightweight` | Critical security only, about 3s. |
49
+ | `Thorough` | All tools, all rules, about 15s. |
50
+
51
+ ## Step 2: Run the install with those answers
52
+
53
+ Map the answers to flags and run it — one command, no prompts, nothing silently
54
+ defaulted:
55
+
56
+ ```bash
57
+ verity init --yes --no-setup --moments <stop[,pre-commit][,pre-push]> --intensity <balanced|lightweight|thorough>
58
+ ```
59
+
60
+ `--moments none` is the deliberate way to gate nothing; an unrecognised value is
61
+ refused rather than dropped, so a typo cannot quietly remove a gate.
62
+
63
+ This is idempotent and does the whole job: removes the project's own
64
+ `.claude/skills/verity-*` copies that the plugin supersedes (they are why the
65
+ slash-command list shows every skill twice), stands down duplicate
66
+ `.claude/settings.json` hooks so a turn is reviewed once, derives the Standard
67
+ and the analysis config if there is none, heals a stale one, and records the
68
+ plugin version — which is what stops Verity asking at every session start.
69
+
70
+ Report what it removed. Those skill directories are usually committed, so the
71
+ deletions will show in `git status` and are the user's to commit.
72
+
73
+ ## Step 3: Tell the user to sign in — in their own terminal
74
+
75
+ **Do not run `verity login` yourself.** It is a GitHub device flow: it prints a
76
+ code, waits for the user to approve it in a browser, and blocks until they do.
77
+ Started from a tool call it just hangs, and the user never sees the code.
78
+
79
+ Say this, and stop:
80
+
81
+ > Verity is set up. One more step, in your own terminal — not here:
82
+ >
83
+ > ```
84
+ > verity login
85
+ > ```
86
+ >
87
+ > It prints a code and a link; approve it in your browser. One login covers every
88
+ > repository you can write to, for 90 days. Without it the gate still reviews
89
+ > your code — it just records nothing, so there is no history, no cloud memory
90
+ > and no dashboard.
91
+
92
+ If `verity status` already reports an account, skip this step and say so.
93
+
94
+ ## Step 4: Offer the part a lookup cannot produce
95
+
96
+ The Standard `init` derives covers the research-backed patterns for the
97
+ languages and tools it found. What it deliberately leaves empty is
98
+ `custom_patterns` — rules like *"every route under `api/` uses the auth
99
+ middleware"*, which need something to read the code for intent.
100
+
101
+ Offer `/verity-setup` for that, and say it is optional: the gate is already
102
+ running.
103
+
104
+ ---
105
+
106
+ ## What NOT to do here
107
+
108
+ - **Do not wire hooks or edit `.gitignore` yourself.** `verity init` owns that.
109
+ Doing it here is how the two flows drifted apart before — the skill reconciled
110
+ hooks init had just installed and silently removed them.
111
+ - **Do not synthesize a Standard by hand.** The CLI derives it in about two
112
+ seconds from the same catalogue you would be reading.
113
+ - **Do not run `verity login`.** Step 3.
@@ -48,20 +48,29 @@ verity doctor --json
48
48
  ```
49
49
 
50
50
  One read gives you prerequisites, which phase is done, the user's recorded
51
- answers, the hook wiring, and a `next` list. Branch on it:
51
+ answers, the hook wiring, and a `next` list.
52
52
 
53
- - **`phases.init.done` is false** `verity init` has not run in this project.
54
- Run it (it is idempotent, and it is what installed this skill):
53
+ ### 1a. If the project is not set up at all, that is init's job, not yours
55
54
 
56
- ```bash
57
- verity init --yes --no-setup
58
- ```
55
+ ⚠ **If `phases.init.done` is false, stop and say so — naming the command that
56
+ exists on THIS install:**
57
+
58
+ - **`hooks.source` is `"plugin"`** → `/verity-init`, which the plugin provides.
59
+ - **otherwise (npm install)** → `verity init`, run in a terminal. Do **not** send
60
+ an npm user to `/verity-init`: it is a plugin skill, and on that path it does
61
+ not exist. A remedy that names a command the user does not have is worse than
62
+ no remedy — they cannot tell whether they are stuck or broken.
63
+
64
+ **Do not run `verity init` from this skill.** That flow asks the user two
65
+ questions before it installs anything, and running it here takes the answers
66
+ away from them — silently choosing stop-only review, which means no commit or
67
+ push gate. One skill owns setup, and it is not this one.
68
+
69
+ Branch on the same `doctor` output:
59
70
 
60
- `--yes` takes the recommended answer for every question use it only because
61
- you are already inside a Claude Code session and cannot ask on init's terminal.
62
- Tell the user which defaults were taken and that `verity init` (run by hand in a
63
- terminal) is where those choices are made. `--no-setup` stops init from trying
64
- to launch a second Claude Code session on top of this one.
71
+ - **`phases.init.done` is false** setup has not run here. Point the user at the
72
+ command their install actually has (see 1a) and stop. That is the one place the
73
+ setup questions get asked.
65
74
  - **`blocked` is true** → a required prerequisite is missing. Show the
66
75
  `prerequisites[].remedy` lines and stop; nothing below can work.
67
76
  - **`artifacts.standard` is true and `phases.setup.done` is true** → the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.32.3-experimental.e9605e1",
3
+ "version": "0.32.4-experimental.e769665",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",