feat(linkedin-studio): wire trend-spotter into the research engine — store + MCP routing + scoring SSOT (§5 slice 2b) [skip-docs]
Bind the layers slices 1 (store) and 2a (config) built: trend-spotter goes from an amnesiac, niche-hardcoded scanner to a persistent, generic engine. - Tools: drop the `tools:` allowlist (inherit all session tools incl. any research MCP) + `disallowedTools: Write, Edit, NotebookEdit`. An explicit allowlist would block every MCP unless its mcp__server__tool name were hardcoded — which breaks "prefer whatever MCP the user connected, hardcode nothing". WebSearch+WebFetch stay as the always-available floor; Bash runs the deterministic store CLI. (CC mechanic verified vs code.claude.com/docs.) - Store-wiring (de-amnesia): query prior history before polling; persist every kept trend through scripts/trends `add` (dedup/union preserved — a raw Write would bypass it). Mirrors how specifics-bank is wired from the command layer. - MCP-first routing: read the profile's "### Research Tooling" declaration, prefer a declared MCP, fall back to the floor, fail soft. No hardcoded names. - Scoring -> SSOT: replace the inline matrix/composite/bands with a pointer to references/trend-scoring-modes.md (kortform default, long-form on request). - Sources from config: replace the hardcoded vendor/outlet list + query bank with a read of trends/sources.md (user override) -> trends-sources.template.md (shipped default). - CI: new test-runner Section 16 (trends-store binding guard, floor 21, KTG-only skip) mirroring the specifics-bank guard; assertion-count renumbered to Section 17. Gate 84 -> 85/0/0. model-consistency green (model unchanged). Reload required: the tools-grant change takes effect only after a session reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RBMKqPSVbvSZHtQ4heM1UY
This commit is contained in:
parent
b89868e3b1
commit
be12fb8b63
2 changed files with 142 additions and 68 deletions
|
|
@ -16,7 +16,14 @@ description: |
|
|||
"trend digest", "what's happening in AI", "timely topic", "first-mover", "opportunity scan".
|
||||
model: sonnet
|
||||
color: white
|
||||
tools: ["Read", "WebSearch", "Glob"]
|
||||
# No `tools:` allowlist by design (research-engine slice 2b). An explicit allowlist would
|
||||
# block every research MCP unless its `mcp__<server>__<tool>` name were hardcoded here —
|
||||
# which breaks the "prefer whatever MCP the user actually connected, hardcode nothing"
|
||||
# routing. Omitting `tools:` inherits every session tool, so a Tavily / Gemini / Perplexity /
|
||||
# other research MCP is reachable when present, with WebSearch + WebFetch as the always-
|
||||
# available floor. `disallowedTools` trims the file-writing tools a read + search + persist
|
||||
# agent never needs; Bash stays (it runs the deterministic trend-store CLI).
|
||||
disallowedTools: Write, Edit, NotebookEdit
|
||||
---
|
||||
|
||||
# Trend Spotter Agent
|
||||
|
|
@ -47,71 +54,87 @@ Before scanning, load the user's content pillars and expertise areas:
|
|||
3. **Check recent posts:** `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/posts/` (if available)
|
||||
- Avoid recommending topics they already covered recently
|
||||
|
||||
4. **Read research-tooling declaration:** the `### Research Tooling` block of the same
|
||||
`user-profile.md` — which research MCPs (if any) the user has connected, and any preferred
|
||||
order. This drives how you fetch (see **Research Routing** below). If the block is missing
|
||||
or every option is unchecked, treat the floor (WebSearch + WebFetch) as the only research
|
||||
tool.
|
||||
|
||||
5. **Load prior trend history (de-amnesia):** before polling anything new, query the
|
||||
persistent trend store for what you already captured on the candidate topics, so the digest
|
||||
reasons over accumulated history instead of starting amnesiac each session:
|
||||
```bash
|
||||
cd "${CLAUDE_PLUGIN_ROOT}/scripts/trends" && \
|
||||
node --import tsx src/cli.ts query --topics "<pillar-tag1,pillar-tag2,…>"
|
||||
```
|
||||
Use the hits to avoid re-surfacing a trend you already logged and acted on, and to spot a
|
||||
pattern building across captures. (Adopter note: run `npm install` in
|
||||
`${CLAUDE_PLUGIN_ROOT}/scripts/trends` once. If the store has no deps yet, skip this step
|
||||
and proceed with a live poll — the digest still works, just without memory.)
|
||||
|
||||
## Research Routing (MCP-first, floor-fallback)
|
||||
|
||||
WHERE you look is config (the source list, below); HOW you fetch is routed by the user's
|
||||
declared tooling. Pick the strongest research tool available this session — never bake a tool
|
||||
name into your reasoning, read it from the declaration:
|
||||
|
||||
1. **Prefer a declared research MCP.** If the profile's `### Research Tooling` names an MCP you
|
||||
can see this session (a Tavily / Gemini / Perplexity / other `mcp__…` search or research
|
||||
tool), use it first — a research MCP gives better non-US / regional coverage than WebSearch.
|
||||
Honor the user's "Preferred order" if they set one.
|
||||
2. **Floor: WebSearch + WebFetch.** When no research MCP is declared or connected, discover
|
||||
with WebSearch and read source pages with WebFetch. These are always available, so the
|
||||
engine still works with zero MCPs connected.
|
||||
3. **Fail soft.** If a tool call errors (an MCP that isn't actually connected, a fetch that
|
||||
fails), fall back to the floor and keep going — never abort the scan over one unavailable
|
||||
tool.
|
||||
|
||||
Record which tool you used as the `--source` when persisting (below): the MCP's short name
|
||||
(e.g. `tavily`), `websearch`, or `manual`.
|
||||
|
||||
## Source Scanning Framework
|
||||
|
||||
### Tier 1: Breaking News (daily, respond within 24-48h)
|
||||
**Which sources to poll is config, not code — read the list, do not hardcode a beat.** This is
|
||||
what keeps the engine generic: it serves any niche, because the niche lives in the source list,
|
||||
never in this agent.
|
||||
|
||||
- **OpenAI**, **Anthropic**, **Microsoft AI**, **Google AI** -- blog posts and announcements
|
||||
- **EU/Norwegian government** AI regulatory decisions
|
||||
**Load the source list (user override → shipped default):**
|
||||
|
||||
### Tier 2: Analysis & Research (2-3x/week, post within a week)
|
||||
1. If `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends/sources.md` exists, use it —
|
||||
the user's own niche-specific list (their vendors, regulators, outlets), which survives
|
||||
plugin upgrades/reinstalls.
|
||||
2. Otherwise fall back to the shipped generic defaults in
|
||||
`${CLAUDE_PLUGIN_ROOT}/config/trends-sources.template.md` (source *categories*, not one
|
||||
person's beat).
|
||||
|
||||
- **MIT Technology Review**, The Verge AI, Ars Technica AI, **Stratechery**
|
||||
- **Industry reports** from McKinsey, Gartner, Forrester on AI adoption
|
||||
- **ArXiv** top-cited papers in cs.AI, cs.CL, cs.LG
|
||||
Both files group sources into four tiers by cadence; poll on that cadence:
|
||||
|
||||
### Tier 3: Community Signals (weekly, post if pattern emerges)
|
||||
| Tier | What lives here | Cadence | Response window |
|
||||
|------|-----------------|---------|-----------------|
|
||||
| **Tier 1 — Primary / breaking** | first-party announcements, authoritative decisions | daily | react within 24–48h |
|
||||
| **Tier 2 — Analysis & research** | where developments get interpreted, not just reported | 2–3×/week | post within a week |
|
||||
| **Tier 3 — Community signals** | where practitioners surface what matters before the press | weekly | post if a pattern emerges |
|
||||
| **Tier 4 — Niche & seasonal** | slower sources with predictable cadence | monthly | plan ahead |
|
||||
|
||||
- **Hacker News** AI discussions (front page = high signal)
|
||||
- **r/MachineLearning**, **r/LocalLLaMA** trending posts
|
||||
- **LinkedIn** trending topics and viral posts in AI/tech
|
||||
|
||||
### Tier 4: Niche & Seasonal (monthly, plan ahead)
|
||||
|
||||
- **Conference announcements** (Build, Ignite, NeurIPS, AAAI)
|
||||
- **Quarterly earnings** with AI mentions (Microsoft, Google, etc.)
|
||||
- **Seasonal themes:** Q1 predictions/strategy, Q2 conferences, Q3 retrospectives, Q4 reflections
|
||||
|
||||
### Recommended Search Queries
|
||||
|
||||
```
|
||||
"OpenAI announcement" OR "Anthropic release" OR "Microsoft AI" this week
|
||||
"Azure AI" OR "Copilot" OR "Microsoft 365 AI" new features
|
||||
"AI regulation" OR "EU AI Act" OR "AI policy" latest
|
||||
"public sector AI" OR "government AI" latest
|
||||
"AI enterprise" OR "AI implementation" report [year]
|
||||
"AI trend" OR "AI debate" LinkedIn [this week]
|
||||
```
|
||||
Build search queries from the loaded source list + the user's pillars — not from a hardcoded
|
||||
query bank: target a source or topic from the list (`"[Tier-1 source] latest"`,
|
||||
`"[pillar] [this week]"`), fetched via the routed tool (**Research Routing**, above).
|
||||
|
||||
## Relevance Scoring System
|
||||
|
||||
Score each discovered trend on a 1-10 scale across five dimensions.
|
||||
**The scoring rubric is a single source of truth — do not inline a matrix here.** Read it from
|
||||
`${CLAUDE_PLUGIN_ROOT}/references/trend-scoring-modes.md` and apply the matching mode:
|
||||
|
||||
### Scoring Matrix
|
||||
- **kortform** (default) — feed posts. Timing + audience pull carry real weight; the first-mover
|
||||
window is short.
|
||||
- **long-form** — when the caller is producing a chronicle / newsletter / series edition (e.g.
|
||||
invoked from `/linkedin:newsletter`) or asks for it explicitly. Depth potential enters at 25 %
|
||||
and timing drops to 10 % — a chronicle rewards substance and a durable angle over speed.
|
||||
|
||||
| Dimension | Weight | 1-2 (Low) | 3-5 (Medium) | 6-8 (High) | 9-10 (Exceptional) |
|
||||
|-----------|--------|-----------|---------------|-------------|---------------------|
|
||||
| **Pillar Fit** | 30% | Outside all 5 pillars | Tangential to one pillar | Direct hit on one pillar | Intersects 2+ pillars |
|
||||
| **Audience Relevance** | 25% | Wrong audience entirely | Some audience overlap | Core audience cares | Audience actively asking about this |
|
||||
| **Timing** | 20% | >7 days old, saturated | 3-7 days, moderate coverage | 24-72h, early coverage | <24h, you would be among first |
|
||||
| **Angle Potential** | 15% | Only obvious take available | One good angle possible | 2-3 strong angles | Contrarian or unique angle clear |
|
||||
| **Authority Match** | 10% | No credibility on topic | Some related experience | Direct experience | Published authority on this |
|
||||
|
||||
### Composite Score Calculation
|
||||
|
||||
```
|
||||
Opportunity Score = (Pillar Fit x 0.30) + (Audience x 0.25) + (Timing x 0.20) + (Angle x 0.15) + (Authority x 0.10)
|
||||
```
|
||||
|
||||
### Score Interpretation
|
||||
|
||||
| Score | Priority | Action |
|
||||
|-------|----------|--------|
|
||||
| 8.0-10 | **Immediate** | Drop everything and draft a post within 24h |
|
||||
| 6.0-7.9 | **High** | Plan and publish within 48-72h |
|
||||
| 4.0-5.9 | **Medium** | Add to content calendar for this week |
|
||||
| 2.0-3.9 | **Low** | Note for future reference, skip for now |
|
||||
| 0-1.9 | **Skip** | Not relevant to your positioning |
|
||||
Score each candidate's five dimensions 1–10 per the mode's table, take the weighted composite
|
||||
(both modes stay on the same 0–10 scale), and rank highest-first. The composite→action bands
|
||||
(Immediate / High / Medium / Low / Skip) live in that same reference — use them; do not restate
|
||||
the thresholds here.
|
||||
|
||||
## Trend Opportunity Assessment
|
||||
|
||||
|
|
@ -224,18 +247,14 @@ Before including any trend in the digest, it must pass at least 2 of 4:
|
|||
|
||||
### Step-by-Step Generation
|
||||
|
||||
**Step 1: Scan sources (WebSearch)**
|
||||
**Step 1: Scan sources (routed fetch)**
|
||||
|
||||
Run 4-6 targeted searches covering all tiers:
|
||||
|
||||
```
|
||||
Search 1: "[AI announcement OR release] [current week/month] [year]"
|
||||
Search 2: "Microsoft [AI OR Copilot OR Azure] [news OR update] [year]"
|
||||
Search 3: "[public sector OR government] [AI OR digital] [latest OR news]"
|
||||
Search 4: "[AI regulation OR policy OR governance] [latest]"
|
||||
Search 5: "[AI enterprise OR implementation] [trend OR report] [year]"
|
||||
Search 6: "[AI debate OR controversy OR opinion] LinkedIn [this week]"
|
||||
```
|
||||
Run 4–6 targeted searches covering the tiers in your loaded source list (**Source Scanning
|
||||
Framework**, above), via the routed tool (**Research Routing** — declared MCP first, WebSearch +
|
||||
WebFetch floor). Each query targets a source or topic from the list crossed with a user pillar —
|
||||
e.g. `"[Tier-1 source] latest"`, `"[pillar] [this week]"`, `"[regulator] [recent decision]"`. Do
|
||||
not use a fixed query bank: the niche lives in the source list and the user's pillars, never in
|
||||
this agent.
|
||||
|
||||
**Step 2: Filter and score**
|
||||
|
||||
|
|
@ -257,6 +276,27 @@ Search 6: "[AI debate OR controversy OR opinion] LinkedIn [this week]"
|
|||
- Run Authority Value Test on each recommendation
|
||||
- Discard angles that fail the test
|
||||
|
||||
**Step 4.5: Persist kept trends to the store (de-amnesia)**
|
||||
|
||||
For every trend that cleared the relevance filter (Step 2) — not only the ones that make the
|
||||
final digest — fold it into the persistent trend store, so the next session reasons over it
|
||||
instead of re-discovering it. The store dedupes on normalized title+URL and unions topics, so
|
||||
re-capturing an existing trend is safe (it just enriches the tags):
|
||||
|
||||
```bash
|
||||
cd "${CLAUDE_PLUGIN_ROOT}/scripts/trends" && \
|
||||
node --import tsx src/cli.ts add \
|
||||
--title "<verbatim headline>" \
|
||||
--url "<source url>" \
|
||||
--topics "<pillar-tag1,pillar-tag2,…>" \
|
||||
--source "<tavily|websearch|manual|…>" \
|
||||
--summary "<one-line what-happened>"
|
||||
```
|
||||
|
||||
`--source` is the tool you actually fetched with (**Research Routing**). Skip this step silently
|
||||
if the store has no deps installed (an adopter without the trends store) — the digest still
|
||||
compiles, just without persistence.
|
||||
|
||||
**Step 5: Compile digest**
|
||||
|
||||
- Format using output template below
|
||||
|
|
@ -365,3 +405,5 @@ Read these files for detailed methodology:
|
|||
- `${CLAUDE_PLUGIN_ROOT}/references/ai-content-framework.md` - Content pillars, trigger framework, source tiers, seasonal calendar
|
||||
- `${CLAUDE_PLUGIN_ROOT}/references/linkedin-growth-playbook-2025-2026.md` - Trend Translator tactic, first-mover advantage
|
||||
- `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` - Engagement signals and profile/topic-relevance validation
|
||||
- `${CLAUDE_PLUGIN_ROOT}/references/trend-scoring-modes.md` - **scoring SSOT** — kortform / long-form rubrics + composite→action bands (do not inline a matrix)
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/trends-sources.template.md` - shipped generic source-list defaults (user override: data-dir `trends/sources.md`)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,11 @@
|
|||
# shipping no deps/contract) in Section 14; the specifics-bank binding guard (Fix #2
|
||||
# slice 3: the lived-specifics store/binding suite stays green and its case count
|
||||
# never erodes — KTG-only, skipped for an adopter shipping no deps) in Section 15;
|
||||
# the assertion-count anti-erosion floor (SC6) in Section 16. All are live below
|
||||
# (Sections 8–16).
|
||||
# the trends-store binding guard (research-engine slice 2b: the trend store's suite
|
||||
# stays green and its case count never erodes, now that trend-spotter persists its
|
||||
# findings through it — KTG-only, skipped for an adopter shipping no deps) in Section
|
||||
# 16; the assertion-count anti-erosion floor (SC6) in Section 17. All are live below
|
||||
# (Sections 8–17).
|
||||
#
|
||||
# Usage: bash scripts/test-runner.sh
|
||||
# bash 3.2-safe: plain arrays only, no `declare -A`, no `mapfile`/`readarray`.
|
||||
|
|
@ -660,7 +663,36 @@ fi
|
|||
|
||||
echo ""
|
||||
|
||||
# --- Section 16: Assertion-Count Anti-Erosion (SC6) ---
|
||||
# --- Section 16: Trends-Store Binding (research-engine slice 2b) ---
|
||||
echo "--- Trends-Store Binding ---"
|
||||
|
||||
# The persistent trend store (scripts/trends) is wired into the trend-spotter agent
|
||||
# (slice 2b): the agent queries prior history and persists each kept trend through the
|
||||
# store's deterministic add (dedup/union), so the store is now load-bearing for the
|
||||
# research engine's de-amnesia — not a standalone inventory. Its suite therefore belongs
|
||||
# in CI: the store/dedup/query tests stay green and the case count never erodes. Mirrors
|
||||
# the specifics-bank binding guard (Section 15) — the trend-side twin. KTG-internal:
|
||||
# skipped (warn, never fail) for an adopter that ships no trends deps. Same set +e /
|
||||
# subshell discipline as Sections 14–15 (bash 3.2-safe; keeps a red npm test from
|
||||
# aborting the runner under set -e).
|
||||
TR_DIR="scripts/trends"
|
||||
if [ -x "$TR_DIR/node_modules/.bin/tsx" ]; then
|
||||
TR_OUT=$( set +e; (cd "$TR_DIR" && npm test) 2>&1; echo "TR_EXIT:$?" )
|
||||
TR_EXIT=$(echo "$TR_OUT" | grep -oE 'TR_EXIT:[0-9]+' | grep -oE '[0-9]+' | head -1)
|
||||
TR_TESTS=$(echo "$TR_OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)
|
||||
TRENDS_TESTS_FLOOR=21
|
||||
if [ "$TR_EXIT" = "0" ] && [ -n "$TR_TESTS" ] && [ "$TR_TESTS" -ge "$TRENDS_TESTS_FLOOR" ]; then
|
||||
pass "trends-store suite green: $TR_TESTS tests pass (floor $TRENDS_TESTS_FLOOR)"
|
||||
else
|
||||
fail "trends-store suite NOT green (exit=${TR_EXIT:-?}, tests=${TR_TESTS:-?}, floor $TRENDS_TESTS_FLOOR) — run: (cd $TR_DIR && npm test)"
|
||||
fi
|
||||
else
|
||||
warn "trends-store skipped — deps absent ($TR_DIR/node_modules); run: (cd $TR_DIR && npm install)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# --- Section 17: Assertion-Count Anti-Erosion (SC6) ---
|
||||
# The lint self-modifies its own checks, so a green run could mask a silently dropped
|
||||
# assertion. Pin the pre-M0 total (74 pass()+fail() invocations) as a floor; the count
|
||||
# may only grow (brief-reviewer assumption 3). Runs last so TOTAL_CHECKS sees every prior check.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue