feat(linkedin-studio): research-engine config layer — sources + scoring modes + MCP profile (§5 slice 2a) [skip-docs]
Declaration/config groundwork that slice 2b's trend-spotter upgrade reads.
Standalone (no agent wiring yet — that's 2b), mirroring slice 1's pattern;
[skip-docs] for the same reason slice 1 was — user-facing docs land when 2b
wires the engine live.
- references/trend-scoring-modes.md: methodology SSOT for two rubrics —
kortform (feed post, timing 20%) + long-form (chronicle, depth 25% / timing 10%,
per tema-research-motor-spec §4.2). Both sum to 100%. trend-spotter renders from
this in 2b instead of inlining a matrix (S12-consistent).
- config/trends-sources.template.md: shipped generic source-list defaults →
user override at ${LINKEDIN_STUDIO_DATA}/trends/sources.md (data-dir, survives
reinstall; same template->data-dir pattern as user-profile).
- user-profile.template.md: new "Research Tooling" section — declared research MCPs
(Tavily/Gemini/Perplexity/Other) + WebSearch/WebFetch floor. 2b routes MCP-first.
- setup.md Step 3f + onboarding.md Phase 2: ask "which research MCPs?" -> profile.
Store only what the user declares; no hard-coded MCP names.
- test-runner.sh: EXPECT_REFS 26->27; generalized the M0 +1 delta-guard into a
named-post-M0-additions guard (POSTM0_REFS) so a legit later ref doc passes while
the anti-masking intent holds. Gate green 84/0/0.
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
be21788321
commit
b89868e3b1
6 changed files with 225 additions and 11 deletions
|
|
@ -154,6 +154,7 @@ file must contain no `<!-- VOICE_PLACEHOLDER -->`.
|
|||
3. Job title / role
|
||||
4. 5 expertise areas (these become your content pillars)
|
||||
5. Target audience description
|
||||
6. Research MCPs connected (Tavily / Gemini deep research / Perplexity — or "none"; WebSearch + WebFetch are the always-available floor). Store only what they name — don't invent MCP names.
|
||||
|
||||
Save to `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/user-profile.md`.
|
||||
|
||||
|
|
|
|||
|
|
@ -336,8 +336,13 @@ Guide through each section of the profile:
|
|||
- "Current follower count?"
|
||||
- "90-day growth goal?"
|
||||
|
||||
7. Read `config/user-profile.template.md` for structure
|
||||
8. Write the completed profile to `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/user-profile.md`
|
||||
7. **Research tooling:**
|
||||
- "Which research MCPs do you have connected? (e.g., Tavily, Gemini deep research, Perplexity — or none)"
|
||||
- Record exactly what they name. If they have none, that's fine — WebSearch + WebFetch are the always-available floor.
|
||||
- This populates the **Research Tooling** section of the profile; the trend/research engine routes to a declared MCP first and falls back to the floor. Do not invent MCP names — store only what the user declares.
|
||||
|
||||
8. Read `config/user-profile.template.md` for structure
|
||||
9. Write the completed profile to `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/user-profile.md`
|
||||
|
||||
**Important:** This file is gitignored (`.local.md` pattern), so personal data stays private.
|
||||
|
||||
|
|
|
|||
76
config/trends-sources.template.md
Normal file
76
config/trends-sources.template.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Trend Sources (template)
|
||||
|
||||
The **source list** a research-engine pass polls for trend/topic candidates. This file
|
||||
ships **generic, niche-agnostic defaults** (source *categories*, not one person's beat).
|
||||
Override it with your own list — the niche specifics (your vendors, your regulators, your
|
||||
country's outlets) belong in the override, never here.
|
||||
|
||||
## How the override works
|
||||
|
||||
Copy this template into your per-user data dir and edit the copy:
|
||||
|
||||
```bash
|
||||
mkdir -p "${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends"
|
||||
cp config/trends-sources.template.md \
|
||||
"${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends/sources.md"
|
||||
```
|
||||
|
||||
A pass reads `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends/sources.md`
|
||||
if it exists, and falls back to these shipped defaults otherwise. The override lives in the
|
||||
data dir (next to the trend store `trends/trends.json`), so it **survives plugin
|
||||
upgrades and reinstalls**. `LINKEDIN_STUDIO_DATA` overrides the root.
|
||||
|
||||
Format below is read by a person and an agent both: a markdown list, one source per line,
|
||||
grouped by tier. Keep `Name — URL — note` so a poll can cite the URL.
|
||||
|
||||
---
|
||||
|
||||
## Tier 1 — Primary / breaking (poll daily, react within 24–48h)
|
||||
|
||||
*First-party announcements and authoritative decisions in your field.*
|
||||
|
||||
- [Major vendor blog] — [url] — releases & announcements
|
||||
- [Major vendor blog] — [url] — releases & announcements
|
||||
- [Regulator / governing body] — [url] — decisions & guidance
|
||||
|
||||
## Tier 2 — Analysis & research (poll 2–3×/week, post within a week)
|
||||
|
||||
*Where a development gets interpreted, not just reported.*
|
||||
|
||||
- [Respected analysis publication] — [url]
|
||||
- [Analyst house report stream] — [url] — adoption / market reports
|
||||
- [Research index] — [url] — preprints / papers in your area
|
||||
|
||||
## Tier 3 — Community signals (poll weekly, post if a pattern emerges)
|
||||
|
||||
*Where practitioners surface what actually matters before the press does.*
|
||||
|
||||
- [Practitioner forum / aggregator] — [url]
|
||||
- [Topic-specific community] — [url]
|
||||
- [Platform-native trending] — [url]
|
||||
|
||||
## Tier 4 — Niche & seasonal (poll monthly, plan ahead)
|
||||
|
||||
*Slower-moving sources with predictable cadence.*
|
||||
|
||||
- [Key conference series] — [url] — announcement / agenda cycles
|
||||
- [Earnings / report calendar] — [url] — scheduled releases
|
||||
- Seasonal themes: [Q1 …] · [Q2 …] · [Q3 …] · [Q4 …]
|
||||
|
||||
---
|
||||
|
||||
## Your niche additions
|
||||
|
||||
Add the sources specific to your field below — these are the ones the generic defaults
|
||||
above cannot know. (Example shape for a Norwegian public-sector / AI niche: national
|
||||
digitalisation agency, data-protection authority, the relevant ministries, the local
|
||||
tech press — replace with yours.)
|
||||
|
||||
- [Your source] — [url] — [why it matters to your audience]
|
||||
- [Your source] — [url] — [why it matters to your audience]
|
||||
|
||||
---
|
||||
|
||||
*Tip: the [8 universal angles](../references/content-angles.md) and the scoring rubric
|
||||
in [trend-scoring-modes.md](../references/trend-scoring-modes.md) decide what to DO with a
|
||||
candidate once a poll surfaces it. This file only decides WHERE to look.*
|
||||
|
|
@ -116,6 +116,27 @@ cp config/user-profile.template.md config/user-profile.local.md
|
|||
|
||||
---
|
||||
|
||||
### Research Tooling
|
||||
|
||||
**Which research MCPs do you have available?** The trend / research engine routes to a
|
||||
declared MCP **first** and falls back to the always-available floor when none is declared.
|
||||
Check what you actually have connected — leave the rest unchecked. (Names are examples;
|
||||
add whatever you have under "Other" — nothing here is hard-coded downstream.)
|
||||
|
||||
- [ ] Tavily (`tavily_search` / `tavily_research`)
|
||||
- [ ] Gemini deep research (`gemini_deep_research`)
|
||||
- [ ] Perplexity
|
||||
- [ ] Other: [name the MCP + what it's good for]
|
||||
|
||||
**Always-available floor (no MCP needed):** WebSearch + WebFetch. These are used when no
|
||||
research MCP is declared above. (Note: WebSearch is US-biased — a research MCP gives
|
||||
better coverage for non-US / regional sources.)
|
||||
|
||||
**Preferred order (optional):** [e.g. "Tavily for niche/regional, Gemini for deep dives,
|
||||
WebSearch as fallback" — or leave blank to let the engine pick]
|
||||
|
||||
---
|
||||
|
||||
### Asset Utilization Preferences
|
||||
|
||||
**When creating content, Claude should:**
|
||||
|
|
|
|||
100
references/trend-scoring-modes.md
Normal file
100
references/trend-scoring-modes.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Trend Scoring Modes Reference
|
||||
|
||||
**Single source of truth** for how a discovered trend/topic candidate is scored.
|
||||
There are **two modes** — they share the same 1–10 per-dimension scale and the same
|
||||
composite formula, but they weight the dimensions differently because a feed post and
|
||||
a long-form chronicle reward different things. Surfaces (the `trend-spotter` agent, any
|
||||
research-engine pass) **select a mode and apply the matching rubric** — they do not
|
||||
restate the weights inline. Cite this file; do not duplicate it.
|
||||
|
||||
This file defines **methodology** (the weights), not user preference. The *source list*
|
||||
a pass polls is separate and user-overridable — see
|
||||
`config/trends-sources.template.md` → `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends/sources.md`.
|
||||
|
||||
## How to read this file
|
||||
|
||||
- Score each dimension **1–10** (see the bands in the per-mode tables).
|
||||
- Composite = the weighted sum of the five dimension scores. Each mode's weights sum to
|
||||
**100 %**, so the composite stays on the same 0–10 scale across modes.
|
||||
- The **ordering** of the weights is the signal; the exact percentages are a deliberate,
|
||||
documented choice, not a measured coefficient. (Timing grounding — why a feed post
|
||||
weights recency and a chronicle does not — traces to
|
||||
`references/algorithm-signals-reference.md`; this file does not restate algorithm
|
||||
magnitudes.)
|
||||
|
||||
## Mode selection
|
||||
|
||||
| Mode | Use for | Driver |
|
||||
|------|---------|--------|
|
||||
| **kortform** | feed posts (`/linkedin:post`, `:quick`, `:react`, `:carousel`, `:video`) | timing + audience pull — a good post published early beats a perfect post published late |
|
||||
| **long-form** | chronicles / newsletters / series editions (`/linkedin:newsletter`) | depth + angle — a chronicle has a longer shelf life; whether there is *enough material for a full piece* matters more than a 24-hour window |
|
||||
|
||||
If no mode is declared, default to **kortform** (the feed is the higher-cadence surface).
|
||||
A caller may pass the mode explicitly (e.g. the long-form orchestrator requesting
|
||||
`long-form`); a future slice may read a per-user default from the profile.
|
||||
|
||||
## Mode: kortform (LinkedIn feed post)
|
||||
|
||||
Tuned for the feed: timing and audience pull carry real weight because the first-mover
|
||||
window is short.
|
||||
|
||||
| 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 = (Pillar × 0.30) + (Audience × 0.25) + (Timing × 0.20) + (Angle × 0.15) + (Authority × 0.10)
|
||||
```
|
||||
|
||||
## Mode: long-form (chronicle / newsletter / series)
|
||||
|
||||
Reweighted for a longer piece: **depth potential** enters at 25 %, and **timing drops to
|
||||
10 %** — a chronicle is not a 24-hour reaction, so recency matters less than whether the
|
||||
topic has enough substance and a differentiated angle to carry a full edition.
|
||||
|
||||
| Dimension | Weight | 1–2 (Low) | 3–5 (Medium) | 6–8 (High) | 9–10 (Exceptional) |
|
||||
|-----------|--------|-----------|---------------|-------------|---------------------|
|
||||
| **Pillar / thesis fit** | 30 % | Outside the active theses | Tangential to one thesis | Fits one thesis / series arc | Advances 2+ theses or a live series arc |
|
||||
| **Depth potential** | 25 % | A single post at most | Enough for one solid post | Enough for a full chronicle | Enough for a multi-part series |
|
||||
| **Angle / differentiation** | 20 % | Commodity take only | One non-obvious angle | 2–3 differentiated angles | Original thesis the field lacks |
|
||||
| **Authority / experience** | 15 % | No credibility on topic | Some related experience | Direct lived experience | Published authority on this |
|
||||
| **Currency** | 10 % | Stale / already resolved | Relevant this quarter | Relevant now, durable | Relevant now AND will compound |
|
||||
|
||||
```
|
||||
Composite = (Pillar × 0.30) + (Depth × 0.25) + (Angle × 0.20) + (Authority × 0.15) + (Currency × 0.10)
|
||||
```
|
||||
|
||||
## Why the two modes differ (the one delta)
|
||||
|
||||
The two rubrics are the same five-dimension instrument with one deliberate swap:
|
||||
|
||||
- **kortform:** Timing **20 %**, no depth dimension — the feed rewards being early.
|
||||
- **long-form:** Timing → Currency at **10 %**, Depth potential added at **25 %** — a
|
||||
chronicle rewards substance and a durable angle over speed.
|
||||
|
||||
Pillar fit (30 %) and authority (kortform 10 % / long-form 15 %) anchor both: an off-pillar
|
||||
or low-credibility topic scores low in either mode, because topic relevance and credibility
|
||||
are non-negotiable regardless of format.
|
||||
|
||||
## Composite → action
|
||||
|
||||
The same priority bands apply to both modes (the composite is on the same 0–10 scale):
|
||||
|
||||
| Composite | Priority | kortform action | long-form action |
|
||||
|-----------|----------|-----------------|------------------|
|
||||
| 8.0–10 | **Immediate** | Draft within 24h | Promote to the edition backlog now |
|
||||
| 6.0–7.9 | **High** | Publish within 48–72h | Strong edition candidate — schedule it |
|
||||
| 4.0–5.9 | **Medium** | Add to this week's calendar | Hold as a backlog candidate, revisit |
|
||||
| 2.0–3.9 | **Low** | Note, skip for now | Park unless the angle sharpens |
|
||||
| 0–1.9 | **Skip** | Off positioning | Off positioning |
|
||||
|
||||
## Consumers
|
||||
|
||||
- `agents/trend-spotter.md` — reads the requested mode and applies the matching rubric
|
||||
instead of inlining a matrix (wired in research-engine slice 2b).
|
||||
- Any future research-engine pass that scores candidates before writing them to the trend
|
||||
store (`scripts/trends/`).
|
||||
|
|
@ -53,12 +53,16 @@ warn() { echo -e "${YELLOW}⚠${NC} $1"; WARN=$((WARN + 1)); }
|
|||
# with the files when adding/removing an agent, command, reference, or skill.
|
||||
EXPECT_AGENTS=19
|
||||
EXPECT_COMMANDS=29
|
||||
EXPECT_REFS=26
|
||||
EXPECT_REFS=27
|
||||
EXPECT_SKILLS=6
|
||||
# Pre-M0 references/ baseline was 25; M0 adds exactly one ref doc
|
||||
# (references/data-path-convention.md). The "delta = +1" assert below proves the
|
||||
# bump was a single intended addition, not a larger jump masking an extra doc (m3/m11).
|
||||
# Pre-M0 references/ baseline was 25. Every ref doc added since is NAMED below, so the
|
||||
# count bump always maps to an intended, named addition — never an incidental doc masked
|
||||
# by the bump (m3/m11). M0 added data-path-convention.md; each later slice appends its
|
||||
# doc to POSTM0_REFS. The assert below proves EXPECT_REFS == 25 + 1 (M0) + |POSTM0_REFS|
|
||||
# AND that every named doc actually exists. bash 3.2-safe: plain indexed array.
|
||||
REFS_BASELINE_PRE_M0=25
|
||||
M0_REF="references/data-path-convention.md"
|
||||
POSTM0_REFS=("references/trend-scoring-modes.md") # research-engine slice 2a (scoring SSOT)
|
||||
|
||||
echo "================================================"
|
||||
echo "LinkedIn Studio Plugin — Structure Validator"
|
||||
|
|
@ -101,12 +105,19 @@ assert_count "commands/*.md" "$COMMANDS" "$EXPECT_COMMANDS"
|
|||
assert_count "references/*.md" "$REFS" "$EXPECT_REFS"
|
||||
assert_count "skills/*/SKILL.md" "$SKILLS" "$EXPECT_SKILLS"
|
||||
|
||||
# M0 references/ delta = exactly +1, and the one added doc is the D3 convention.
|
||||
# Guards against the bump silently absorbing an incidental extra ref doc (m3/m11).
|
||||
if [ "$EXPECT_REFS" -eq $((REFS_BASELINE_PRE_M0 + 1)) ] && [ -f references/data-path-convention.md ]; then
|
||||
pass "references/ delta = exactly +1 (M0 added data-path-convention.md; ${REFS_BASELINE_PRE_M0}→${EXPECT_REFS})"
|
||||
# references/ count must map 1:1 to the NAMED additions (M0 + every later slice's doc),
|
||||
# and each named doc must exist. Guards against the bump silently absorbing an incidental
|
||||
# extra ref doc (m3/m11). To add a ref doc: append it to POSTM0_REFS and bump EXPECT_REFS.
|
||||
NAMED_REFS_OK=1
|
||||
[ -f "$M0_REF" ] || NAMED_REFS_OK=0
|
||||
for r in "${POSTM0_REFS[@]}"; do
|
||||
[ -f "$r" ] || NAMED_REFS_OK=0
|
||||
done
|
||||
EXPECT_NAMED=$((REFS_BASELINE_PRE_M0 + 1 + ${#POSTM0_REFS[@]}))
|
||||
if [ "$EXPECT_REFS" -eq "$EXPECT_NAMED" ] && [ "$NAMED_REFS_OK" -eq 1 ]; then
|
||||
pass "references/ count maps to named additions (${REFS_BASELINE_PRE_M0} baseline +1 M0 +${#POSTM0_REFS[@]} post-M0 = ${EXPECT_REFS}; all named docs exist)"
|
||||
else
|
||||
fail "references/ delta != +1 — M0 must add exactly one ref doc (the D3 convention), not $((EXPECT_REFS - REFS_BASELINE_PRE_M0))"
|
||||
fail "references/ count != named additions — bump EXPECT_REFS and name the doc in POSTM0_REFS (expected ${EXPECT_NAMED}, have ${EXPECT_REFS}; named-docs-exist=${NAMED_REFS_OK})"
|
||||
fi
|
||||
|
||||
# Cross-check the CLAUDE.md declared headers against the contract (doc-drift guard)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue