linkedin-studio/commands/trends.md
Kjell Tore Guttormsen 65b60337fe feat(linkedin-studio): N6 — forslags-lag i trends (angle/targetLevel/rationale/relatedIds + selected + --ids + F7/F9-felt actionability/verdict/readerQuestion/painPoint/saturation) [skip-docs]
Artikkelforslaget blir en persistert entitet (steg 2, A1-4) og godkjenningen får
et hjem (steg 3, A1-7) — før dette genererte agenten vinkel/begrunnelse som ble kastet.

Åtte additivt-valgfrie felt på TrendRecord/TrendInput/TrendItem:
- Forslag (A1-4/A1-5): angle · targetLevel (fritekst, brukerdefinert spenn — aldri enum) ·
  rationale · relatedIds[] (flerkilde).
- F7 leser-side: actionability {formulated, note?} (N7-bånd-cap-gaten leser `formulated`) ·
  verdict BÆRENDE/STØTTE/NYHET (lukket mekanisme-vokab).
- F9 leser-side: readerQuestion · painPoint · saturation (N7.5-sveipet fyller dem).

Ingen schema-bump: feltene er additivt-valgfrie, ingen record trenger migrering →
SCHEMA_VERSION forblir 4 (loadStore Math.max håndterer det). First-sight-persistering
(re-capture unionerer topics + re-scorer kun; klobrer aldri en triagert vinkel).

Validering: typede felt (verdict/actionability) feiler hardt ved malformert input;
fritekst/id-liste normaliseres lempelig (summary/topics-idiomet).

Livssyklus: TrendStatus += "selected" → new→selected→acted|skipped. Ny select-verb +
--ids-batch (act/skip/reset/select); partiell suksess = exit 0 + miss-rapport, all-miss = exit 2.
setStatusMany(): ren batch-mutasjon, per-id found/notFound.

Brief: forslagsfeltene rendres per kandidat (detaljert i topp-treff, kompakt token i bullets) +
ny «🚧 I produksjon»-seksjon (selected=valgt + acted=skrevet), pillar-uavhengig, deterministisk
sortert. selected/acted forlater arbeidskøen men vises i produksjons-boardet.

commands/trends.md Step 5 oppgradert: triage → Velg (select) / Skip, batchet per verb (--ids).

Trends-suite 245→266 (ny floor, +21 N6-tester). To eksisterende tester oppdatert for den
endrede brief-kontrakten (acted vises nå i I produksjon; ranking-descriptoren ekskluderer selected).
tsc rent. Roundtrip bevist: capture m/ alle felt → query → brief rendrer feltene → select → I produksjon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7SQpXJpBSNvpWaTNq1kWZ
2026-07-21 09:09:51 +02:00

142 lines
7.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 |
| `--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.
## 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 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