Skip to content

Commit ad60993

Browse files
feat(analytics): adoption analytics, diagnostics skill, hook replay (#260)
Adoption telemetry was split and partly unreadable: the only writer of analytics_events targets the user-level global DB (the per-project copy is schema-only), hook_analytics.jsonl splits between the project store and the user-level fallback with no attribution, dashboard diagnostics read only the project-store file, and there was no CLI to answer adoption questions. - hooks: stamp project_root/event_cwd on hook analytics rows; resolve the project root in Claude preToolUse (event cwd, then process cwd) and the Kiro hooks instead of hardcoding None - dashboard: diagnostics merges the project-store and user-level hook files (user-level rows filtered by attribution) and reports hook_sources - analytics_bridge: idempotent hook-JSONL -> analytics_events import using parse_offsets byte cursors; partial trailing lines stay unconsumed - cli: new `tracedecay analytics diagnostics [--all|--no-sync]` and `tracedecay analytics sync` - plugin: new diagnosing-analytics skill teaching the analytics/doctor/ lcm_status/sessions surface; added to CURSOR_PLUGIN_SKILLS - tests: end-to-end hook replay driving all four providers' hook subcommands through the binary and asserting JSONL attribution plus durable bridging; vendored Claude Code schemas (hooks/plugin/marketplace) with positive and negative validation, closing the gap where only Cursor/Codex configs were schema-checked Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 0d81cf3 commit ad60993

28 files changed

Lines changed: 1447 additions & 40 deletions

docs/PLUGIN-VALIDATION.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,22 @@ job); layer 6 covers what can't live in the Rust test harness.
3939
### 1. Schema validation (cargo test)
4040

4141
JSON artifacts in the bundles are validated against vendored JSON Schemas in
42-
`tests/fixtures/cursor-schemas/`:
42+
`tests/fixtures/cursor-schemas/` (Cursor/Codex shapes) and
43+
`tests/fixtures/claude-schemas/` (Claude Code shapes):
4344

4445
| Artifact | Schema | Test |
4546
|---|---|---|
46-
| `plugin/.cursor-plugin/plugin.json` | `plugin.schema.json` | `tests/agent_suite/plugin_manifest_schema_test.rs` |
47-
| `plugin/.codex-plugin/plugin.json` | `plugin.schema.json` + `interface` extension | `tests/agent_suite/plugin_manifest_schema_test.rs` |
48-
| `plugin/.claude-plugin/marketplace.json` | `marketplace.schema.json` | vendored for refresh parity |
49-
| `plugin/mcp-cursor.json` (deploys as `mcp.json`) | `mcp.schema.json` | `tests/agent_suite/plugin_config_schema_test.rs` |
50-
| `plugin/hooks/hooks-cursor.json` and `plugin/hooks/hooks-codex.json` | `hooks.schema.json` | `tests/agent_suite/plugin_config_schema_test.rs` |
47+
| `plugin/.cursor-plugin/plugin.json` | `cursor-schemas/plugin.schema.json` | `tests/agent_suite/plugin_manifest_schema_test.rs` |
48+
| `plugin/.codex-plugin/plugin.json` | `cursor-schemas/plugin.schema.json` + `interface` extension | `tests/agent_suite/plugin_manifest_schema_test.rs` |
49+
| `plugin/.claude-plugin/plugin.json` | `claude-schemas/plugin.schema.json` | `tests/agent_suite/claude_plugin_schema_test.rs` |
50+
| `plugin/.claude-plugin/marketplace.json` | `claude-schemas/marketplace.schema.json` | `tests/agent_suite/claude_plugin_schema_test.rs` |
51+
| `plugin/mcp-cursor.json` (deploys as `mcp.json`) | `cursor-schemas/mcp.schema.json` | `tests/agent_suite/plugin_config_schema_test.rs` |
52+
| `plugin/hooks/hooks-cursor.json` and `plugin/hooks/hooks-codex.json` | `cursor-schemas/hooks.schema.json` | `tests/agent_suite/plugin_config_schema_test.rs` |
53+
| `plugin/hooks/hooks-claude.json` | `claude-schemas/hooks.schema.json` | `tests/agent_suite/claude_plugin_schema_test.rs` |
54+
55+
(The Cursor `marketplace.schema.json` stays vendored for refresh parity; the
56+
Claude marketplace uses its own schema above because Claude entries carry
57+
fields — `category`, `homepage` — that Cursor's marketplace schema rejects.)
5158

5259
The tests use the `jsonschema` crate (dev-dependency only, no network
5360
resolvers — the schemas are self-contained draft-07, and the shipped binary
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
name: diagnosing-analytics
3+
description: 'Use when checking TraceDecay adoption or telemetry health — usage analytics, silent hooks, stale hook logs, or empty analytics tables. Uses the analytics, doctor, and sessions CLI, never raw database queries.'
4+
---
5+
6+
# Diagnosing TraceDecay analytics
7+
8+
TraceDecay records three telemetry streams: durable `analytics_events` in the
9+
user-level global store (`mcp_tool_call` rows from the MCP server,
10+
`hook_route` rows from daemon routing, and `hook_<agent>` rows bridged from
11+
hook logs), append-only `hook_analytics.jsonl` files (one per project store
12+
plus a user-level fallback for hooks that could not resolve a project), and
13+
the per-project session store used for message-level usage. Adoption and
14+
health questions are answered from the durable table — never by opening
15+
store databases directly.
16+
17+
## Commands
18+
19+
1. **`tracedecay analytics diagnostics`** — the one-stop summary. Imports any
20+
new hook-log rows into the durable table first, then prints event, tool,
21+
and hook counts, the TraceDecay tool-call count, per-tool/per-hook rollups,
22+
`hook_sources` with per-file row counts, and usage ratios. Flags:
23+
`--all` (every project, not just the current one), `--no-sync` (skip the
24+
import pass; read-only).
25+
2. **`tracedecay analytics sync`** — run only the hook-log import and report
26+
how many rows each source contributed.
27+
3. **`tracedecay doctor`** — per-agent install health: hook registration, MCP
28+
config, binary staleness. Run this first when a hook seems silent
29+
(Codex hooks may exist but stay skipped until trusted in Codex's own
30+
hooks approval prompt).
31+
4. **`tracedecay tool lcm_status --provider all --json`** and
32+
**`tracedecay tool lcm_doctor --provider codex --mode diagnose --json`**
33+
session-store ingest and compression health per provider.
34+
5. **`tracedecay sessions search "mcp__tracedecay" --provider all`** — find
35+
tool usage evidence inside ingested transcripts across providers.
36+
6. **`tracedecay gain`** / **`tracedecay cost`** — token savings ledger and
37+
spend rollups.
38+
7. **Dashboard**: `tracedecay dashboard` serves the same summaries at
39+
`/api/plugins/analytics/overview|diagnostics|usage|hints`.
40+
41+
## Reading the diagnostics output
42+
43+
- `source: "analytics_events"` means durable data answered; a
44+
`session_messages_and_hook_analytics` source means no durable events matched
45+
and the summary fell back to hook logs only.
46+
- `hook_sources` lists each hook-log file with `rows_total` vs
47+
`rows_included`. A large gap means rows written before project attribution
48+
existed; they are visible under `--all` but cannot be assigned to a project.
49+
- `project_id: null` means the command ran outside an indexed project and
50+
reported globally. Run from an indexed project root for per-project scope,
51+
or pass `--all` deliberately.
52+
- `import.imported` greater than zero means a hook-log backlog was just
53+
bridged — re-read the counts, they include it.
54+
- A high hook-call count with a low TraceDecay tool-call count is an adoption gap:
55+
hooks fire, but sessions are not using tracedecay tools. Check
56+
`by_prompt_category` to see which task types miss it.
57+
58+
## Guardrails
59+
60+
- A zero-row `analytics_events` table inside a project store is not evidence
61+
that telemetry is broken — durable events live in the user-level store. Run
62+
`tracedecay analytics diagnostics` before concluding anything is dead.
63+
- Never query store databases with sqlite3 or scripts; schemas are internal.
64+
The diagnostics JSON already merges every relevant source.
65+
- If the MCP transport is down, every command above still works — they are
66+
plain CLI subcommands (see `tracedecay:using-the-cli`).
67+
68+
## Output
69+
70+
- Headline counts (events, MCP tool calls, tracedecay calls, hook calls) with
71+
the exact command used, plus any gaps found: unattributed hook rows, stale
72+
or missing sources, providers with hooks firing but zero tool usage.

src/agents/claude.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1589,7 +1589,7 @@ mod tests {
15891589
.collect();
15901590

15911591
let skills = plugin_subdir_names("skills");
1592-
assert_eq!(skills.len(), 29, "expected 29 shared skill dirs");
1592+
assert_eq!(skills.len(), 30, "expected 30 shared skill dirs");
15931593
// Every file under plugin/skills/ (SKILL.md *and* any support files) is
15941594
// deployed — the recursive embed leaves nothing on disk unwired.
15951595
let skills_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("plugin/skills");

src/agents/codex.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,7 @@ mod tests {
15491549
.map(|entry| entry.file_name().to_string_lossy().into_owned())
15501550
.collect();
15511551
skill_dirs.sort();
1552-
assert_eq!(skill_dirs.len(), 29, "expected 29 shared skill dirs");
1552+
assert_eq!(skill_dirs.len(), 30, "expected 30 shared skill dirs");
15531553
// Every file under plugin/skills/ (SKILL.md *and* any support files) is
15541554
// deployed — the recursive embed leaves nothing on disk unwired.
15551555
for relative in skill_tree_files(&skills_root) {

src/agents/plugin_bundle.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
//! agent format.
66
//!
77
//! Layout of `plugin/`:
8-
//! - `plugin/skills/*/SKILL.md` — the 16 shared model-invocable skills **plus**
9-
//! the 13 canonical (`claude`/`codex`) workflow dispatcher skills (29 total).
10-
//! Cursor deploys only the 16 model-invocable skills (not the dispatcher
8+
//! - `plugin/skills/*/SKILL.md` — the 17 shared model-invocable skills **plus**
9+
//! the 13 canonical (`claude`/`codex`) workflow dispatcher skills (30 total).
10+
//! Cursor deploys only the 17 model-invocable skills (not the dispatcher
1111
//! skills); its explicit dispatch is native commands (below).
1212
//! - `plugin/overlays/cursor/commands/tracedecay-*.md` — Cursor 1.6+ native
1313
//! slash commands, one per workflow slug, deployed to `commands/<slug>.md`.
@@ -235,7 +235,7 @@ fn compose(
235235
}
236236

237237
/// Files Claude deploys: manifest + Claude agents + Claude commands + every
238-
/// skill file (all 29 skills incl. dispatchers, plus any support files).
238+
/// skill file (all 30 skills incl. dispatchers, plus any support files).
239239
pub fn claude_files() -> Vec<(&'static str, &'static str)> {
240240
compose(
241241
&[
@@ -262,7 +262,7 @@ pub fn cursor_files() -> Vec<(&'static str, &'static str)> {
262262
)
263263
}
264264

265-
/// Files Codex deploys: manifest + every skill file (all 29 skills incl.
265+
/// Files Codex deploys: manifest + every skill file (all 30 skills incl.
266266
/// dispatchers, plus any support files). Codex ships no agents/commands/rules.
267267
pub fn codex_files() -> Vec<(&'static str, &'static str)> {
268268
compose(&[CODEX_MANIFEST_FILES], all_skill_files())

0 commit comments

Comments
 (0)