linkedin-studio/commands/trends.md
Kjell Tore Guttormsen 9de38c4939 feat(linkedin-studio): N7.5 — MR-F9 etterspørsels-sveip (Tier-5-kilder + smertepunkt + vokabular-oversettelse + arc-kontrakt) [skip-docs]
The «innenfra og ut» demand-sweep: the mechanism that FILLS the N6 reader fields
(readerQuestion/painPoint/saturation). Discovery finds "what happened"; this layer
translates it into "the problem the reader is stuck on".

- demand-spotter agent (agents 19->20, inherits session): three passes after
  discovery, before drafting — demand-sweep -> pain-point map -> vocabulary translation.
- Tier 5 demand sources in config/trends-sources.template.md (inverse of Tier 1-4;
  honest blind spots: YouTube API, Reddit approximate, HN/GitHub ground truth).
- demand signal on TrendRecord (strength + answered) — rankable twin of the verbatim
  saturation text; additive-optional, store schema stays v4 (no migration).
- arc.ts (§4 output contract): groupIntoArcs (relatedIds transitive closure),
  rankArcQuestions (etterspørsel x kan-svare x ikke-besvart), classifyMarketGap
  (supply-gap != demand-gap != saturated). Pure + deterministic (TDD).
- arcs CLI verb + /linkedin:trends --demand mode (commands stay 30); morning brief
  shows the per-candidate demand signal.

Suites: trends 276->300, test-runner 139->140, tsc clean. Others unchanged
(brain 134, hooks 140, tests 35, render 60).

MR-F9 built (bygget-men-ubevist) — the (a)/(b)/(c) evidence gate is a runtime
demonstration, proven consumer-side (plugin agents don't resolve in the dev repo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bE7VbkmR3cqHFEeGfzgwb
2026-07-23 22:26:39 +02:00

169 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: linkedin:trends
description: |
Run a trend discovery pass over the user's own content pillars and source list:
delegate the scan to the trend-spotter agent, make sure kept candidates are persisted
to the trend store (dedup) and the dated morning brief is written, then return a
triage-ranked candidate list the user resolves per id (select/skip, batched). Default scoring mode
is long-form (chronicle/newsletter material); `--mode kortform` overrides for feed posts.
Use when the user wants a discovery pass, a trend scan, or a morning-brief refresh.
Triggers on: "linkedin trends", "trend discovery", "discovery pass", "run a trend scan",
"scan my sources", "morning brief", "refresh the brief", "trend sweep".
allowed-tools:
- Read
- Bash
- Task
- AskUserQuestion
---
# LinkedIn Trend Discovery
You are a thin discovery orchestrator. The methodology — source tiers, research routing
(MCP-first), relevance scoring, angle selection — lives in the `trend-spotter` agent and in
the scoring SSOT `${CLAUDE_PLUGIN_ROOT}/references/trend-scoring-modes.md`. Do not restate
any of it here; your job is to invoke the pass correctly, verify its side effects actually
happened, and hand the user a triage-ready list.
Data dir shorthand used below: `${DATA}` = `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}`.
Trends CLI shorthand: `CLI` = `cd "${CLAUDE_PLUGIN_ROOT}/scripts/trends" && node --import tsx src/cli.ts`.
## Step 0: Parse flags
All flags are optional, given after the command name:
| Flag | Meaning | Default |
|------|---------|---------|
| `--mode kortform\|long-form` | Scoring mode (see the SSOT for what each rewards) | **long-form** — no arguments means a long-form discovery pass |
| `--fresh-days N` | Freshness window for the morning brief | CLI default (7) |
| `--brief-only` | Skip the discovery poll entirely; render the brief from the existing store | off |
| `--demand` | Run the **demand-sweep** (`demand-spotter`, «innenfra og ut») instead of supply-side discovery: poll where readers ASK, fill the reader fields, render the §4 arc map | off |
| `--dry-run` | Poll + score, but persist nothing: no capture, no brief, no status writes, no last-run marker | off |
Note the mode inversion deliberately: the **agent's** own default is kortform, this
**command's** default is long-form. That is why Step 2 must always pass the mode explicitly.
**`--demand` reroutes the pass** (Step 2D below) to the demand side — a different agent
(`demand-spotter`), a different unit (the arc/åre, not the news-item), a different output (the §4
arc map, not the pillar brief). The two are complementary: run discovery to find *what happened*,
then `--demand` on a chosen theme to see *what the reader is stuck on*.
## Step 1: Load context
1. **Pillars:** Read `${DATA}/profile/user-profile.md` and extract the content pillars /
expertise areas. If the file does not exist, ask the user for their pillars before
proceeding (one question, comma-separated answer).
2. **Source list:** Resolve which list this pass will use — `${DATA}/trends/sources.md` if it
exists, otherwise the shipped defaults `${CLAUDE_PLUGIN_ROOT}/config/trends-sources.template.md`.
Tell the user which one applies. Do not read research-tooling or route research yourself —
the agent owns Research Routing (its own "Research Routing" section reads the profile's
`### Research Tooling` block); duplicating it here would drift.
## Step 2D: Run the demand-sweep (only on `--demand`)
**If `--demand`:** this replaces Steps 24 (the supply-side discovery pass). Delegate to the
demand-spotter agent — invoke it via `Task` with `subagent_type: linkedin-studio:demand-spotter`
(foreground). The prompt MUST state explicitly:
- The pillars and the resolved source-list path from Step 1 (its **Tier 5** demand sources are what
this pass polls — the inverse of the Tier 14 supply sources).
- The theme(s) to sweep (from `--topics` if given, else the user's pillars).
- That the three passes are mandatory: demand-sweep (verbatim questions + signal strength + is-it-answered) → pain-point map (fills `painPoint`) → vocabulary translation (fills `readerQuestion`).
- That persistence is **mandatory unless `--dry-run`**: the agent runs `capture` with the reader
fields (`readerQuestion`/`painPoint`/`saturation`/`demand`/`verdict`/`actionability`), then renders
the §4 arc map with `CLI arcs`.
- That an **honest null is a finding** — a thin/un-swept source is reported, never filled in.
Then present the arc map the agent returns: per vein, the ranked question inventory (reader's words),
the honest market verdict (supply-gap = write here · demand-gap = high-value/low-audience · saturated
= skip), and the BÆRENDE/STØTTE reading order. On `--dry-run`, present the same map but state that
nothing was persisted. **Skip Steps 24.** Hand off: a chosen vein feeds `/linkedin:newsletter`
Step 1 (its reader question, pain point, and angle prefill the edition).
## Step 2: Run the discovery pass
**If `--brief-only`:** skip the agent entirely — go to Step 3 and render the brief from the
existing store.
Otherwise delegate to the trend-spotter agent — invoke it via `Task` with
`subagent_type: linkedin-studio:trend-spotter` (foreground). The prompt MUST state explicitly:
- **The scoring mode** from Step 0 (default long-form). Never omit it — the agent falls back
to kortform when the caller is silent.
- The pillars and the resolved source-list path from Step 1.
- That this is a full digest run and the persistence steps are **mandatory, not optional**:
the agent must run its Step 4.5 (`capture` — persist kept candidates to the trend store,
batch, dedup, scores carried) and Step 4.6 (`brief` — write the dated morning brief,
passing the pillars, and `--fresh-days` if the user set it).
- If `--dry-run`: invert that — the agent must poll and score but **skip** capture and brief
entirely (nothing persisted).
- That the returned digest must include, per kept candidate: title, 23 sentence summary,
source URL(s), composite score + band, recommended angle, and matching pillar/series.
## Step 3: Verify the side effects (skip on `--dry-run`)
Trust but verify — the pass is only done when its artifacts exist:
1. **Store:** `CLI status --json` — confirm the store mutated (captured count reflects the
run; on a no-new-trends day `{added: 0, merged: N}` is a fine outcome, not a failure).
2. **Brief:** confirm today's file exists: `${DATA}/trends/morning-brief/<today YYYY-MM-DD>.md`.
If the agent captured but failed to render the brief, render it directly — the brief is
deterministic: `CLI brief --pillars "<pillar1,pillar2,…>"` (add `--fresh-days N` if set).
3. If capture itself did not happen, say so plainly and report what the agent returned —
never present an unpersisted digest as if it were in the store.
## Step 4: Present the triage-ranked list
Present the candidates ranked highest composite first (the agent's digest already carries the
ranking — do not re-rank). Per candidate, the contract is:
```
N. [Title] (id: <trend-id>)
Score: X.X — [Band] | Pillar: [pillar/series]
[23 sentence summary]
Source: [URL(s)]
Angle: [recommended angle]
```
Include each candidate's store id (shown in the brief and via `CLI list --json`) — the triage
step below resolves per id. On `--dry-run`, present the same list but say clearly that nothing
was persisted and there are no store ids to triage.
## Step 5: Triage (skip on `--dry-run`)
Resolve the top of the queue now instead of leaving it as homework. For the candidates in the
top bands (Immediate + High; cap at 8), use AskUserQuestion — one question per candidate, up
to 4 candidates per call, options:
- **Velg** — you'll write about this: mark `selected`, moving it onto the brief's "I produksjon"
board (valgt) so the queue stops re-surfacing it while it's in progress
- **Skip** — not for me: mark `skipped` (dropped from the queue)
- **Leave** — keep it in the queue untouched
Then apply the decisions through the store CLI. **Batch by verb** — collect all the "Velg" ids
and all the "Skip" ids and resolve each set in ONE call (ten candidates ≤ two calls):
```bash
CLI select --ids <id1,id2,id3> # everything chosen this pass
CLI skip --ids <id4,id5> # everything rejected this pass
```
Single-id form (`--id <id>`) still works for a one-off. "Leave" means no call. A partial batch
(some id unknown) still applies the matches, reports the misses, and exits 0. Finish with a
one-line summary: N valgt, N skipped, N left in queue. (`act` — already written — and the
auto-`act` when an edition reaches scheduling arrive with the N7 trend→newsletter bridge.)
## Step 6: Write the last-run marker (skip on `--dry-run`)
On completed runs (including `--brief-only`), stamp the marker so other surfaces can tell when
discovery last ran:
```bash
mkdir -p "${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends" && \
date -u +"%Y-%m-%dT%H:%M:%SZ" > "${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/trends/.last-run"
```
## Reference Files
- `${CLAUDE_PLUGIN_ROOT}/references/trend-scoring-modes.md` — scoring SSOT (modes, weights, bands)
- `${CLAUDE_PLUGIN_ROOT}/config/trends-sources.template.md` — shipped source-list defaults (user override: `${DATA}/trends/sources.md`)
- `${CLAUDE_PLUGIN_ROOT}/agents/trend-spotter.md` — the discovery methodology this command invokes