@osovv/vv-opencode 1.2.11 → 1.3.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +27 -2
  3. package/dist/cli.js +4 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/analytics.d.ts +58 -0
  6. package/dist/commands/analytics.js +220 -0
  7. package/dist/commands/analytics.js.map +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +6 -5
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/analytics/metrics.d.ts +33 -0
  12. package/dist/lib/analytics/metrics.js +162 -0
  13. package/dist/lib/analytics/metrics.js.map +1 -0
  14. package/dist/lib/analytics/store.d.ts +20 -0
  15. package/dist/lib/analytics/store.js +81 -0
  16. package/dist/lib/analytics/store.js.map +1 -0
  17. package/dist/lib/analytics/types.d.ts +67 -0
  18. package/dist/lib/analytics/types.js +26 -0
  19. package/dist/lib/analytics/types.js.map +1 -0
  20. package/dist/lib/managed-skills.d.ts +1 -1
  21. package/dist/lib/managed-skills.js +3 -2
  22. package/dist/lib/managed-skills.js.map +1 -1
  23. package/dist/lib/opencode.d.ts +2 -0
  24. package/dist/lib/opencode.js +4 -2
  25. package/dist/lib/opencode.js.map +1 -1
  26. package/dist/lib/plugin-toggle-config.d.ts +1 -1
  27. package/dist/lib/plugin-toggle-config.js +2 -1
  28. package/dist/lib/plugin-toggle-config.js.map +1 -1
  29. package/dist/plugins/analytics/index.d.ts +18 -0
  30. package/dist/plugins/analytics/index.js +151 -0
  31. package/dist/plugins/analytics/index.js.map +1 -0
  32. package/dist/tui/analytics/indicator.d.ts +40 -0
  33. package/dist/tui/analytics/indicator.js +117 -0
  34. package/dist/tui/analytics/indicator.js.map +1 -0
  35. package/dist/tui/branding/footer.d.ts +14 -0
  36. package/dist/tui/branding/footer.js +34 -0
  37. package/dist/tui/branding/footer.js.map +1 -0
  38. package/dist/tui.js +25 -7
  39. package/dist/tui.js.map +1 -1
  40. package/package.json +6 -2
  41. package/schemas/vvoc/v3.json +1 -1
  42. package/templates/skills/vvoc-usage-analytics/SKILL.md +58 -0
  43. package/templates/skills/vvoc-usage-analytics/references/opencode-db-queries.md +113 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 1.3.0 (2026-08-19)
2
+
3
+ ### Summary
4
+
5
+ Version 1.3.0 introduces a new analytics system to help users understand and optimize token usage and prompt-cache effectiveness. A new AnalyticsPlugin collects per-step token and cache telemetry with vvoc and OpenCode version attribution into local-only JSONL files, the new `vvoc analytics cache-hit-rate` command aggregates and compares cache hit rates by day, week, month, model, provider, project, session, or vvoc/OpenCode version with date/project filters and JSON output, and the TUI gains a live per-session cache percentage indicator plus a vvoc version footer. A new vvoc-usage-analytics managed skill lets users ask usage, cache, and cost questions conversationally during a session, including historical comparisons from opencode.db that predate the plugin. All telemetry stays on the machine, and analytics collection can be disabled via the plugin toggle.
6
+
7
+ * feat(analytics): add analytics types, JSONL store, metrics, and version helper export ([a709547](https://github.com/osovv/vv-opencode/commit/a709547))
8
+ * feat(analytics): add cache-hit-rate CLI command with grouping and JSON output ([8d0e70f](https://github.com/osovv/vv-opencode/commit/8d0e70f))
9
+ * feat(analytics): add live cache indicator and vvoc version footer for the TUI ([8098f10](https://github.com/osovv/vv-opencode/commit/8098f10))
10
+ * feat(analytics): add server plugin collecting step-finish telemetry with version attribution ([9db2cb3](https://github.com/osovv/vv-opencode/commit/9db2cb3))
11
+ * feat(analytics): register analytics toggle, CLI command, and TUI wiring ([e54d4b0](https://github.com/osovv/vv-opencode/commit/e54d4b0))
12
+ * feat(skills): add vvoc-usage-analytics managed skill with read-only opencode.db queries ([d61cda0](https://github.com/osovv/vv-opencode/commit/d61cda0))
13
+ * docs(analytics): document cache hit rate analytics in README and stabilize CLI exit-code tests ([8519eab](https://github.com/osovv/vv-opencode/commit/8519eab))
14
+
1
15
  ## <small>1.2.11 (2026-08-18)</small>
2
16
 
3
17
  ### Summary
package/README.md CHANGED
@@ -166,10 +166,11 @@ OpenCode is a strong, flexible base for agentic coding, but it intentionally lea
166
166
  | **Workflow Tracking** | Replace free-form multi-agent chaos with explicit work items, bounded review rounds, reviewer result collection, and hard stops when more context is needed |
167
167
  | **Unified Web Tools** | Replace provider-specific search and reader schemas with the canonical `web_search` and `web_fetch` tools, configurable for Exa, Brave, native retrieval, or Spider extraction |
168
168
  | **Context Inspector** | Run `/context` in an active OpenCode TUI session for Overview, Tools, and MCP tabs with provider-reported usage, approximate context-window percentages, active post-compaction tool history, and deterministic source attribution |
169
+ | **Cache Analytics** | Watch a live per-session `cache NN%` indicator in the TUI and compare cache hit rates across vvoc releases, OpenCode versions, models, and projects with `vvoc analytics cache-hit-rate` |
169
170
 
170
171
  ---
171
172
 
172
- ## The Nine Plugins
173
+ ## The Ten Plugins
173
174
 
174
175
  | Plugin | What it helps you do |
175
176
  |---|---|
@@ -182,6 +183,7 @@ OpenCode is a strong, flexible base for agentic coding, but it intentionally lea
182
183
  | **WebToolsPlugin** | Register the provider-neutral `web_search` and `web_fetch` tools, return direct image/PDF attachments, and hide OpenCode's built-in web tools at runtime unless the user explicitly configured their permissions. |
183
184
  | **ContextTuiPlugin** | Add a native scrollable `/context` dialog with measured usage plus detailed observable per-tool and per-MCP schema/history estimates, explicitly marking data that OpenCode does not expose. |
184
185
  | **ToolHistoryCompactionPlugin** | Shrink the replayed conversation context non-destructively by compacting old tool outputs in the model replay (old reads to `[Read <file>, lines X-Y]`, over-budget ephemeral outputs pruned), while retaining web/search/skill knowledge results. |
186
+ | **AnalyticsPlugin** | Persist per-step token and cache telemetry with vvoc/OpenCode version attribution, show a live `cache NN%` indicator next to the session prompt plus a `vvoc vX.Y.Z` sidebar footer, and answer "did my cache optimizations help?" via `vvoc analytics cache-hit-rate`. |
185
187
 
186
188
  Workflow work items are opened with explicit intent. For implementation loops, controllers use:
187
189
 
@@ -259,6 +261,26 @@ Config lives in `vvoc.json` under `plugins["tool-history-compaction"]` (boolean
259
261
 
260
262
  Set `outputMaxChars` to `0` to disable pruning, or `"enabled": false` to disable the plugin entirely. Changes require an OpenCode restart.
261
263
 
264
+ ### Cache Hit Rate Analytics
265
+
266
+ `AnalyticsPlugin` records one line per completed model step — fresh input, cache read, cache write, output, reasoning, recorded cost — to `$XDG_DATA_HOME/vvoc/analytics/usage-YYYY-MM.jsonl`, attributed with the vvoc version, the OpenCode version (from session telemetry), project, provider, model, and agent. Telemetry never leaves the machine; disable collection with `"plugins": { "analytics": false }` and delete old monthly files freely.
267
+
268
+ In the TUI you get a live `cache NN%` indicator next to the session prompt (green at 80%+, yellow at 50%+, red below, muted `n/a` before the first cache-eligible step) and a `vvoc vX.Y.Z` label in the sidebar footer. The indicator is per-session and computed in memory.
269
+
270
+ Retrospective analysis lives in the CLI:
271
+
272
+ ```bash
273
+ vvoc analytics cache-hit-rate --group-by day # daily trend
274
+ vvoc analytics cache-hit-rate --group-by vvoc --since 30d # compare vvoc releases
275
+ vvoc analytics cache-hit-rate --group-by opencode --since 30d # compare OpenCode upgrades
276
+ vvoc analytics cache-hit-rate --group-by session|model|provider|project|week|month
277
+ vvoc analytics cache-hit-rate --project my-repo --order hit-rate --limit 10 --json
278
+ ```
279
+
280
+ The hit rate is token-weighted: `cacheRead / (cacheRead + cacheWrite + input)` over cache-eligible steps; `COVERAGE` shows the share of steps whose provider reported cache tokens at all, so providers without prompt caching read as `n/a` instead of a misleading `0%`. `--since`/`--until` accept `Nd`/`Nw`/`Nm` or `YYYY-MM-DD`.
281
+
282
+ Agents can run this analysis conversationally too: the managed `vvoc-usage-analytics` skill answers usage, cache, and cost questions inside a session — including historical comparisons from `opencode.db` that predate the analytics plugin (see Managed Skills).
283
+
262
284
  ### Web Tools
263
285
 
264
286
  `WebToolsPlugin` exposes exactly two canonical model-facing tools:
@@ -339,6 +361,7 @@ The `context` vvoc plugin toggle defaults to enabled. Disable it with `vvoc plug
339
361
  | `vvoc patch-provider stepfun-ai\|codex\|deepseek\|kimi\|alibaba\|all` | Patch OpenCode providers; `codex` adds subscription-safe OpenAI aliases (also accepts `openai`), `deepseek`/`kimi`/`alibaba` add vv- reasoning-effort aliases, `all` patches every provider at once |
340
362
  | `vvoc completion` | Install shell completions |
341
363
  | `vvoc upgrade` | Upgrade global package and run follow-up sync; sync failure is reported as a partial upgrade |
364
+ | `vvoc analytics cache-hit-rate` | Aggregate persisted cache hit rate by day, week, month, session, model, provider, project, vvoc version, or OpenCode version |
342
365
  | `vvoc version` | Print installed version |
343
366
 
344
367
  Guardian duration overrides use positive whole milliseconds. Both `--timeout-ms` and
@@ -475,6 +498,7 @@ Spec documents → ./.vvoc/specs/YYYY-MM-DD-<slug>/spec.xml
475
498
  Optional design context → ./.vvoc/specs/YYYY-MM-DD-<slug>/design-context.xml
476
499
  Implementation plans → ./.vvoc/specs/YYYY-MM-DD-<slug>/plan.xml
477
500
  Persisted data → $XDG_DATA_HOME/vvoc/
501
+ Usage analytics → $XDG_DATA_HOME/vvoc/analytics/usage-YYYY-MM.jsonl (local-only cache telemetry)
478
502
  Repository memory → ./.vvoc/lessons/*.xml (lazy vv-reflect fallback)
479
503
  ./.vvoc/runbooks/*.xml (lazy vv-reflect fallback)
480
504
  Session handoff notes → ./.vvoc/handoff/YYYY-MM-DD-<session-slug>/handoff.xml
@@ -546,7 +570,7 @@ All prompt files are scaffolded by `vvoc install` / `vvoc sync`:
546
570
 
547
571
  ## Managed Skills
548
572
 
549
- Six workflow skills are scaffolded alongside agents:
573
+ Managed skills come in two families: `vv-*` skills guide the work protocol (spec, plan, execute, review, reflect, handoff), while `vvoc-*` skills operate and observe the vvoc/OpenCode tooling itself. Seven skills are scaffolded alongside agents:
550
574
 
551
575
  | Skill | When to use it | What it gives you |
552
576
  |---|---|---|
@@ -556,6 +580,7 @@ Six workflow skills are scaffolded alongside agents:
556
580
  | `vv-review` | You want findings, not fixes | A review-only workflow that reports spec/code issues and stops before implementation |
557
581
  | `vv-reflect` | A long development, debugging, ops, or investigation session produced reusable knowledge | Durable notes in existing docs or `.vvoc/lessons` / `.vvoc/runbooks` for future agents |
558
582
  | `vv-handoff` | You are ending a session and want the visible context preserved for a future session | A redacted XML note at `.vvoc/handoff/YYYY-MM-DD-<session-slug>/handoff.xml`, without running new checks or collecting fresh context |
583
+ | `vvoc-usage-analytics` | You ask about token usage, cache hit rate, costs, or whether a vvoc/OpenCode upgrade changed caching | An agent-run read-only analysis across `vvoc analytics`, the analytics JSONL, and historical `opencode.db` data (validated SQL snippets included) |
559
584
 
560
585
  Spec and plan artifacts stay XML so requirements, tasks, acceptance criteria, and dependencies remain easy to grep and review.
561
586
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // START_MODULE_CONTRACT
5
5
  // PURPOSE: Assemble and run the vvoc CLI entrypoint.
6
6
  // SCOPE: Package version lookup, top-level command registration, and main command execution.
7
- // DEPENDS: [citty, src/commands/completion.ts, src/commands/config.ts, src/commands/doctor.ts, src/commands/guardian.ts, src/commands/init.ts, src/commands/install.ts, src/commands/launch.ts, src/commands/orchestration.ts, src/commands/patch-provider.ts, src/commands/preset.ts, src/commands/plugin.ts, src/commands/role.ts, src/commands/status.ts, src/commands/sync.ts, src/commands/upgrade.ts, src/commands/version.ts, src/lib/package.ts]
7
+ // DEPENDS: [citty, src/commands/analytics.ts, src/commands/completion.ts, src/commands/config.ts, src/commands/doctor.ts, src/commands/guardian.ts, src/commands/init.ts, src/commands/install.ts, src/commands/launch.ts, src/commands/orchestration.ts, src/commands/patch-provider.ts, src/commands/preset.ts, src/commands/plugin.ts, src/commands/role.ts, src/commands/status.ts, src/commands/sync.ts, src/commands/upgrade.ts, src/commands/version.ts, src/lib/package.ts]
8
8
  // LINKS: [M-CLI-COMMANDS]
9
9
  // ROLE: SCRIPT
10
10
  // MAP_MODE: LOCALS
@@ -16,9 +16,10 @@
16
16
  // END_MODULE_MAP
17
17
  //
18
18
  // START_CHANGE_SUMMARY
19
- // LAST_CHANGE: [v0.2.11 - Aligned meta description with package.json, README, and GitHub repo; license and badge polish.]
19
+ // LAST_CHANGE: [2026-08-19-cache-hit-rate-analytics - Registered the analytics command in the CLI tree.]
20
20
  // END_CHANGE_SUMMARY
21
21
  import { defineCommand, runMain } from "citty";
22
+ import analytics from "./commands/analytics.js";
22
23
  import completion from "./commands/completion.js";
23
24
  import config from "./commands/config.js";
24
25
  import doctor from "./commands/doctor.js";
@@ -45,6 +46,7 @@ const main = defineCommand({
45
46
  description: "Portable OpenCode workflow toolkit — 6 plugins, managed agents & skills, a spec-to-code pipeline, security, and the vvoc CLI.",
46
47
  },
47
48
  subCommands: {
49
+ analytics,
48
50
  completion,
49
51
  config,
50
52
  doctor,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,mBAAmB;AACnB,kBAAkB;AAClB,wBAAwB;AACxB,uDAAuD;AACvD,+FAA+F;AAC/F,2bAA2b;AAC3b,4BAA4B;AAC5B,iBAAiB;AACjB,qBAAqB;AACrB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,oEAAoE;AACpE,wCAAwC;AACxC,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,4HAA4H;AAC5H,qBAAqB;AAErB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,UAAU,MAAM,0BAA0B,CAAC;AAClD,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,aAAa,MAAM,8BAA8B,CAAC;AACzD,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,iCAAiC;AACjC,MAAM,cAAc,GAAG,MAAM,iBAAiB,EAAE,CAAC;AAEjD,MAAM,IAAI,GAAG,aAAa,CAAC;IACzB,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,cAAc;QACvB,WAAW,EACT,+HAA+H;KAClI;IACD,WAAW,EAAE;QACX,UAAU;QACV,MAAM;QACN,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,OAAO;QACP,MAAM;QACN,aAAa;QACb,gBAAgB,EAAE,aAAa;QAC/B,MAAM;QACN,MAAM;QACN,IAAI;QACJ,MAAM;QACN,IAAI;QACJ,OAAO;QACP,OAAO;KACR;CACF,CAAC,CAAC;AACH,+BAA+B;AAE/B,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,mBAAmB;AACnB,kBAAkB;AAClB,wBAAwB;AACxB,uDAAuD;AACvD,+FAA+F;AAC/F,sdAAsd;AACtd,4BAA4B;AAC5B,iBAAiB;AACjB,qBAAqB;AACrB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,oEAAoE;AACpE,wCAAwC;AACxC,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,2GAA2G;AAC3G,qBAAqB;AAErB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,SAAS,MAAM,yBAAyB,CAAC;AAChD,OAAO,UAAU,MAAM,0BAA0B,CAAC;AAClD,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,aAAa,MAAM,8BAA8B,CAAC;AACzD,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,iCAAiC;AACjC,MAAM,cAAc,GAAG,MAAM,iBAAiB,EAAE,CAAC;AAEjD,MAAM,IAAI,GAAG,aAAa,CAAC;IACzB,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,cAAc;QACvB,WAAW,EACT,+HAA+H;KAClI;IACD,WAAW,EAAE;QACX,SAAS;QACT,UAAU;QACV,MAAM;QACN,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,OAAO;QACP,MAAM;QACN,aAAa;QACb,gBAAgB,EAAE,aAAa;QAC/B,MAAM;QACN,MAAM;QACN,IAAI;QACJ,MAAM;QACN,IAAI;QACJ,OAAO;QACP,OAAO;KACR;CACF,CAAC,CAAC;AACH,+BAA+B;AAE/B,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,58 @@
1
+ import type { AnalyticsGroupKey, GroupedMetrics } from "../lib/analytics/types.js";
2
+ export declare const GROUP_BY_VALUES: AnalyticsGroupKey[];
3
+ export declare const ORDER_VALUES: readonly ["date", "steps", "hit-rate"];
4
+ export type CacheHitRateRow = {
5
+ group: string;
6
+ steps: number;
7
+ eligibleSteps: number;
8
+ coverage: number;
9
+ hitRate: number | null;
10
+ cacheRead: number;
11
+ cacheWrite: number;
12
+ input: number;
13
+ output: number;
14
+ reasoning: number;
15
+ };
16
+ /** Orders groups: date desc (default), steps desc, or hit-rate desc with null lowest. */
17
+ export declare function orderGroups(groups: GroupedMetrics[], order: string): GroupedMetrics[];
18
+ /** Projects grouped metrics into JSON/table rows, labeling session groups with known titles. */
19
+ export declare function buildCacheHitRateRows(groups: GroupedMetrics[], groupBy: AnalyticsGroupKey, sessionTitles: Map<string, string>): CacheHitRateRow[];
20
+ /** Session display label: latest known title plus a short session id. */
21
+ export declare function sessionDisplayLabel(sessionID: string, titles: Map<string, string>): string;
22
+ /** Renders cache hit rate rows as a padded ASCII table. */
23
+ export declare function renderCacheHitRateTable(rows: CacheHitRateRow[]): string;
24
+ export declare const cacheHitRateCommand: import("citty").CommandDef<{
25
+ readonly since: {
26
+ readonly type: "string";
27
+ readonly description: "Include records from this date: Nd/Nw/Nm relative or absolute YYYY-MM-DD.";
28
+ };
29
+ readonly until: {
30
+ readonly type: "string";
31
+ readonly description: "Include records up to this inclusive date (YYYY-MM-DD).";
32
+ };
33
+ readonly "group-by": {
34
+ readonly type: "string";
35
+ readonly default: "day";
36
+ readonly description: `Grouping key: ${string}.`;
37
+ };
38
+ readonly project: {
39
+ readonly type: "string";
40
+ readonly description: "Case-insensitive project directory substring filter.";
41
+ };
42
+ readonly limit: {
43
+ readonly type: "string";
44
+ readonly description: "Maximum number of rendered groups (default 30).";
45
+ };
46
+ readonly order: {
47
+ readonly type: "string";
48
+ readonly default: "date";
49
+ readonly description: `Row ordering: ${string}.`;
50
+ };
51
+ readonly json: {
52
+ readonly type: "boolean";
53
+ readonly default: false;
54
+ readonly description: "Print machine-readable JSON instead of a table.";
55
+ };
56
+ }>;
57
+ declare const _default: import("citty").CommandDef<import("citty").ArgsDef>;
58
+ export default _default;
@@ -0,0 +1,220 @@
1
+ // FILE: src/commands/analytics.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Provide the vvoc analytics command with the cache-hit-rate aggregation subcommand.
5
+ // SCOPE: Flag validation, store reading with env override, dedupe/filter/group pipeline, ordering, limits, JSON and table rendering, and the empty state.
6
+ // DEPENDS: [citty, src/lib/analytics/store.ts, src/lib/analytics/metrics.ts, src/lib/analytics/types.ts]
7
+ // LINKS: [M-ANALYTICS-STORE, M-ANALYTICS-METRICS, M-CLI-COMMANDS]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // GROUP_BY_VALUES - Supported --group-by values.
14
+ // ORDER_VALUES - Supported --order values.
15
+ // cacheHitRateCommand - cache-hit-rate subcommand definition.
16
+ // orderGroups - Orders grouped metrics by date, steps, or hit rate.
17
+ // buildCacheHitRateRows - Projects groups and session titles into JSON/table rows.
18
+ // renderCacheHitRateTable - Renders rows as a padded ASCII table.
19
+ // default - analytics parent command registering cache-hit-rate.
20
+ // END_MODULE_MAP
21
+ //
22
+ // START_CHANGE_SUMMARY
23
+ // LAST_CHANGE: [2026-08-19-cache-hit-rate-analytics - Added analytics cache-hit-rate CLI command.]
24
+ // END_CHANGE_SUMMARY
25
+ import { defineCommand } from "citty";
26
+ import { readAnalyticsRecords } from "../lib/analytics/store.js";
27
+ import { computeGroupedMetrics, dedupeUsageRecords, filterUsageRecords, formatHitRate, formatTokenCount, parseSinceUntil, } from "../lib/analytics/metrics.js";
28
+ export const GROUP_BY_VALUES = [
29
+ "session",
30
+ "day",
31
+ "week",
32
+ "month",
33
+ "model",
34
+ "provider",
35
+ "project",
36
+ "vvoc",
37
+ "opencode",
38
+ ];
39
+ export const ORDER_VALUES = ["date", "steps", "hit-rate"];
40
+ // START_BLOCK_ORDER_AND_ROWS
41
+ /** Orders groups: date desc (default), steps desc, or hit-rate desc with null lowest. */
42
+ export function orderGroups(groups, order) {
43
+ const sorted = [...groups];
44
+ if (order === "steps") {
45
+ sorted.sort((a, b) => b.steps - a.steps);
46
+ }
47
+ else if (order === "hit-rate") {
48
+ sorted.sort((a, b) => (b.hitRate ?? -1) - (a.hitRate ?? -1));
49
+ }
50
+ else {
51
+ sorted.sort((a, b) => (a.key < b.key ? 1 : a.key > b.key ? -1 : 0));
52
+ }
53
+ return sorted;
54
+ }
55
+ /** Projects grouped metrics into JSON/table rows, labeling session groups with known titles. */
56
+ export function buildCacheHitRateRows(groups, groupBy, sessionTitles) {
57
+ return groups.map((group) => ({
58
+ group: groupBy === "session" ? sessionDisplayLabel(group.key, sessionTitles) : group.key,
59
+ steps: group.steps,
60
+ eligibleSteps: group.eligibleSteps,
61
+ coverage: group.coverage,
62
+ hitRate: group.hitRate,
63
+ cacheRead: group.cacheRead,
64
+ cacheWrite: group.cacheWrite,
65
+ input: group.input,
66
+ output: group.output,
67
+ reasoning: group.reasoning,
68
+ }));
69
+ }
70
+ /** Session display label: latest known title plus a short session id. */
71
+ export function sessionDisplayLabel(sessionID, titles) {
72
+ const title = titles.get(sessionID);
73
+ const short = sessionID.slice(0, 8);
74
+ return title ? `${title} ${short}` : short;
75
+ }
76
+ // END_BLOCK_ORDER_AND_ROWS
77
+ // START_BLOCK_RENDER_TABLE
78
+ /** Renders cache hit rate rows as a padded ASCII table. */
79
+ export function renderCacheHitRateTable(rows) {
80
+ const header = [
81
+ "GROUP",
82
+ "STEPS",
83
+ "COVERAGE",
84
+ "HIT-RATE",
85
+ "CACHE-READ",
86
+ "CACHE-WRITE",
87
+ "FRESH-IN",
88
+ ];
89
+ const lines = rows.map((row) => [
90
+ row.group,
91
+ String(row.steps),
92
+ row.coverage.toFixed(2),
93
+ formatHitRate(row.hitRate),
94
+ formatTokenCount(row.cacheRead),
95
+ formatTokenCount(row.cacheWrite),
96
+ formatTokenCount(row.input),
97
+ ]);
98
+ const widths = header.map((_, index) => Math.max(header[index].length, ...lines.map((line) => line[index].length)));
99
+ const render = (cells) => cells
100
+ .map((cell, index) => cell.padEnd(widths[index]))
101
+ .join(" ")
102
+ .trimEnd();
103
+ return [render(header), ...lines.map(render)].join("\n");
104
+ }
105
+ // END_BLOCK_RENDER_TABLE
106
+ // START_BLOCK_CACHE_HIT_RATE_COMMAND
107
+ export const cacheHitRateCommand = defineCommand({
108
+ meta: {
109
+ name: "cache-hit-rate",
110
+ description: "Aggregate cache hit rate from persisted step telemetry.",
111
+ },
112
+ args: {
113
+ since: {
114
+ type: "string",
115
+ description: "Include records from this date: Nd/Nw/Nm relative or absolute YYYY-MM-DD.",
116
+ },
117
+ until: {
118
+ type: "string",
119
+ description: "Include records up to this inclusive date (YYYY-MM-DD).",
120
+ },
121
+ "group-by": {
122
+ type: "string",
123
+ default: "day",
124
+ description: `Grouping key: ${GROUP_BY_VALUES.join(", ")}.`,
125
+ },
126
+ project: {
127
+ type: "string",
128
+ description: "Case-insensitive project directory substring filter.",
129
+ },
130
+ limit: {
131
+ type: "string",
132
+ description: "Maximum number of rendered groups (default 30).",
133
+ },
134
+ order: {
135
+ type: "string",
136
+ default: "date",
137
+ description: `Row ordering: ${ORDER_VALUES.join(", ")}.`,
138
+ },
139
+ json: {
140
+ type: "boolean",
141
+ default: false,
142
+ description: "Print machine-readable JSON instead of a table.",
143
+ },
144
+ },
145
+ async run({ args }) {
146
+ const groupBy = String(args["group-by"] ?? "day");
147
+ if (!GROUP_BY_VALUES.includes(groupBy)) {
148
+ console.error(`Invalid --group-by value: ${groupBy} (expected one of ${GROUP_BY_VALUES.join(", ")})`);
149
+ process.exitCode = 1;
150
+ return;
151
+ }
152
+ const order = String(args.order ?? "date");
153
+ if (!ORDER_VALUES.includes(order)) {
154
+ console.error(`Invalid --order value: ${order} (expected one of ${ORDER_VALUES.join(", ")})`);
155
+ process.exitCode = 1;
156
+ return;
157
+ }
158
+ const now = new Date();
159
+ const since = parseSinceUntil(args.since === undefined ? undefined : String(args.since), now);
160
+ if (since === null) {
161
+ console.error(`Invalid --since value: ${String(args.since)} (expected Nd/Nw/Nm or YYYY-MM-DD)`);
162
+ process.exitCode = 1;
163
+ return;
164
+ }
165
+ const until = parseSinceUntil(args.until === undefined ? undefined : String(args.until), now);
166
+ if (until === null) {
167
+ console.error(`Invalid --until value: ${String(args.until)} (expected YYYY-MM-DD or a relative form)`);
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ const dataHomeOverride = resolveAnalyticsDataHomeOverride();
172
+ const records = await readAnalyticsRecords({ dataHomeOverride });
173
+ const usage = dedupeUsageRecords(records.filter((record) => record.kind === "usage"));
174
+ const sessionTitles = collectSessionTitles(records);
175
+ const filtered = filterUsageRecords(usage, {
176
+ since,
177
+ until,
178
+ project: args.project === undefined ? undefined : String(args.project),
179
+ });
180
+ const parsedLimit = Number.parseInt(String(args.limit ?? "30"), 10);
181
+ const limit = Math.max(1, Number.isFinite(parsedLimit) ? parsedLimit : 30);
182
+ const ordered = orderGroups(computeGroupedMetrics(filtered, groupBy), order).slice(0, limit);
183
+ const rows = buildCacheHitRateRows(ordered, groupBy, sessionTitles);
184
+ if (args.json === true) {
185
+ console.log(JSON.stringify(rows, null, 2));
186
+ return;
187
+ }
188
+ if (rows.length === 0) {
189
+ console.log("No analytics records matched the given filters.");
190
+ return;
191
+ }
192
+ console.log(renderCacheHitRateTable(rows));
193
+ },
194
+ });
195
+ // END_BLOCK_CACHE_HIT_RATE_COMMAND
196
+ export default defineCommand({
197
+ meta: {
198
+ name: "analytics",
199
+ description: "Inspect persisted vvoc usage analytics.",
200
+ },
201
+ subCommands: {
202
+ "cache-hit-rate": cacheHitRateCommand,
203
+ },
204
+ });
205
+ /** Resolves the VVOC_ANALYTICS_DATA_HOME data-home override for tests and sandboxes. */
206
+ function resolveAnalyticsDataHomeOverride() {
207
+ const value = process.env.VVOC_ANALYTICS_DATA_HOME;
208
+ return value && value.trim() ? value.trim() : undefined;
209
+ }
210
+ /** Keeps the latest title per sessionID from session records. */
211
+ function collectSessionTitles(records) {
212
+ const titles = new Map();
213
+ for (const record of records) {
214
+ if (record.kind === "session") {
215
+ titles.set(record.sessionID, record.title);
216
+ }
217
+ }
218
+ return titles;
219
+ }
220
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/commands/analytics.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -6,3 +6,4 @@ export { WorkflowPlugin } from "./plugins/workflow/index.js";
6
6
  export { SecretsRedactionPlugin } from "./plugins/secrets-redaction.js";
7
7
  export { WebToolsPlugin } from "./plugins/web-tools/index.js";
8
8
  export { ToolHistoryCompactionPlugin } from "./plugins/tool-history-compaction/index.js";
9
+ export { AnalyticsPlugin } from "./plugins/analytics/index.js";
package/dist/index.js CHANGED
@@ -2,19 +2,19 @@
2
2
  // VERSION: 1.0.0
3
3
  // START_MODULE_CONTRACT
4
4
  // PURPOSE: Re-export the public vv-opencode plugin entrypoints from the package root.
5
- // SCOPE: Package-root exports for GuardianPlugin, HashlineEditPlugin, ModelRolesPlugin, SystemContextInjectionPlugin, WorkflowPlugin, SecretsRedactionPlugin, WebToolsPlugin, and ToolHistoryCompactionPlugin.
6
- // DEPENDS: [src/plugins/guardian/index.ts, src/plugins/hashline-edit/index.ts, src/plugins/model-roles/index.ts, src/plugins/system-context-injection/index.ts, src/plugins/workflow/index.ts, src/plugins/secrets-redaction.ts, src/plugins/web-tools/index.ts, src/plugins/tool-history-compaction/index.ts]
7
- // LINKS: [M-PLUGIN-GUARDIAN, M-PLUGIN-HASHLINE-EDIT, M-PLUGIN-MODEL-ROLES, M-PLUGIN-SYSTEM-CONTEXT-INJECTION, M-PLUGIN-WORKFLOW, M-PLUGIN-SECRETS-REDACTION, M-PLUGIN-WEB-TOOLS, M-PLUGIN-TOOL-HISTORY-COMPACTION]
5
+ // SCOPE: Package-root exports for GuardianPlugin, HashlineEditPlugin, ModelRolesPlugin, SystemContextInjectionPlugin, WorkflowPlugin, SecretsRedactionPlugin, WebToolsPlugin, ToolHistoryCompactionPlugin, and AnalyticsPlugin.
6
+ // DEPENDS: [src/plugins/guardian/index.ts, src/plugins/hashline-edit/index.ts, src/plugins/model-roles/index.ts, src/plugins/system-context-injection/index.ts, src/plugins/workflow/index.ts, src/plugins/secrets-redaction.ts, src/plugins/web-tools/index.ts, src/plugins/tool-history-compaction/index.ts, src/plugins/analytics/index.ts]
7
+ // LINKS: [M-PLUGIN-GUARDIAN, M-PLUGIN-HASHLINE-EDIT, M-PLUGIN-MODEL-ROLES, M-PLUGIN-SYSTEM-CONTEXT-INJECTION, M-PLUGIN-WORKFLOW, M-PLUGIN-SECRETS-REDACTION, M-PLUGIN-WEB-TOOLS, M-PLUGIN-TOOL-HISTORY-COMPACTION, M-PLUGIN-ANALYTICS]
8
8
  // ROLE: BARREL
9
9
  // MAP_MODE: SUMMARY
10
10
  // END_MODULE_CONTRACT
11
11
  //
12
12
  // START_MODULE_MAP
13
- // GuardianPlugin, HashlineEditPlugin, ModelRolesPlugin, SystemContextInjectionPlugin, WorkflowPlugin, SecretsRedactionPlugin, WebToolsPlugin - Public plugin exports available from @osovv/vv-opencode.
13
+ // GuardianPlugin, HashlineEditPlugin, ModelRolesPlugin, SystemContextInjectionPlugin, WorkflowPlugin, SecretsRedactionPlugin, WebToolsPlugin, ToolHistoryCompactionPlugin, AnalyticsPlugin - Public plugin exports available from @osovv/vv-opencode.
14
14
  // END_MODULE_MAP
15
15
  //
16
16
  // START_CHANGE_SUMMARY
17
- // LAST_CHANGE: [v1.3.0 - Added ToolHistoryCompactionPlugin to the package-root exports.]
17
+ // LAST_CHANGE: [2026-08-19-cache-hit-rate-analytics - Added AnalyticsPlugin to the package-root exports.]
18
18
  // END_CHANGE_SUMMARY
19
19
  export { GuardianPlugin } from "./plugins/guardian/index.js";
20
20
  export { HashlineEditPlugin } from "./plugins/hashline-edit/index.js";
@@ -24,4 +24,5 @@ export { WorkflowPlugin } from "./plugins/workflow/index.js";
24
24
  export { SecretsRedactionPlugin } from "./plugins/secrets-redaction.js";
25
25
  export { WebToolsPlugin } from "./plugins/web-tools/index.js";
26
26
  export { ToolHistoryCompactionPlugin } from "./plugins/tool-history-compaction/index.js";
27
+ export { AnalyticsPlugin } from "./plugins/analytics/index.js";
27
28
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,iBAAiB;AACjB,wBAAwB;AACxB,wFAAwF;AACxF,iNAAiN;AACjN,iTAAiT;AACjT,qNAAqN;AACrN,iBAAiB;AACjB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,0MAA0M;AAC1M,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,2FAA2F;AAC3F,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,4BAA4B,EAAE,MAAM,6CAA6C,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,iBAAiB;AACjB,wBAAwB;AACxB,wFAAwF;AACxF,kOAAkO;AAClO,iVAAiV;AACjV,yOAAyO;AACzO,iBAAiB;AACjB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,wPAAwP;AACxP,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,4GAA4G;AAC5G,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,4BAA4B,EAAE,MAAM,6CAA6C,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { AnalyticsGroupKey, GroupedMetrics, UsageRecord } from "./types.js";
2
+ /** Keeps only the last occurrence of each partID in array order (last-write-wins). */
3
+ export declare function dedupeUsageRecords(records: UsageRecord[]): UsageRecord[];
4
+ /** ISO week key "YYYY-Wnn" for a UTC timestamp. */
5
+ export declare function isoWeekKey(ts: string): string;
6
+ /** Group key extraction; day/week/month buckets are UTC-based. */
7
+ export declare function groupKeyOf(record: UsageRecord, groupBy: AnalyticsGroupKey): string;
8
+ /**
9
+ * Aggregates usage records into per-group metrics.
10
+ * hitRate = sum(cacheRead) / sum(cacheRead + cacheWrite + input) over eligible
11
+ * steps (cacheRead + cacheWrite > 0); null when a group has no eligible steps.
12
+ */
13
+ export declare function computeGroupedMetrics(usage: UsageRecord[], groupBy: AnalyticsGroupKey): GroupedMetrics[];
14
+ export type UsageFilter = {
15
+ /** Inclusive lower date bound "YYYY-MM-DD" (UTC). */
16
+ since?: string;
17
+ /** Inclusive upper date bound "YYYY-MM-DD" (UTC). */
18
+ until?: string;
19
+ /** Case-insensitive substring matched against projectDirectory. */
20
+ project?: string;
21
+ };
22
+ /** Applies inclusive date-range and project-substring filters. */
23
+ export declare function filterUsageRecords(usage: UsageRecord[], filter: UsageFilter): UsageRecord[];
24
+ /**
25
+ * Parses a --since/--until value. Relative "Nd"/"Nw"/"Nm" resolve against `now`
26
+ * as a UTC date string; absolute "YYYY-MM-DD" passes through. Returns undefined
27
+ * for undefined input and null for unparseable input.
28
+ */
29
+ export declare function parseSinceUntil(raw: string | undefined, now: Date): string | undefined | null;
30
+ /** Compact human token count: 999 -> "999"; 1234 -> "1.2k"; 2100000 -> "2.1M". */
31
+ export declare function formatTokenCount(value: number): string;
32
+ /** "n/a" for null; otherwise a percentage with one decimal. */
33
+ export declare function formatHitRate(rate: number | null): string;