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

9.3 KiB
Raw Blame History

name description allowed-tools
linkedin:trends 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".
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):

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:

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