@codacy/verity-cli 0.20.2 → 0.22.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
@@ -14107,7 +14107,7 @@ async function runSeed(opts) {
14107
14107
  var MAX_ASSISTANT_RESPONSE_CHARS_PLAN = 32768;
14108
14108
  var MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT = 8e3;
14109
14109
  async function readStopHookStdin() {
14110
- const empty = { assistantMessage: null, stopReason: null, transcriptPath: null };
14110
+ const empty = { assistantMessage: null, stopReason: null, transcriptPath: null, sessionId: null };
14111
14111
  try {
14112
14112
  if (process.stdin.isTTY) return empty;
14113
14113
  const chunks = [];
@@ -14125,7 +14125,8 @@ async function readStopHookStdin() {
14125
14125
  resolve({
14126
14126
  assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
14127
14127
  stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
14128
- transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null
14128
+ transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null,
14129
+ sessionId: typeof data.session_id === "string" ? data.session_id : null
14129
14130
  });
14130
14131
  } catch {
14131
14132
  resolve(empty);
@@ -14158,7 +14159,7 @@ async function runAnalyze(opts, globals) {
14158
14159
  process.chdir(repoRoot());
14159
14160
  } catch {
14160
14161
  }
14161
- const { assistantMessage: assistantResponse, stopReason, transcriptPath } = await readStopHookStdin();
14162
+ const { assistantMessage: assistantResponse, stopReason, transcriptPath, sessionId } = await readStopHookStdin();
14162
14163
  const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
14163
14164
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
14164
14165
  const analyzable = filterAnalyzable(allChanged);
@@ -14191,7 +14192,7 @@ async function runAnalyze(opts, globals) {
14191
14192
  if (!urlResult.ok) {
14192
14193
  passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
14193
14194
  }
14194
- const sessionIdForMemory = process.env.CLAUDE_SESSION_ID ?? "";
14195
+ const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
14195
14196
  let contextFilePaths = [];
14196
14197
  let predictedMode;
14197
14198
  try {
@@ -14389,7 +14390,7 @@ async function runAnalyze(opts, globals) {
14389
14390
  context: {
14390
14391
  agent_model: process.env.CLAUDE_MODEL ?? "unknown",
14391
14392
  agent_harness: "claude-code",
14392
- session_id: process.env.CLAUDE_SESSION_ID ?? "unknown",
14393
+ session_id: sessionId || process.env.CLAUDE_SESSION_ID || "unknown",
14393
14394
  iteration
14394
14395
  },
14395
14396
  ...memoryManifest && { memory_manifest: memoryManifest },
@@ -15800,8 +15801,143 @@ function registerRunCommand(program2) {
15800
15801
  });
15801
15802
  }
15802
15803
 
15804
+ // src/lib/telemetry.ts
15805
+ var import_promises13 = require("node:fs/promises");
15806
+ var import_node_path18 = require("node:path");
15807
+ var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
15808
+ var GITIGNORE_FILE = ".gitignore";
15809
+ var GITIGNORE_ENTRY = ".claude/settings.local.json";
15810
+ function deriveOtlpEndpoint(serviceUrl) {
15811
+ return serviceUrl.replace(/\/+$/, "").replace(/\/v1$/, "") + "/v1/otlp";
15812
+ }
15813
+ function buildTelemetryEnv(serviceUrl, token) {
15814
+ return {
15815
+ CLAUDE_CODE_ENABLE_TELEMETRY: "1",
15816
+ OTEL_METRICS_EXPORTER: "otlp",
15817
+ OTEL_TRACES_EXPORTER: "otlp",
15818
+ CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
15819
+ OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
15820
+ OTEL_EXPORTER_OTLP_ENDPOINT: deriveOtlpEndpoint(serviceUrl),
15821
+ // <token> is whatever is in .verity/credentials — verity_ OR legacy gate_.
15822
+ // The server hashes the raw token regardless of prefix.
15823
+ OTEL_EXPORTER_OTLP_HEADERS: `Authorization=Bearer ${token}`,
15824
+ OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: "delta",
15825
+ OTEL_LOG_USER_PROMPTS: "0",
15826
+ OTEL_LOG_TOOL_DETAILS: "0",
15827
+ OTEL_LOG_TOOL_CONTENT: "0",
15828
+ OTEL_METRICS_INCLUDE_SESSION_ID: "true",
15829
+ OTEL_METRICS_INCLUDE_ACCOUNT_UUID: "false",
15830
+ OTEL_METRICS_INCLUDE_VERSION: "false",
15831
+ OTEL_METRIC_EXPORT_INTERVAL: "60000"
15832
+ };
15833
+ }
15834
+ var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv("", ""));
15835
+ async function readSettingsLocal() {
15836
+ try {
15837
+ return JSON.parse(await (0, import_promises13.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
15838
+ } catch {
15839
+ return {};
15840
+ }
15841
+ }
15842
+ async function writeSettingsLocal(settings) {
15843
+ const file = projectPath(SETTINGS_LOCAL_FILE2);
15844
+ await (0, import_promises13.mkdir)((0, import_node_path18.dirname)(file), { recursive: true });
15845
+ await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
15846
+ }
15847
+ async function ensureGitignore() {
15848
+ const file = projectPath(GITIGNORE_FILE);
15849
+ let content = "";
15850
+ try {
15851
+ content = await (0, import_promises13.readFile)(file, "utf-8");
15852
+ } catch {
15853
+ }
15854
+ const lines = content.split("\n").map((l) => l.trim());
15855
+ if (lines.includes(GITIGNORE_ENTRY) || lines.includes(".claude/") || lines.includes(".claude")) {
15856
+ return;
15857
+ }
15858
+ const block = "# Verity telemetry \u2014 holds your project token\n" + GITIGNORE_ENTRY + "\n";
15859
+ const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
15860
+ await (0, import_promises13.writeFile)(file, next);
15861
+ }
15862
+ async function installTelemetry(serviceUrl, token) {
15863
+ const env = buildTelemetryEnv(serviceUrl, token);
15864
+ const settings = await readSettingsLocal();
15865
+ settings.env = { ...settings.env ?? {}, ...env };
15866
+ await writeSettingsLocal(settings);
15867
+ await ensureGitignore();
15868
+ return { ok: true, data: { endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT } };
15869
+ }
15870
+ async function checkTelemetry() {
15871
+ const env = (await readSettingsLocal()).env ?? {};
15872
+ const enabled = env.CLAUDE_CODE_ENABLE_TELEMETRY === "1" && !!env.OTEL_EXPORTER_OTLP_ENDPOINT;
15873
+ return { enabled, endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? null, settingsPath: SETTINGS_LOCAL_FILE2 };
15874
+ }
15875
+ async function uninstallTelemetry() {
15876
+ const settings = await readSettingsLocal();
15877
+ if (!settings.env) return { ok: true, data: { removed: 0 } };
15878
+ let removed = 0;
15879
+ for (const key of VERITY_TELEMETRY_KEYS) {
15880
+ if (key in settings.env) {
15881
+ delete settings.env[key];
15882
+ removed++;
15883
+ }
15884
+ }
15885
+ if (Object.keys(settings.env).length === 0) delete settings.env;
15886
+ await writeSettingsLocal(settings);
15887
+ return { ok: true, data: { removed } };
15888
+ }
15889
+
15890
+ // src/commands/telemetry.ts
15891
+ function registerTelemetryCommands(program2) {
15892
+ const telemetry = program2.command("telemetry").description("Manage Claude Code OpenTelemetry export to Verity (cost & usage)");
15893
+ telemetry.command("install").description("Enable Claude Code telemetry export to Verity (writes .claude/settings.local.json)").action(async () => {
15894
+ const globals = program2.opts();
15895
+ const tokenResult = await resolveToken(globals.token);
15896
+ if (!tokenResult.ok) {
15897
+ printError(tokenResult.error);
15898
+ process.exit(1);
15899
+ }
15900
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
15901
+ if (!urlResult.ok) {
15902
+ printError(urlResult.error);
15903
+ process.exit(1);
15904
+ }
15905
+ const result = await installTelemetry(urlResult.data, tokenResult.data.token);
15906
+ if (!result.ok) {
15907
+ printError(result.error);
15908
+ process.exit(1);
15909
+ }
15910
+ printInfo(`Telemetry enabled \u2192 ${result.data.endpoint}`);
15911
+ printInfo(` wrote ${SETTINGS_LOCAL_FILE2} (gitignored \u2014 it holds your project token)`);
15912
+ printInfo(" takes effect on your NEXT Claude Code session; first metrics appear within ~60s of activity");
15913
+ printInfo(" view cost & usage at /usage");
15914
+ });
15915
+ telemetry.command("check").description("Show whether Claude Code telemetry export to Verity is enabled").option("--json", "Output status as JSON").action(async (opts) => {
15916
+ const status = await checkTelemetry();
15917
+ if (opts.json) {
15918
+ printJson(status);
15919
+ return;
15920
+ }
15921
+ if (status.enabled) {
15922
+ printInfo(`Telemetry: enabled \u2192 ${status.endpoint}`);
15923
+ } else {
15924
+ printWarn('Telemetry: disabled. Run "verity telemetry install" to enable cost & usage tracking.');
15925
+ }
15926
+ });
15927
+ telemetry.command("uninstall").description("Disable Claude Code telemetry export to Verity").action(async () => {
15928
+ const result = await uninstallTelemetry();
15929
+ if (!result.ok) {
15930
+ printError(result.error);
15931
+ process.exit(1);
15932
+ }
15933
+ printInfo(
15934
+ result.data.removed > 0 ? `Telemetry disabled (${result.data.removed} keys removed from ${SETTINGS_LOCAL_FILE2})` : "Telemetry was not enabled \u2014 nothing to remove."
15935
+ );
15936
+ });
15937
+ }
15938
+
15803
15939
  // src/cli.ts
15804
- program.name("verity").description("CLI for Verity quality gate service").version("0.20.2").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
15940
+ program.name("verity").description("CLI for Verity quality gate service").version("0.22.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
15805
15941
  registerAuthCommands(program);
15806
15942
  registerHooksCommands(program);
15807
15943
  registerIntentCommands(program);
@@ -15819,4 +15955,5 @@ registerReflectCommand(program);
15819
15955
  registerMemoryCommand(program);
15820
15956
  registerRunCommand(program);
15821
15957
  registerMigrateCommand(program);
15958
+ registerTelemetryCommands(program);
15822
15959
  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.2",
3
+ "version": "0.22.0",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "homepage": "https://verity.md",
6
6
  "repository": {