@codacy/verity-cli 0.32.4 → 0.32.5

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,22 @@ 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.5] — 2026-09-11
68
+
69
+ **`/verity:init` fixes, reported from its first runs.**
70
+
71
+ - **An old global `verity` can no longer derail setup.** If the `verity` on your
72
+ PATH is a different version from the plugin's, Verity's skills now use the
73
+ plugin's own copy — the one its hooks run — so your setup answers always apply.
74
+ Verity also tells you when the session starts, before `/verity:init` asks
75
+ anything, with the `npm install -g` command that brings the global one in line.
76
+ - **"After every turn" says what it is.** With the plugin, the after-every-turn
77
+ review can't be turned off yet. The setup question now says so instead of
78
+ calling it "recommended".
79
+ - **The update instructions name commands that exist.** Claude Code has no
80
+ `/plugin update`. The README now explains auto-update, and `/reload-plugins`
81
+ picks up a new version without restarting.
82
+
67
83
  ## [0.32.4] — 2026-09-11
68
84
 
69
85
  **Setup asks you how you want Verity to work.** Until now, installing the Claude
package/bin/verity.js CHANGED
@@ -6849,6 +6849,7 @@ var require_resolve_props = __commonJS({
6849
6849
  hasSpace = false;
6850
6850
  break;
6851
6851
  }
6852
+ // else fallthrough
6852
6853
  default:
6853
6854
  onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.type} token`);
6854
6855
  atNewline = false;
@@ -7557,6 +7558,7 @@ var require_resolve_block_scalar = __commonJS({
7557
7558
  switch (token.type) {
7558
7559
  case "space":
7559
7560
  hasSpace = true;
7561
+ // fallthrough
7560
7562
  case "newline":
7561
7563
  length += token.source.length;
7562
7564
  break;
@@ -7572,6 +7574,7 @@ var require_resolve_block_scalar = __commonJS({
7572
7574
  onError(token, "UNEXPECTED_TOKEN", token.message);
7573
7575
  length += token.source.length;
7574
7576
  break;
7577
+ /* istanbul ignore next should not happen */
7575
7578
  default: {
7576
7579
  const message = `Unexpected token in block scalar header: ${token.type}`;
7577
7580
  onError(token, "UNEXPECTED_TOKEN", message);
@@ -7621,6 +7624,7 @@ var require_resolve_flow_scalar = __commonJS({
7621
7624
  _type = Scalar.Scalar.QUOTE_DOUBLE;
7622
7625
  value = doubleQuotedValue(source, _onError);
7623
7626
  break;
7627
+ /* istanbul ignore next should not happen */
7624
7628
  default:
7625
7629
  onError(scalar, "UNEXPECTED_TOKEN", `Expected a flow scalar value, but found: ${type}`);
7626
7630
  return {
@@ -7642,6 +7646,7 @@ var require_resolve_flow_scalar = __commonJS({
7642
7646
  function plainValue(source, onError) {
7643
7647
  let badChar = "";
7644
7648
  switch (source[0]) {
7649
+ /* istanbul ignore next should not happen */
7645
7650
  case " ":
7646
7651
  badChar = "a tab character";
7647
7652
  break;
@@ -8905,6 +8910,7 @@ var require_lexer = __commonJS({
8905
8910
  switch (line[n]) {
8906
8911
  case "#":
8907
8912
  yield* this.pushCount(line.length - n);
8913
+ // fallthrough
8908
8914
  case void 0:
8909
8915
  yield* this.pushNewline();
8910
8916
  return yield* this.parseLineStart();
@@ -9000,6 +9006,7 @@ var require_lexer = __commonJS({
9000
9006
  return "flow";
9001
9007
  }
9002
9008
  }
9009
+ // fallthrough
9003
9010
  default:
9004
9011
  this.flowKey = false;
9005
9012
  return yield* this.parsePlainScalar();
@@ -9077,6 +9084,7 @@ var require_lexer = __commonJS({
9077
9084
  if (next === "\n")
9078
9085
  break;
9079
9086
  }
9087
+ // fallthrough
9080
9088
  default:
9081
9089
  break loop;
9082
9090
  }
@@ -9194,7 +9202,9 @@ var require_lexer = __commonJS({
9194
9202
  case "&":
9195
9203
  return (yield* this.pushUntil(isNotAnchorChar)) + (yield* this.pushSpaces(true)) + (yield* this.pushIndicators());
9196
9204
  case "-":
9205
+ // this is an error
9197
9206
  case "?":
9207
+ // this is an error outside flow collections
9198
9208
  case ":": {
9199
9209
  const inFlow = this.flowLevel > 0;
9200
9210
  const ch1 = this.charAt(1);
@@ -9343,6 +9353,7 @@ var require_parser = __commonJS({
9343
9353
  }
9344
9354
  case "block-seq":
9345
9355
  return parent.items[parent.items.length - 1].start;
9356
+ /* istanbul ignore next should not happen */
9346
9357
  default:
9347
9358
  return [];
9348
9359
  }
@@ -9576,6 +9587,7 @@ var require_parser = __commonJS({
9576
9587
  Object.assign(it, { key: token, sep: [] });
9577
9588
  return;
9578
9589
  }
9590
+ /* istanbul ignore next should not happen */
9579
9591
  default:
9580
9592
  yield* this.pop();
9581
9593
  yield* this.pop(token);
@@ -9697,6 +9709,7 @@ var require_parser = __commonJS({
9697
9709
  }
9698
9710
  yield* this.pop();
9699
9711
  break;
9712
+ /* istanbul ignore next should not happen */
9700
9713
  default:
9701
9714
  yield* this.pop();
9702
9715
  yield* this.step();
@@ -10142,6 +10155,7 @@ var require_parser = __commonJS({
10142
10155
  break;
10143
10156
  case "newline":
10144
10157
  this.onKeyLine = false;
10158
+ // fallthrough
10145
10159
  case "space":
10146
10160
  case "comment":
10147
10161
  default:
@@ -11612,6 +11626,7 @@ async function githubDeviceFlow() {
11612
11626
  switch (data.error) {
11613
11627
  case "authorization_pending":
11614
11628
  break;
11629
+ // user hasn't approved yet
11615
11630
  case "slow_down":
11616
11631
  interval += 5;
11617
11632
  break;
@@ -12917,25 +12932,48 @@ function marketplaceLocations() {
12917
12932
  }
12918
12933
  return out;
12919
12934
  }
12920
- function verityOnPath() {
12935
+ function verityPathEntry() {
12921
12936
  const path = process.env.PATH;
12922
- if (!path) return false;
12937
+ if (!path) return null;
12923
12938
  for (const dir of path.split(import_node_path7.delimiter)) {
12924
12939
  if (!dir) continue;
12940
+ const candidate = (0, import_node_path7.join)(dir, "verity");
12925
12941
  try {
12926
- (0, import_node_fs6.accessSync)((0, import_node_path7.join)(dir, "verity"), import_node_fs6.constants.X_OK);
12927
- return true;
12942
+ (0, import_node_fs6.accessSync)(candidate, import_node_fs6.constants.X_OK);
12943
+ return candidate;
12928
12944
  } catch {
12929
12945
  }
12930
12946
  }
12931
- return false;
12947
+ return null;
12948
+ }
12949
+ function verityOnPath() {
12950
+ return verityPathEntry() !== null;
12951
+ }
12952
+ function globalVerityVersion() {
12953
+ const entry = verityPathEntry();
12954
+ if (!entry) return null;
12955
+ try {
12956
+ const pkg = readJsonFile((0, import_node_path7.join)((0, import_node_path7.dirname)((0, import_node_fs6.realpathSync)(entry)), "..", "package.json"));
12957
+ if (pkg?.name !== "@codacy/verity-cli" || typeof pkg.version !== "string") return null;
12958
+ return pkg.version;
12959
+ } catch {
12960
+ return null;
12961
+ }
12932
12962
  }
12933
12963
  function pluginCliInvocation() {
12934
12964
  const root = process.env.VERITY_PLUGIN_ROOT;
12935
12965
  if (!root) return null;
12936
- if (verityOnPath()) return null;
12966
+ const onPath = globalVerityVersion();
12967
+ if (onPath !== null && onPath === activePluginVersion()) return null;
12937
12968
  return `node ${JSON.stringify((0, import_node_path7.join)(root, "scripts", "verity.mjs"))}`;
12938
12969
  }
12970
+ function cliVersionSkew() {
12971
+ if (!process.env.VERITY_PLUGIN_ROOT) return null;
12972
+ const onPath = globalVerityVersion();
12973
+ const plugin = activePluginVersion();
12974
+ if (!onPath || !plugin || onPath === plugin) return null;
12975
+ return { global: onPath, plugin };
12976
+ }
12939
12977
  function activePluginVersion() {
12940
12978
  const fromEnv = process.env.VERITY_PLUGIN_VERSION;
12941
12979
  if (fromEnv) return fromEnv;
@@ -20596,6 +20634,15 @@ function buildSummary(lines) {
20596
20634
  }
20597
20635
  case "Agent":
20598
20636
  case "Task":
20637
+ // Claude Code's actual subagent tool name (legacy 'Agent' kept)
20638
+ // ⚠ ORCHESTRATION IS DELEGATION. `Workflow` fans work out to many
20639
+ // subagents; `SendMessage` hands it to an existing one.
20640
+ //
20641
+ // Two consumers read this counter and both fail closed without it: a
20642
+ // delegated turn reads as a plain chat reply, so `didAgentInvestigate`
20643
+ // downgrades a plan-mode review to `skip`, and `hasNonEditAuthorship`
20644
+ // loses the rescue that stops delegated code exiting via
20645
+ // `zero-increment` with its PASS intact. See DECISIONS.md D21.
20599
20646
  case "Workflow":
20600
20647
  case "SendMessage":
20601
20648
  subagents++;
@@ -20948,7 +20995,7 @@ function channelSilence(input) {
20948
20995
  // src/lib/cli-version.ts
20949
20996
  function cliVersion() {
20950
20997
  try {
20951
- return true ? "0.32.4" : "dev";
20998
+ return true ? "0.32.5" : "dev";
20952
20999
  } catch {
20953
21000
  return "dev";
20954
21001
  }
@@ -24216,27 +24263,30 @@ function registerBaselineCommands(program2) {
24216
24263
  process.chdir(repoRoot());
24217
24264
  } catch {
24218
24265
  }
24219
- let sessionId = opts.sessionId;
24220
- let source = opts.source;
24266
+ let sessionId = validSessionId(opts.sessionId);
24267
+ let source = hookSource(opts.source);
24221
24268
  if (!process.stdin.isTTY) {
24222
24269
  const input = (await readStdin()).trim();
24223
24270
  if (input) {
24224
- try {
24225
- const event = JSON.parse(input);
24226
- sessionId = sessionId ?? event.session_id;
24227
- source = source ?? event.source;
24228
- } catch {
24229
- }
24271
+ const event = parseHookEvent(input);
24272
+ if (opts.sessionId === void 0) sessionId = event.sessionId;
24273
+ if (opts.source === void 0) source = event.source;
24230
24274
  }
24231
24275
  }
24276
+ const session = sessionId ?? validSessionId(process.env.CLAUDE_SESSION_ID);
24232
24277
  const cli = pluginCliInvocation();
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;
24278
+ const skew = cliVersionSkew();
24279
+ const why = skew ? `the \`verity\` on PATH is ${skew.global} but this plugin runs ${skew.plugin}, so it is not the CLI the hooks use` : verityOnPath() ? "the `verity` on PATH is a separate install whose version could not be read" : "this machine has no `verity` on PATH \u2014 the plugin carries its own copy";
24280
+ const cliLine = cli ? `Verity CLI: ${why}. When a Verity skill tells you to run \`verity <args>\`, run \`${cli} <args>\` instead. Everything else about the command is identical.` : null;
24281
+ const skewMsg = skew ? `Verity: your global \`verity\` CLI is ${skew.global} but the plugin runs ${skew.plugin}. Update it before using \`verity\` in a terminal: npm install -g @codacy/verity-cli@${skew.plugin}` : null;
24282
+ const skewNote = skew ? `Before anything else, including /verity:init's questions, tell the user their global \`verity\` is ${skew.global}, the plugin runs ${skew.plugin}, and \`npm install -g @codacy/verity-cli@${skew.plugin}\` fixes it. Until they have, do not send them to run \`verity login\` in a terminal: that runs the global one, which can sign this machine in to a different server.` : null;
24234
24283
  if (!verityConfigured()) {
24235
24284
  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.";
24236
24285
  const lines = [
24237
24286
  `Verity is installed but this project is not set up yet, so the quality gate will not review anything here. ${how}`
24238
24287
  ];
24239
24288
  if (cliLine) lines.push(cliLine);
24289
+ if (skewNote) lines.unshift(skewNote);
24240
24290
  process.stdout.write(
24241
24291
  JSON.stringify({
24242
24292
  // ⚠ BOTH FIELDS, AND THEY ARE NOT THE SAME AUDIENCE.
@@ -24246,7 +24296,11 @@ function registerBaselineCommands(program2) {
24246
24296
  // user who restarted Claude Code specifically to see it saw an
24247
24297
  // empty screen. guard.ts has carried that distinction in a comment
24248
24298
  // 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.",
24299
+ // The mismatch first: it decides whether the setup that follows can work.
24300
+ systemMessage: [
24301
+ skewMsg,
24302
+ 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."
24303
+ ].filter(Boolean).join("\n"),
24250
24304
  hookSpecificOutput: {
24251
24305
  hookEventName: "SessionStart",
24252
24306
  additionalContext: lines.join("\n")
@@ -24261,6 +24315,7 @@ function registerBaselineCommands(program2) {
24261
24315
  const state = await readSetupState().catch(() => null);
24262
24316
  const stale = staleProjectSkills();
24263
24317
  const notices = [];
24318
+ if (skewNote) notices.push(skewNote);
24264
24319
  if (cliLine) notices.push(cliLine);
24265
24320
  const neverRan = !state?.pluginSetup;
24266
24321
  const ownsItsWiring = state?.wiring === "project";
@@ -24280,7 +24335,7 @@ function registerBaselineCommands(program2) {
24280
24335
  if (notices.length > 0) {
24281
24336
  process.stdout.write(
24282
24337
  JSON.stringify({
24283
- ...userMsg ? { systemMessage: userMsg } : {},
24338
+ ...userMsg || skewMsg ? { systemMessage: [skewMsg, userMsg].filter(Boolean).join("\n") } : {},
24284
24339
  hookSpecificOutput: {
24285
24340
  hookEventName: "SessionStart",
24286
24341
  additionalContext: notices.join("\n")
@@ -24289,12 +24344,12 @@ function registerBaselineCommands(program2) {
24289
24344
  );
24290
24345
  }
24291
24346
  }
24292
- if (deferredToPlugin("baseline capture", sessionId ?? process.env.CLAUDE_SESSION_ID ?? null)) {
24347
+ if (deferredToPlugin("baseline capture", session ?? null)) {
24293
24348
  process.exit(0);
24294
24349
  }
24295
24350
  const authForScope = await resolveToken(program2.opts().token);
24296
24351
  const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
24297
- const scopeSession = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
24352
+ const scopeSession = session;
24298
24353
  const result = captureBaseline({ sessionId: sessionScopeKey(scopeToken, scopeSession), source });
24299
24354
  logEvent("baseline_capture", {
24300
24355
  created: result.created,
@@ -24307,12 +24362,35 @@ function registerBaselineCommands(program2) {
24307
24362
  process.exit(0);
24308
24363
  });
24309
24364
  }
24365
+ var MAX_HOOK_EVENT_BYTES = 64 * 1024;
24366
+ var HOOK_SOURCES = /* @__PURE__ */ new Set(["startup", "resume", "clear", "compact"]);
24367
+ var SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
24310
24368
  async function readStdin() {
24311
24369
  const chunks = [];
24370
+ let size = 0;
24312
24371
  for await (const chunk of process.stdin) {
24313
- chunks.push(chunk);
24372
+ size += chunk.length;
24373
+ if (size <= MAX_HOOK_EVENT_BYTES) chunks.push(chunk);
24374
+ }
24375
+ return size > MAX_HOOK_EVENT_BYTES ? "" : Buffer.concat(chunks).toString("utf-8");
24376
+ }
24377
+ function parseHookEvent(input) {
24378
+ let event;
24379
+ try {
24380
+ event = JSON.parse(input);
24381
+ } catch {
24382
+ return {};
24314
24383
  }
24315
- return Buffer.concat(chunks).toString("utf-8");
24384
+ if (!event || typeof event !== "object" || Array.isArray(event)) return {};
24385
+ const { session_id: id, source } = event;
24386
+ return { sessionId: validSessionId(id), source: hookSource(source) };
24387
+ }
24388
+ function validSessionId(value) {
24389
+ return typeof value === "string" && SESSION_ID_RE.test(value) ? value : void 0;
24390
+ }
24391
+ function hookSource(value) {
24392
+ if (typeof value !== "string") return void 0;
24393
+ return HOOK_SOURCES.has(value) ? value : "unknown";
24316
24394
  }
24317
24395
 
24318
24396
  // src/commands/review.ts
@@ -26908,7 +26986,7 @@ function registerInitCommand(program2) {
26908
26986
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
26909
26987
  init: {
26910
26988
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
26911
- cli_version: true ? "0.32.4" : "dev"
26989
+ cli_version: true ? "0.32.5" : "dev"
26912
26990
  }
26913
26991
  });
26914
26992
  } catch (err) {
@@ -27589,8 +27667,8 @@ function registerTelemetryCommands(program2) {
27589
27667
  }
27590
27668
 
27591
27669
  // src/cli.ts
27592
- program.name("verity").description("CLI for Verity quality gate service").version("0.32.4").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
27593
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.4");
27670
+ program.name("verity").description("CLI for Verity quality gate service").version("0.32.5").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) => {
27671
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.5");
27594
27672
  setUserNamedServiceUrl(program.opts().serviceUrl);
27595
27673
  try {
27596
27674
  await foldLegacyLocalCredential();
@@ -23,6 +23,17 @@ happens by accident. So **you** ask, in the session, where the user actually is.
23
23
 
24
24
  ---
25
25
 
26
+ ## Step 0: Check the CLI before asking anything
27
+
28
+ If this session's context says the `verity` on PATH is a different version from
29
+ the plugin's, **tell the user that first**, in one line, before the questions —
30
+ with the `npm install -g` command the context gives. Finding out after they have
31
+ answered is the failure this step exists to prevent: an older CLI cannot take the
32
+ answers.
33
+
34
+ Then run every command below the way the context says (the plugin's own copy),
35
+ not with plain `verity`.
36
+
26
37
  ## Step 1: Ask both questions in ONE prompt
27
38
 
28
39
  Use `AskUserQuestion` with **both** questions in a single call. Do not ask them
@@ -33,12 +44,14 @@ setup gets, so spend it once.
33
44
 
34
45
  | Option | Description |
35
46
  |---|---|
36
- | `After every turn (recommended)` | Reviews what the agent just wrote, as you work. Fast feedback, never blocks a commit. |
47
+ | `After every turn (always on with the plugin, for now)` | Reviews what the agent just wrote, as you work. Fast feedback, never blocks a commit. Under the Claude Code plugin it cannot be turned off yet. |
37
48
  | `Before commit` | Reviews the staged diff and blocks the commit on a failing review. |
38
49
  | `Before push / PR` | Reviews the commits about to be pushed and blocks on a failing review. |
39
50
 
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**.
51
+ Pre-select **After every turn**, and do not call it "recommended": under the
52
+ plugin it runs whatever the answer, and a user who unticks it should not find
53
+ that out afterwards. Say plainly in the question text that choosing only the
54
+ first means **git commits and pushes are not gated**.
42
55
 
43
56
  **Question 2 — "How deeply should Verity review?"** (single-select)
44
57
 
@@ -60,6 +73,10 @@ verity init --yes --no-setup --moments <stop[,pre-commit][,pre-push]> --intensit
60
73
  `--moments none` is the deliberate way to gate nothing; an unrecognised value is
61
74
  refused rather than dropped, so a typo cannot quietly remove a gate.
62
75
 
76
+ Pass `stop` only if they ticked it. Under the plugin the Stop review runs either
77
+ way and `verity init` says so; recording their real answer is what lets it be
78
+ honoured once Stop can be turned off.
79
+
63
80
  This is idempotent and does the whole job: removes the project's own
64
81
  `.claude/skills/verity-*` copies that the plugin supersedes (they are why the
65
82
  slash-command list shows every skill twice), stands down duplicate
@@ -91,6 +108,11 @@ Say this, and stop:
91
108
 
92
109
  If `verity status` already reports an account, skip this step and say so.
93
110
 
111
+ If Step 0 found a version mismatch, the `verity` in their terminal is the old
112
+ global one, not the plugin's: tell them to update it first (the `npm install -g`
113
+ command from Step 0), then log in. An older or experimental build can sign the
114
+ machine in to a different server, and the plugin follows that sign-in.
115
+
94
116
  ## Step 4: Offer the part a lookup cannot produce
95
117
 
96
118
  The Standard `init` derives covers the research-backed patterns for the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.32.4",
3
+ "version": "0.32.5",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",
@@ -23,14 +23,14 @@
23
23
  },
24
24
  "scripts": {
25
25
  "build": "node scripts/build.js",
26
- "test": "node --import tsx --test $(find tests -name '*.test.ts' -not -path 'tests/e2e/*' | sort)",
26
+ "test": "npm run build && VERITY_TEST_PREBUILT=1 node --import tsx --test $(find tests -name '*.test.ts' -not -path 'tests/e2e/*' | sort)",
27
27
  "test:e2e": "node --import tsx --test --test-concurrency=1 tests/e2e/*.test.ts",
28
28
  "typecheck": "tsc --noEmit",
29
29
  "prepublishOnly": "npm run typecheck && npm run build"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^20",
33
- "esbuild": "^0.21",
33
+ "esbuild": "^0.25.12",
34
34
  "tsx": "^4.21.0",
35
35
  "typescript": "^5.4"
36
36
  },