You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude-plugin/plugin.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
{
2
2
"name": "posthog",
3
3
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from your AI coding tool. Optionally capture Claude Code sessions to PostHog LLM Analytics.",
|**Anomaly watcher**| a product surface has a metric with a baseline that can move (bursts, drops, regressions). |`signals-scout-error-tracking`, `-logs`, `-revenue-analytics`, `-csp-violations`|
46
-
|**Watchlist explore/exploit**| the surface is too big to cover in one run; you must curate what's worth re-checking. |`signals-scout-anomaly-detection`|
47
-
|**Cross-product correlation**| the question spans products — a cause in one surface, an effect in another. |`signals-scout-general`|
48
-
|**Recommendation / gap**| nothing is broken, but the team is missing coverage or following an anti-pattern. |`signals-scout-observability-gaps`|
49
-
|**Warehouse-backed source**| the signal lives in a non-PostHog source synced into the warehouse. | a Slack-channel-sync scout (below) |
50
-
|**Custom / single-event**| one bespoke event carries the whole signal. | an MCP-feedback scout (below) |
51
-
|**Open-text theme**| the data is free text and the value is in recurring themes, not individual rows. |`signals-scout-surveys` (open-text); brand/feedback scouts |
52
-
|**External-tool / code**| the judgement comes from running a tool or reading code, not from analytics. | a static-analysis CLI scout (below) |
53
-
|**State ∩ code intersection**| the signal is the _overlap_ of a PostHog entity's state and what's in the source repo. | a feature-flag-cleanup scout (below) |
|**Anomaly watcher**| a product surface has a metric with a baseline that can move (bursts, drops, regressions). |`signals-scout-error-tracking`, `-logs`, `-revenue-analytics`, `-csp-violations`|
46
+
|**Watchlist (explore/exploit, or curated)**| the surface has more to watch than one run can cover — _discovered_ over time (explore/exploit) or a _fixed set you already know matters_ (curated). |`signals-scout-anomaly-detection`(discovered); a curated-dashboard scout (below)|
47
+
|**Cross-product correlation**| the question spans products — a cause in one surface, an effect in another. |`signals-scout-general`|
48
+
|**Recommendation / gap**| nothing is broken, but the team is missing coverage or following an anti-pattern. |`signals-scout-observability-gaps`|
49
+
|**Warehouse-backed source**| the signal lives in a non-PostHog source synced into the warehouse. | a Slack-channel-sync scout (below)|
50
+
|**Custom / single-event**| one bespoke event carries the whole signal. | an MCP-feedback scout (below)|
51
+
|**Open-text theme**| the data is free text and the value is in recurring themes, not individual rows. |`signals-scout-surveys` (open-text); brand/feedback scouts|
52
+
|**External-tool / code**| the judgement comes from running a tool or reading code, not from analytics. | a static-analysis CLI scout (below)|
53
+
|**State ∩ code intersection**| the signal is the _overlap_ of a PostHog entity's state and what's in the source repo. | a feature-flag-cleanup scout (below)|
54
54
55
55
### Anomaly watcher
56
56
@@ -67,6 +67,15 @@ The default specialist shape, and the one most surfaces fit.
67
67
re-derive it.
68
68
-**Gotcha:** score the **latest complete** bucket, not the in-progress one — a partial
69
69
current hour/day always looks like a drop.
70
+
-**Don't reinvent the scoring.** When the metric is a **saved time-series insight**, score it
71
+
with PostHog's own detectors via `alert-simulate` rather than hand-rolling anomaly math —
72
+
it already handles seasonality and the team's own alert thresholds. Fall back to a
73
+
hand-computed robust z-score (`|value − median| / (1.4826 × MAD)`) only when the series
74
+
isn't a saved insight.
75
+
-**Score the rate, not the raw total.** Normalize by the relevant denominator — cost
76
+
_per unit_, conversion _%_ per funnel stage, error _share_ — so a legitimate volume change
77
+
doesn't read as an anomaly (more traffic raises total spend but not cost-per-unit). The
78
+
"raw total moved" false positive is the most common one here.
70
79
- Copy the closest specialist verbatim and replace the surface + discriminator. Read
71
80
`products/signals/skills/signals-scout-error-tracking/SKILL.md` for the cleanest worked
72
81
example (its `count`-vs-`distinct_users` table is the canonical discriminator).
@@ -87,6 +96,17 @@ insights). The scout can't re-check everything every run, so it **curates**.
87
96
that bundles its own references; read
88
97
`products/signals/skills/signals-scout-anomaly-detection/` for the full treatment.
89
98
99
+
**Curated (fixed) variant — the common user ask.** When the team already knows exactly which
100
+
entities matter ("watch _these_ dashboards / insights / metrics"), drop the explore half: the
101
+
watchlist is a **fixed, curated set** held in the scratchpad (or even inlined in the body), so a
102
+
run spends almost nothing on discovery and almost everything on "is the latest number worth a
103
+
human's attention?". This is what most users mean by "keep an eye on my key dashboards", and it's
104
+
the cleanest first scout to hand someone. Still reconcile the set against reality each run
105
+
(entities get renamed/deleted), and still score each item against its own seasonality-matched
106
+
baseline — you've only removed discovery, not scoring. The worked shape: a fixed list of
107
+
dashboard / insight ids in the scratchpad, scored tile-by-tile via `alert-simulate`, with the
108
+
priority items re-checked every run and the rest rotated in as time allows.
109
+
90
110
### Cross-product correlation
91
111
92
112
The generalist's job. Not a deep dive into one surface — that's what specialists are for —
@@ -123,9 +143,15 @@ it's whatever that upstream system produces.
123
143
124
144
-**Watched data:** one (or a few) warehouse tables. Always confirm columns with
125
145
`read-data-warehouse-schema` first — column names are source-defined and often opaque.
126
-
-**Discriminator:** read off whatever the source already gives you cheaply. If the upstream
127
-
pre-classifies rows (a sentiment field, a category, a status), anchor on that — it's a
128
-
free discriminator. Otherwise derive one (recency × a keyword/shape match × recurrence).
146
+
-**Discriminator — pre-classified vs derived, and know which you have:**
147
+
-**Pre-classified** — if the upstream tool already labels rows (a sentiment field, a
148
+
category, a status, a priority), anchor on that. It's a free, high-signal discriminator —
149
+
e.g. a social-listening feed that ships a per-item sentiment.
150
+
-**Derived** — most synced sources give you nothing pre-labeled (a raw Slack/Discord
151
+
channel, a support stream). Build the discriminator from the row's own shape:
152
+
**topic × problem/request language × recurrence**, boosted by corroboration (a relayed
153
+
customer voice, ≥2 people hitting the same thing). This is harder — calibrate it against
154
+
the inbox more carefully than a pre-classified one.
129
155
-**Dedupe + memory:** dedupe on a **stable source id** carried in the row (a post id, a
130
156
ticket id, an external primary key) — `dedupe:<domain>:<source_id>`. Don't dedupe on the
131
157
warehouse row id; syncs re-materialize rows.
@@ -135,9 +161,19 @@ it's whatever that upstream system produces.
135
161
you've processed in a scratchpad cursor (`pattern:<domain>:cursor` = "processed through
136
162
{timestamp}") and only look past it each run. The cheap close-out is "has the max
137
163
timestamp advanced past my cursor?"
164
+
-**Sync lag — anchor on the data, not the wall clock.** The sync itself runs behind real
165
+
time (often hours), so a quiet last hour usually means the sync is lagging, not that the
166
+
source went silent. Window your queries relative to the table's own `max(timestamp)`, not
167
+
`now()`, and don't mistake sync lag for "nothing happening".
138
168
-**Timestamp parsing.** Warehouse timestamps are often strings — parse explicitly
139
169
(`parseDateTimeBestEffort(...)`), and confirm which parse functions the table supports
140
170
rather than assuming.
171
+
-**Threaded / conversational sources — the thread is the unit, not the row.** For a Slack
172
+
or Discord channel, a support thread, or any forum-shaped source, a single row is a tiny
173
+
fragment ("they", "i made them") meaningless alone. Aggregate to the thread root
174
+
(e.g. `coalesce(thread_ts, ts)` for Slack), **read the whole thread before judging it**,
175
+
and dedupe on the thread root id, not the message row. A nice touch: reconstruct a
176
+
permalink back to the source thread from its id so the finding links straight to it.
141
177
-**The table may not be in the project profile.** It's a warehouse table, not an event,
142
178
so `project-profile-get` won't list it. Rely on SQL; handle the "table missing entirely"
143
179
case with a `not-in-use:<domain>:team{team_id}` close-out.
@@ -318,6 +354,10 @@ These compose into any pattern above:
318
354
-**Blast-radius corroboration** — turn a qualitative signal into a quantified one by
319
355
cross-checking a second source over the same window. Raises confidence, and
320
356
gives the human a number to act on.
357
+
-**Notebook write-up behind a rich finding.** When a finding carries real analysis (charts,
358
+
a multi-step investigation, several supporting queries), write it up in a notebook with
359
+
`notebooks-create` and link the URL from the finding description, rather than cramming
360
+
everything into the emit prose. The inbox entry stays scannable; the depth is one click away.
0 commit comments