@codacy/verity-cli 0.20.1 → 0.21.0

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/README.md CHANGED
@@ -24,13 +24,25 @@ source ~/.zshrc
24
24
  npm install -g @codacy/verity-cli
25
25
  ```
26
26
 
27
- ## Upgrade to v0.16.0
27
+ ## Upgrade
28
28
 
29
29
  ```bash
30
30
  npm install -g @codacy/verity-cli@latest
31
31
  ```
32
32
 
33
- Existing projects work without changes. New compound features (knowledge extraction, graph memory, pre-work injection) activate automatically. Run `/verity-setup` again to add the CLAUDE.md knowledge base pointer and reflection instructions.
33
+ Existing projects keep working new compound features (knowledge extraction, graph memory, pre-work injection) activate automatically.
34
+
35
+ ### Coming from GATE.md (`@codacy/gate-cli`)?
36
+
37
+ GATE.md is now Verity. One-time switch:
38
+
39
+ ```bash
40
+ npm rm -g @codacy/gate-cli
41
+ npm install -g @codacy/verity-cli
42
+ verity init # per project — moves .gate→.verity, keeps your token, Standard & memory
43
+ ```
44
+
45
+ No re-setup needed: your token, Standard, and run history all carry over.
34
46
 
35
47
  ## Quick reference
36
48
 
package/bin/verity.js CHANGED
@@ -14824,6 +14824,7 @@ async function runMigration(opts = {}) {
14824
14824
  migrateGlobalCredentials(home, actions);
14825
14825
  await migrateLegacyHooks(root, actions);
14826
14826
  await migrateClaudeMd(root, actions);
14827
+ migrateStandardFile(root, actions);
14827
14828
  removeLegacyPackage(movedProjectDir, npmRemover, actions);
14828
14829
  return { actions, migrated: actions.length > 0 };
14829
14830
  }
@@ -14919,6 +14920,24 @@ async function migrateClaudeMd(root, actions) {
14919
14920
  printWarn(`Could not rewrite CLAUDE.md block: ${err.message}`);
14920
14921
  }
14921
14922
  }
14923
+ function migrateStandardFile(root, actions) {
14924
+ const gateMd = (0, import_node_path14.join)(root, "GATE.md");
14925
+ const verityMd = (0, import_node_path14.join)(root, "VERITY.md");
14926
+ if (!(0, import_node_fs20.existsSync)(gateMd) || (0, import_node_fs20.existsSync)(verityMd)) return;
14927
+ let moved = false;
14928
+ if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
14929
+ try {
14930
+ (0, import_node_child_process8.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
14931
+ moved = true;
14932
+ } catch {
14933
+ }
14934
+ }
14935
+ if (!moved) moveFile(gateMd, verityMd);
14936
+ const content = readFileSyncSafe(verityMd);
14937
+ const refreshed = content.split("GATE.md").join("VERITY.md");
14938
+ if (refreshed !== content) (0, import_node_fs20.writeFileSync)(verityMd, refreshed);
14939
+ actions.push("Renamed GATE.md \u2192 VERITY.md");
14940
+ }
14922
14941
  function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
14923
14942
  if (!movedProjectDir) return;
14924
14943
  try {
@@ -15024,6 +15043,9 @@ async function needsMigration(root = repoRoot()) {
15024
15043
  if ((0, import_node_fs20.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
15025
15044
  return true;
15026
15045
  }
15046
+ if ((0, import_node_fs20.existsSync)((0, import_node_path14.join)(root, "GATE.md")) && !(0, import_node_fs20.existsSync)((0, import_node_path14.join)(root, "VERITY.md"))) {
15047
+ return true;
15048
+ }
15027
15049
  if (await hasLegacyHooksAt(root)) return true;
15028
15050
  return false;
15029
15051
  }
@@ -15778,8 +15800,143 @@ function registerRunCommand(program2) {
15778
15800
  });
15779
15801
  }
15780
15802
 
15803
+ // src/lib/telemetry.ts
15804
+ var import_promises13 = require("node:fs/promises");
15805
+ var import_node_path18 = require("node:path");
15806
+ var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
15807
+ var GITIGNORE_FILE = ".gitignore";
15808
+ var GITIGNORE_ENTRY = ".claude/settings.local.json";
15809
+ function deriveOtlpEndpoint(serviceUrl) {
15810
+ return serviceUrl.replace(/\/+$/, "").replace(/\/v1$/, "") + "/v1/otlp";
15811
+ }
15812
+ function buildTelemetryEnv(serviceUrl, token) {
15813
+ return {
15814
+ CLAUDE_CODE_ENABLE_TELEMETRY: "1",
15815
+ OTEL_METRICS_EXPORTER: "otlp",
15816
+ OTEL_TRACES_EXPORTER: "otlp",
15817
+ CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
15818
+ OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
15819
+ OTEL_EXPORTER_OTLP_ENDPOINT: deriveOtlpEndpoint(serviceUrl),
15820
+ // <token> is whatever is in .verity/credentials — verity_ OR legacy gate_.
15821
+ // The server hashes the raw token regardless of prefix.
15822
+ OTEL_EXPORTER_OTLP_HEADERS: `Authorization=Bearer ${token}`,
15823
+ OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: "delta",
15824
+ OTEL_LOG_USER_PROMPTS: "0",
15825
+ OTEL_LOG_TOOL_DETAILS: "0",
15826
+ OTEL_LOG_TOOL_CONTENT: "0",
15827
+ OTEL_METRICS_INCLUDE_SESSION_ID: "true",
15828
+ OTEL_METRICS_INCLUDE_ACCOUNT_UUID: "false",
15829
+ OTEL_METRICS_INCLUDE_VERSION: "false",
15830
+ OTEL_METRIC_EXPORT_INTERVAL: "60000"
15831
+ };
15832
+ }
15833
+ var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv("", ""));
15834
+ async function readSettingsLocal() {
15835
+ try {
15836
+ return JSON.parse(await (0, import_promises13.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
15837
+ } catch {
15838
+ return {};
15839
+ }
15840
+ }
15841
+ async function writeSettingsLocal(settings) {
15842
+ const file = projectPath(SETTINGS_LOCAL_FILE2);
15843
+ await (0, import_promises13.mkdir)((0, import_node_path18.dirname)(file), { recursive: true });
15844
+ await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
15845
+ }
15846
+ async function ensureGitignore() {
15847
+ const file = projectPath(GITIGNORE_FILE);
15848
+ let content = "";
15849
+ try {
15850
+ content = await (0, import_promises13.readFile)(file, "utf-8");
15851
+ } catch {
15852
+ }
15853
+ const lines = content.split("\n").map((l) => l.trim());
15854
+ if (lines.includes(GITIGNORE_ENTRY) || lines.includes(".claude/") || lines.includes(".claude")) {
15855
+ return;
15856
+ }
15857
+ const block = "# Verity telemetry \u2014 holds your project token\n" + GITIGNORE_ENTRY + "\n";
15858
+ const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
15859
+ await (0, import_promises13.writeFile)(file, next);
15860
+ }
15861
+ async function installTelemetry(serviceUrl, token) {
15862
+ const env = buildTelemetryEnv(serviceUrl, token);
15863
+ const settings = await readSettingsLocal();
15864
+ settings.env = { ...settings.env ?? {}, ...env };
15865
+ await writeSettingsLocal(settings);
15866
+ await ensureGitignore();
15867
+ return { ok: true, data: { endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT } };
15868
+ }
15869
+ async function checkTelemetry() {
15870
+ const env = (await readSettingsLocal()).env ?? {};
15871
+ const enabled = env.CLAUDE_CODE_ENABLE_TELEMETRY === "1" && !!env.OTEL_EXPORTER_OTLP_ENDPOINT;
15872
+ return { enabled, endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? null, settingsPath: SETTINGS_LOCAL_FILE2 };
15873
+ }
15874
+ async function uninstallTelemetry() {
15875
+ const settings = await readSettingsLocal();
15876
+ if (!settings.env) return { ok: true, data: { removed: 0 } };
15877
+ let removed = 0;
15878
+ for (const key of VERITY_TELEMETRY_KEYS) {
15879
+ if (key in settings.env) {
15880
+ delete settings.env[key];
15881
+ removed++;
15882
+ }
15883
+ }
15884
+ if (Object.keys(settings.env).length === 0) delete settings.env;
15885
+ await writeSettingsLocal(settings);
15886
+ return { ok: true, data: { removed } };
15887
+ }
15888
+
15889
+ // src/commands/telemetry.ts
15890
+ function registerTelemetryCommands(program2) {
15891
+ const telemetry = program2.command("telemetry").description("Manage Claude Code OpenTelemetry export to Verity (cost & usage)");
15892
+ telemetry.command("install").description("Enable Claude Code telemetry export to Verity (writes .claude/settings.local.json)").action(async () => {
15893
+ const globals = program2.opts();
15894
+ const tokenResult = await resolveToken(globals.token);
15895
+ if (!tokenResult.ok) {
15896
+ printError(tokenResult.error);
15897
+ process.exit(1);
15898
+ }
15899
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
15900
+ if (!urlResult.ok) {
15901
+ printError(urlResult.error);
15902
+ process.exit(1);
15903
+ }
15904
+ const result = await installTelemetry(urlResult.data, tokenResult.data.token);
15905
+ if (!result.ok) {
15906
+ printError(result.error);
15907
+ process.exit(1);
15908
+ }
15909
+ printInfo(`Telemetry enabled \u2192 ${result.data.endpoint}`);
15910
+ printInfo(` wrote ${SETTINGS_LOCAL_FILE2} (gitignored \u2014 it holds your project token)`);
15911
+ printInfo(" takes effect on your NEXT Claude Code session; first metrics appear within ~60s of activity");
15912
+ printInfo(" view cost & usage at /usage");
15913
+ });
15914
+ telemetry.command("check").description("Show whether Claude Code telemetry export to Verity is enabled").option("--json", "Output status as JSON").action(async (opts) => {
15915
+ const status = await checkTelemetry();
15916
+ if (opts.json) {
15917
+ printJson(status);
15918
+ return;
15919
+ }
15920
+ if (status.enabled) {
15921
+ printInfo(`Telemetry: enabled \u2192 ${status.endpoint}`);
15922
+ } else {
15923
+ printWarn('Telemetry: disabled. Run "verity telemetry install" to enable cost & usage tracking.');
15924
+ }
15925
+ });
15926
+ telemetry.command("uninstall").description("Disable Claude Code telemetry export to Verity").action(async () => {
15927
+ const result = await uninstallTelemetry();
15928
+ if (!result.ok) {
15929
+ printError(result.error);
15930
+ process.exit(1);
15931
+ }
15932
+ printInfo(
15933
+ result.data.removed > 0 ? `Telemetry disabled (${result.data.removed} keys removed from ${SETTINGS_LOCAL_FILE2})` : "Telemetry was not enabled \u2014 nothing to remove."
15934
+ );
15935
+ });
15936
+ }
15937
+
15781
15938
  // src/cli.ts
15782
- program.name("verity").description("CLI for Verity quality gate service").version("0.20.1").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
15939
+ program.name("verity").description("CLI for Verity quality gate service").version("0.21.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
15783
15940
  registerAuthCommands(program);
15784
15941
  registerHooksCommands(program);
15785
15942
  registerIntentCommands(program);
@@ -15797,4 +15954,5 @@ registerReflectCommand(program);
15797
15954
  registerMemoryCommand(program);
15798
15955
  registerRunCommand(program);
15799
15956
  registerMigrateCommand(program);
15957
+ registerTelemetryCommands(program);
15800
15958
  program.parse();
@@ -75,6 +75,36 @@ Default to `balanced` if the user says "default" or doesn't specify a preference
75
75
 
76
76
  ---
77
77
 
78
+ ## Step 3b: Ask about cost & usage telemetry (opt-in)
79
+
80
+ Cost & usage observability is **opt-in** and **required** for the `/usage` dashboard — all
81
+ cost and token data comes from Claude Code's own OpenTelemetry export, so with telemetry off
82
+ the dashboard stays empty.
83
+
84
+ First check the current state (so you don't re-ask if it's already on):
85
+
86
+ ```bash
87
+ verity telemetry check
88
+ ```
89
+
90
+ - If it reports **enabled**, don't re-ask — tell the user telemetry is already on and continue
91
+ (mention `verity telemetry uninstall` only if they want to turn it off).
92
+ - If **disabled**, use the **AskUserQuestion** tool:
93
+
94
+ > **Enable Claude Code cost & usage telemetry for this project?**
95
+ > Verity can show cost, tokens, a per-agent / per-session / model breakdown, and a fleet cost
96
+ > tree — by receiving Claude Code's built-in **OpenTelemetry** export. This sends usage
97
+ > **metrics and traces only** (model names, token counts, USD cost, agent types, session IDs).
98
+ > It does **NOT** send your prompts, code, or tool input/output. Without it, the `/usage`
99
+ > dashboard stays empty.
100
+ > - **Yes** (recommended) — enable telemetry
101
+ > - **No** — skip (you can enable later with `verity telemetry install`)
102
+
103
+ Remember the answer; you act on it in Step 7b (the token must exist first). **Declining is
104
+ fine and reversible** — nothing is written, and the next `/verity-setup` re-offers it.
105
+
106
+ ---
107
+
78
108
  ## Step 4: Synthesize the Standard
79
109
 
80
110
  Read `.claude/skills/verity-setup/patterns-reference.yaml` and `.claude/skills/verity-setup/standard-template.yaml` from the project root.
@@ -301,6 +331,25 @@ This derives a small set of descriptive memory nodes from what you already analy
301
331
 
302
332
  ---
303
333
 
334
+ ## Step 7b: Enable telemetry (only if the user opted in at Step 3b)
335
+
336
+ If — and only if — the user said **Yes** in Step 3b, enable the Claude Code telemetry export
337
+ now (the token from Step 6 must already exist):
338
+
339
+ ```bash
340
+ verity telemetry install
341
+ ```
342
+
343
+ This writes the `OTEL_*` env block to the gitignored `.claude/settings.local.json` (which
344
+ holds your token) and points Claude Code's OpenTelemetry exporter at Verity's OTLP endpoint.
345
+ Tell the user it takes effect on their **next** Claude Code session, that first metrics appear
346
+ within ~60s of activity, and point them at `/usage`.
347
+
348
+ If the user declined, skip this step and note that `/usage` will stay empty until they run
349
+ `verity telemetry install`.
350
+
351
+ ---
352
+
304
353
  ## Step 8: Generate VERITY.md file
305
354
 
306
355
  Create `VERITY.md` at the project root with this content:
@@ -404,8 +453,12 @@ Add these entries to `.gitignore` (create it if it doesn't exist, append if it d
404
453
  .verity/.last-intent
405
454
  .verity/.memory-sync-state.json
406
455
  .verity/memory/log.md
456
+ .claude/settings.local.json
407
457
  ```
408
458
 
459
+ `.claude/settings.local.json` holds the telemetry env block **including your token**, so it
460
+ must never be committed (`verity telemetry install` also adds this entry automatically).
461
+
409
462
  Do NOT gitignore `.verity/standard.yaml` or `VERITY.md` — those should be committed.
410
463
 
411
464
  **Commit the knowledge graph, but not its log.** The nodes under `.verity/memory/<domain>/` and `.verity/memory/index.md` are durable project knowledge meant to be committed and reviewed. But `.verity/memory/log.md` is an append-only, per-run timestamped activity log — it churns on every analysis and carries no reviewable content, so it is gitignored above. If a project already committed it, untrack it once with `git rm --cached .verity/memory/log.md`.
@@ -431,12 +484,14 @@ Standard: v1 (4 quality, 7 security, N custom patterns)
431
484
  Tools: ${TOOL_LIST}
432
485
  Service: registered (project_id: ${PROJECT_ID})
433
486
  Hooks: installed (verity analyze + verity intent capture)
487
+ Telemetry: ${TELEMETRY_STATUS} (enabled → cost+usage at /usage, or disabled)
434
488
 
435
489
  Files created:
436
490
  .verity/standard.yaml — Quality standard
437
491
  .codacy/codacy.config.json — Analysis CLI config
438
492
  VERITY.md — Project quality overview
439
493
  .claude/settings.json — Hook configuration (verified)
494
+ .claude/settings.local.json — Telemetry env (only if enabled; gitignored)
440
495
  .gitignore — Updated with Verity entries
441
496
 
442
497
  Next: You should now be seeing the first analysis happening below. The hook will fire automatically every time your agent stops working on something.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "homepage": "https://verity.md",
6
6
  "repository": {