Compare commits
18 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d71941072d | |||
| 66fd00c005 | |||
| 7e5133c8a6 | |||
| 978601447f | |||
| 3290e8fb7d | |||
| 63745ddcab | |||
| 24ac8dbe02 | |||
| 83b054aad4 | |||
| 51bb5a6fb2 | |||
| bc47c1842a | |||
| e38f71f418 | |||
| 77426135a5 | |||
| af80150019 | |||
| 8c91c409cd | |||
| 885526738c | |||
| e7276cf8bb | |||
| ab091121d9 | |||
| b5a7125e19 |
66 changed files with 1694 additions and 526 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "linkedin-studio",
|
"name": "linkedin-studio",
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"description": "LinkedIn Studio — full-spectrum LinkedIn content engine: feed posts, carousels, video scripts, and long-form newsletter editions, with the 2026 relevance-ranking model baked in. v4.0.0 is an audit-remediation release (Voyage Phase 0–3): every user-facing claim is made honest or removed, all 11 previously-orphaned agents are wired (→ 19 agents), a `/linkedin:firsthour` post-publish command is added (→ 27 commands), the algorithm-signal claims are reconciled to one sourced statement (no unpublishable model name or date), short-form de-AI and video quality gates are added, and the structure lint is rebuilt to guard the real layout plus version/count/stat consistency. Breaking: the newly-wired agents register only on reinstall/reload, and this consolidates the v3.0.0 identity break (slug, agent namespace `linkedin-studio:<agent>`, state-file path `~/.claude/linkedin-studio.local.md`). v3.1.0 added the cold adversarial review package (`/linkedin:headless-review` + Step 6.5 + `/linkedin:pivot` + per-artifact personas); the `/linkedin:*` commands are unchanged. v4.1.0 adds a journey layer: two guided front-doors (`/linkedin:create`, `/linkedin:measure`) plus a router re-tiered into five journeys (Start · Create · Engage · Measure · Grow), with the 27 existing commands kept as the execution tier (→ 29 commands; additive, reload registers the two new commands).",
|
"description": "LinkedIn Studio — full-spectrum LinkedIn content engine: feed posts, carousels, video scripts, and long-form newsletter editions, with the 2026 relevance-ranking model baked in. Commands are organized into five journeys (Start · Create · Engage · Measure · Grow) over an execution tier of atomic commands, backed by agents for drafting, review, analytics and strategy, and by hooks that gate content quality, voice drift and topic rotation as you write. The long-form pipeline is a multi-phase orchestrator with gates before prose (skeleton + spine), a deterministic contract-gate, fact-check, editorial review, and a cold adversarial review package run on a frozen draft — all before lock. Analytics import, weekly reports and A/B tests feed back into creation through a do-next contract, so a measurement changes the next piece instead of dying in chat. Domain-general by design: expertise areas, reader personas, publishing slots, brand tokens and language rules come from your own config — none of it is hardcoded. Version history is in CHANGELOG.md.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Kjell Tore Guttormsen"
|
"name": "Kjell Tore Guttormsen"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
26
CHANGELOG.md
26
CHANGELOG.md
|
|
@ -7,9 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added — unattended discovery, `--headless` (RE-R3f / MR-F5)
|
||||||
|
|
||||||
|
- **`/linkedin:trends --headless`** — the full poll → score → capture → brief loop, safe to fire
|
||||||
|
with no operator present, so Sunday-morning discovery can run on a schedule. Fails fast instead of
|
||||||
|
asking for pillars when the profile is missing; skips triage unconditionally (candidates stay
|
||||||
|
`pending` for the next attended session); refuses `--demand --headless` rather than silently
|
||||||
|
degrading to supply-side discovery. Recommended trigger is Claude Code's own Desktop Scheduled
|
||||||
|
Tasks; a fallback hand-rolled `claude -p` recipe is documented (`scripts/trends/README.md`), not
|
||||||
|
built as repo code. No new command, agent, or store field — a command-contract change plus docs.
|
||||||
|
- **Gate note:** this opens the research-engine's own 2026-06-24 decision that AI-capture autonomy
|
||||||
|
(slice e) sits behind a re-evaluation gate against v1.0.0 product maturity. The conflict was
|
||||||
|
surfaced to the operator explicitly and the gate was opened deliberately for the production need
|
||||||
|
(Maskinrommet feedback register, MR-F5), not silently bypassed. See `docs/research-engine/
|
||||||
|
brief-re-r3f.md`.
|
||||||
|
|
||||||
## [0.8.0] - 2026-07-27
|
## [0.8.0] - 2026-07-27
|
||||||
|
|
||||||
**The production line, closed.** 0.7.0 built the bridge from discovery to a scheduled edition; 0.8.0 makes the whole line hold together and, for the first time, feed back into itself. A candidate now travels id-traceably from capture to a scheduled slot with **no manual re-entry anywhere in the chain** (proven end-to-end before this release). A measurement now changes the next piece instead of dying in chat. A correction made once is enforced from then on. Voice is bound *before* prose rather than scrubbed after. The series remembers what it has already spent. And the engine finally knows your own normal well enough to **refuse a verdict** when the data is too thin — the answer that keeps everything above it honest.
|
**The production line, closed.** 0.7.0 built the bridge from discovery to a scheduled edition; 0.8.0 makes the whole line hold together and, for the first time, feed back into itself. A candidate now travels id-traceably from capture to a scheduled slot with **no manual re-entry anywhere in the chain** — exercised before this release with the CLI layers run for real and the command steps simulated, since plugin command and agent surfaces resolve consumer-side rather than in the development repo. A measurement now changes the next piece instead of dying in chat. A correction made once is enforced from then on. Voice is bound *before* prose rather than scrubbed after. The series remembers what it has already spent. And the engine finally knows your own normal well enough to **refuse a verdict** when the data is too thin — the answer that keeps everything above it honest.
|
||||||
|
|
||||||
### Added — demand-sweep «innenfra og ut» (N7.5 / MR-F9)
|
### Added — demand-sweep «innenfra og ut» (N7.5 / MR-F9)
|
||||||
|
|
||||||
|
|
@ -36,6 +51,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- **WebFetch on both fact agents (C-7)** — `fact-checker` and `fact-reviewer` gain `WebFetch`: quote precision (F2) and exact-figure attribution (F3) require the verbatim source text in context, not a search snippet.
|
- **WebFetch on both fact agents (C-7)** — `fact-checker` and `fact-reviewer` gain `WebFetch`: quote precision (F2) and exact-figure attribution (F3) require the verbatim source text in context, not a search snippet.
|
||||||
- test-runner 146 → 154 (Section 16p: 8 unconditional randsone-gate greps + self-test; anti-erosion floor 129 → 137). Domain-general; no KTG-specific values hardcoded.
|
- test-runner 146 → 154 (Section 16p: 8 unconditional randsone-gate greps + self-test; anti-erosion floor 129 → 137). Domain-general; no KTG-specific values hardcoded.
|
||||||
|
|
||||||
|
### Added — target level becomes a configurable axis, not a hardcoded reader (N10 / C-8)
|
||||||
|
|
||||||
|
- **The hardcoded primary reader is gone** — `references/longform-quality-rules.md` judged every edition against "the non-technical line manager", one specific reader baked into a domain-general plugin. It now refers to **the user's own configured target-level span**, so "practically usable at every level" is measured against the levels *they* write for.
|
||||||
|
- **A mandatory secondary persona at the opposite end of the span** — `config/personas.template.md`'s "primary trumps" rule let a secondary-persona NO be waved through as "signal, not failure". A secondary at the far end of the span is now required, and dismissing its NO demands an **explicit ceiling justification**. The seed library also gained a technician-end persona (a solutions developer / AI engineer): it shipped three *manager* personas and no technician, which quietly biased every gate toward one end of the very span it was supposed to test.
|
||||||
|
- **`targetLevel` stops being a phantom field** — the plan assumed the N7 bridge already carried it into the edition state; it did not (it existed only in the trend store and a free-text mapping). Two files beyond the planned scope close the loop: an additive `articles.NN.targetLevel` slot in `config/edition-state.template.json` (default `null`, **no schemaVersion bump** — same pattern as `sourceTrendId` and `language`), and `/linkedin:newsletter` persisting the resolved level at the Step 1.5 checkpoint. `persona-reviewer` reads it and places the personas on the span, falling back to persona expertise when the field is `null`.
|
||||||
|
- test-runner 154 → 163 (Section 16q: 9 unconditional `.md`/`.json` greps + non-vacuity self-test; anti-erosion floor 137 → 146). A vacuity bug in the new edition-state check was caught and tightened during the session — it grepped for the *word* `targetLevel`, which the `sourceTrendId` doc-string already contained, so it now pins the slot literal. (`0810631`)
|
||||||
|
|
||||||
### Added — series-level protection: make the retread visible before prose (N11 / C-5)
|
### Added — series-level protection: make the retread visible before prose (N11 / C-5)
|
||||||
|
|
||||||
- **`scripts/editions/` — series distillate (new package)** — each locked edition leaves behind the narrative units it spent (anecdotes told, arguments argued, hooks used) in `<serie>/linkedin/series-distillate.json`, beside `edition-state.json` in the **series root** — series-scoped state where the series' other state already lives, so it travels with the series and needs no slug→path map. `distil-append` (Step 8 lock) folds an edition in; a re-lock after `/linkedin:pivot` **replaces** its entry rather than duplicating it.
|
- **`scripts/editions/` — series distillate (new package)** — each locked edition leaves behind the narrative units it spent (anecdotes told, arguments argued, hooks used) in `<serie>/linkedin/series-distillate.json`, beside `edition-state.json` in the **series root** — series-scoped state where the series' other state already lives, so it travels with the series and needs no slug→path map. `distil-append` (Step 8 lock) folds an edition in; a re-lock after `/linkedin:pivot` **replaces** its entry rather than duplicating it.
|
||||||
|
|
@ -544,7 +566,7 @@ Public release for open-source marketplace. All runtime bugs fixed, documentatio
|
||||||
- **Orphaned files** — deleted outdated docs/commands-reference.md
|
- **Orphaned files** — deleted outdated docs/commands-reference.md
|
||||||
- **BACKLOG.md and DEVELOPMENT-LOG.md** — gitignored (internal development files)
|
- **BACKLOG.md and DEVELOPMENT-LOG.md** — gitignored (internal development files)
|
||||||
|
|
||||||
## [0.6.0] - 2026-02-07
|
## [0.6.0-legacy] - 2026-02-07
|
||||||
|
|
||||||
### Note
|
### Note
|
||||||
First formal version. Previously unversioned.
|
First formal version. Previously unversioned.
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ Full-spectrum LinkedIn content engine — short-form feed posts, carousels, vide
|
||||||
- **Post queue:** `assets/drafts/queue.json` (managed by `hooks/scripts/queue-manager.mjs`)
|
- **Post queue:** `assets/drafts/queue.json` (managed by `hooks/scripts/queue-manager.mjs`)
|
||||||
- **Analytics:** CLI `scripts/analytics/` (TypeScript, needs `tsx` + `npm install`); data `assets/analytics/` (gitignored)
|
- **Analytics:** CLI `scripts/analytics/` (TypeScript, needs `tsx` + `npm install`); data `assets/analytics/` (gitignored)
|
||||||
- **Specifics-bank:** `scripts/specifics-bank/` (TypeScript, needs `tsx` + `npm install`) — deterministic, topic-tagged store of the operator's lived specifics (real numbers, named cases, held opinions) in the per-user data dir; elicited/bound by `/linkedin:newsletter` Step 1.5 so drafts draw from real inventory, never invented filler. Consumption is logged back at Step 8 lock (`record-usage` → `usedIn`), so cross-edition re-use of the same material is visible rather than silently encouraged by the dedupe
|
- **Specifics-bank:** `scripts/specifics-bank/` (TypeScript, needs `tsx` + `npm install`) — deterministic, topic-tagged store of the operator's lived specifics (real numbers, named cases, held opinions) in the per-user data dir; elicited/bound by `/linkedin:newsletter` Step 1.5 so drafts draw from real inventory, never invented filler. Consumption is logged back at Step 8 lock (`record-usage` → `usedIn`), so cross-edition re-use of the same material is visible rather than silently encouraged by the dedupe
|
||||||
|
- **Positioning brief (N23):** `${DATA}/profile/positioning-brief.md` (template `config/positioning-brief.template.md`) — the stance-and-difference layer every drafting surface reads before it writes: **Who I am · Who this is for · What I hold · How I differ**, each carrying an `_Evidence:_` line. It is a **distillate, not a second profile**: `profile/user-profile.md` stays the operator's *declared* identity and the brief **references** it rather than restating it — two files asserting the same fact is how positioning forks. Generated by `/linkedin:setup` **Step 3g**, which *delegates* to `linkedin-studio:strategy-advisor` (the SB-S3a brain readers live there in one copy; the command never copies the block). Precedence is declaration-over-inference: the operations anchor and the declared profile outrank the distillation, and a contradiction is reported as a tension, never silently applied. Consumed by `post`/`quick`/`batch`, `newsletter` Step 1 and `differentiation-checker` (which scores originality against **How I differ** rather than generic novelty). **Regeneration is operator-gated** — explicit go, section-by-section diff, never a silent overwrite of the operator's own edits; a section without evidence is marked `[insufficient evidence]`, never filled with a plausible invention. Absent file ⇒ every surface proceeds silently
|
||||||
- **Series distillate:** `scripts/editions/` (TypeScript, needs `tsx` + `npm install`) — series-level memory: each locked edition's spent narrative units (anecdotes/arguments/hooks) in `<serie>/linkedin/series-distillate.json`; written at `/linkedin:newsletter` Step 8 (`distil-append`), checked against the next skeleton at Step 2.5 (`distil-check`, advisory). Deterministic character-trigram similarity — AI extracts, code compares
|
- **Series distillate:** `scripts/editions/` (TypeScript, needs `tsx` + `npm install`) — series-level memory: each locked edition's spent narrative units (anecdotes/arguments/hooks) in `<serie>/linkedin/series-distillate.json`; written at `/linkedin:newsletter` Step 8 (`distil-append`), checked against the next skeleton at Step 2.5 (`distil-check`, advisory). Deterministic character-trigram similarity — AI extracts, code compares
|
||||||
- **Editions register:** `scripts/editions/` (same package) — one row per edition in production in the per-user data dir (`editions/register.json`): series, edition, path, current phase, next action, slot, `startedAt`/`completedAt`. Each of the 17 canonical `/linkedin:newsletter` phase transitions runs `register-upsert --edition-state <path>`, which appends `articles.NN.phaseLog` (lead-time telemetry, additive — schemaVersion stays 1) **and** mirrors the row in one call; Step 10 runs `register-complete`. `register-list` is the work-in-progress view. The register is a **mirror** — deterministic resumption reads `edition-state.json` only, and a lost register is rebuilt by the next transition
|
- **Editions register:** `scripts/editions/` (same package) — one row per edition in production in the per-user data dir (`editions/register.json`): series, edition, path, current phase, next action, slot, `startedAt`/`completedAt`. Each of the 17 canonical `/linkedin:newsletter` phase transitions runs `register-upsert --edition-state <path>`, which appends `articles.NN.phaseLog` (lead-time telemetry, additive — schemaVersion stays 1) **and** mirrors the row in one call; Step 10 runs `register-complete`. `register-list` is the work-in-progress view. The register is a **mirror** — deterministic resumption reads `edition-state.json` only, and a lost register is rebuilt by the next transition
|
||||||
- **Publishing slots:** `hooks/scripts/slots.mjs` — the weekly production grid. Slots are **operator config** in the per-user data dir (`profile/publishing-slots.json`, schema + opt-in template in `config/publishing-slots.template.json`); absent config ⇒ every slot surface stays silent, and the plugin hardcodes no publishing times. Coverage of a slot counts the short-form queue (`scheduled`/`published`) **and** editions-register rows claiming that date, so long-form and short-form cannot be double-booked. Zero-dep (SessionStart may not spawn tsx) and imported identically by the hook and by the commands (`queue-manager.mjs` pattern), so session-start's vacancy warning and `/linkedin:newsletter` Step 10's slot default are the same computation
|
- **Publishing slots:** `hooks/scripts/slots.mjs` — the weekly production grid. Slots are **operator config** in the per-user data dir (`profile/publishing-slots.json`, schema + opt-in template in `config/publishing-slots.template.json`); absent config ⇒ every slot surface stays silent, and the plugin hardcodes no publishing times. Coverage of a slot counts the short-form queue (`scheduled`/`published`) **and** editions-register rows claiming that date, so long-form and short-form cannot be double-booked. Zero-dep (SessionStart may not spawn tsx) and imported identically by the hook and by the commands (`queue-manager.mjs` pattern), so session-start's vacancy warning and `/linkedin:newsletter` Step 10's slot default are the same computation
|
||||||
|
|
@ -57,7 +58,7 @@ All content commands auto-copy output to clipboard via `clipboard-helper.mjs`; i
|
||||||
| `/linkedin:post` | Full post creation (10-15 min) |
|
| `/linkedin:post` | Full post creation (10-15 min) |
|
||||||
| `/linkedin:quick` | 5-minute quick post (3-line formula) + 8 post-type templates |
|
| `/linkedin:quick` | 5-minute quick post (3-line formula) + 8 post-type templates |
|
||||||
| `/linkedin:pipeline` | Full end-to-end content pipeline |
|
| `/linkedin:pipeline` | Full end-to-end content pipeline |
|
||||||
| `/linkedin:trends` | Trend discovery pass — delegates to `trend-spotter` (default long-form mode), persists to trend store + dated morning brief, triage per id (act/skip) |
|
| `/linkedin:trends` | Trend discovery pass — delegates to `trend-spotter` (default long-form mode), persists to trend store + dated morning brief, triage per id (act/skip); `--headless` (RE-R3f) runs it unattended (fail-fast instead of asking, triage always skipped) for a scheduled Sunday pass |
|
||||||
| `/linkedin:newsletter` | Long-form orchestrator (newsletter / essay / series article) — multi-session 19-phase pipeline with gates before prose (skeleton + spine), fact-check, editorial-review (Step 5.5), persona sweep, headless adversarial review (Step 6.5), and visual-assets — all before lock; closes with the Step 11 retro that promotes this edition's corrections so the next one starts with them enforced |
|
| `/linkedin:newsletter` | Long-form orchestrator (newsletter / essay / series article) — multi-session 19-phase pipeline with gates before prose (skeleton + spine), fact-check, editorial-review (Step 5.5), persona sweep, headless adversarial review (Step 6.5), and visual-assets — all before lock; closes with the Step 11 retro that promotes this edition's corrections so the next one starts with them enforced |
|
||||||
| `/linkedin:headless-review` | Cold adversarial review package — 3 headless archetypes (`content-reviewer`, `language-reviewer`, `fact-reviewer`) + `persona-reviewer` on a FROZEN draft, no drafting-session context; operator-gated report (Step 6.5 standalone surface). `--background` runs the package in a background agent that writes the report to disk — fresh-session isolation without the paste-back seam |
|
| `/linkedin:headless-review` | Cold adversarial review package — 3 headless archetypes (`content-reviewer`, `language-reviewer`, `fact-reviewer`) + `persona-reviewer` on a FROZEN draft, no drafting-session context; operator-gated report (Step 6.5 standalone surface). `--background` runs the package in a background agent that writes the report to disk — fresh-session isolation without the paste-back seam |
|
||||||
| `/linkedin:pivot` | Re-open a long-form edition after a late substantive change so cleared gates re-run before lock (pivot heuristic: >20% word-count or >2 new sections) |
|
| `/linkedin:pivot` | Re-open a long-form edition after a late substantive change so cleared gates re-run before lock (pivot heuristic: >20% word-count or >2 new sections) |
|
||||||
|
|
|
||||||
131
GOVERNANCE.md
131
GOVERNANCE.md
|
|
@ -1,131 +0,0 @@
|
||||||
# Governance
|
|
||||||
|
|
||||||
How this marketplace is maintained, what you can expect from upstream, and how it's meant to be used.
|
|
||||||
|
|
||||||
## TL;DR
|
|
||||||
|
|
||||||
- Solo-maintained, AI-assisted development, MIT licensed.
|
|
||||||
- **Fork-and-own is the default model.** Upstream is a starting point, not a vendor.
|
|
||||||
- Issues welcome as signals. Pull requests are not accepted — see [Why no PRs](#pull-requests--no).
|
|
||||||
- No SLA. Best-effort bug fixes and security advisories. Breaking changes happen and are noted in each plugin's CHANGELOG.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Can I trust this?
|
|
||||||
|
|
||||||
Be honest with yourself about what you're adopting:
|
|
||||||
|
|
||||||
- **One maintainer.** If I get hit by a bus, the bus wins. The repos stay up under MIT, but no one owes you a fix.
|
|
||||||
- **AI-generated code with human review.** Every plugin is built through dialog-driven development with Claude Code. I read, test, and judge the output before it ships, but I'm not auditing every line the way a security firm would. Treat it accordingly.
|
|
||||||
- **No commercial interests.** I'm not selling a SaaS, not steering you toward a paid tier, not collecting telemetry. The plugins run locally in your Claude Code installation.
|
|
||||||
- **MIT licensed.** Fork it, modify it, ship it under your own name.
|
|
||||||
|
|
||||||
If you work somewhere that needs vendor accountability, support contracts, or signed assurances — **this isn't that.** Use it as a reference implementation, fork it into your own organization, and own the result.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How this is meant to be used
|
|
||||||
|
|
||||||
### Fork-and-own
|
|
||||||
|
|
||||||
The intended workflow:
|
|
||||||
|
|
||||||
1. **Fork** the marketplace (or a single plugin) into your own organization or namespace.
|
|
||||||
2. **Tailor** it to your context — terminology, integrations, cycle lengths, regulatory framing, whatever doesn't fit out of the box.
|
|
||||||
3. **Maintain it yourself.** Treat your fork as the canonical version for your team.
|
|
||||||
4. **Watch upstream selectively.** Cherry-pick changes that help, ignore changes that don't. There's no obligation to stay in sync.
|
|
||||||
|
|
||||||
This isn't a workaround for not accepting PRs. It's the actual recommended adoption pattern, especially for plugins like `okr` and `ms-ai-architect` where every Norwegian public sector organization will need its own tildelingsbrev mappings, terminology, and integrations. A central "one true plugin" would be wrong for everyone.
|
|
||||||
|
|
||||||
### What to change first when you fork
|
|
||||||
|
|
||||||
Each plugin differs, but the common edits are:
|
|
||||||
|
|
||||||
- **Identity** — rename the plugin, replace authorship, update README.
|
|
||||||
- **External integrations** — issue trackers, knowledge bases, dashboards, observability backends. The plugins ship as starting points, not pre-wired. Every organization must configure its own integrations.
|
|
||||||
- **Norwegian-specific framing** — relevant for `okr` and `ms-ai-architect`. Other plugins are jurisdiction-neutral. Rewrite for your jurisdiction if you're outside Norway.
|
|
||||||
- **Reference docs** — the knowledge base in each plugin reflects my reading. Replace with your organization's authoritative sources.
|
|
||||||
- **Hooks and policies** — security thresholds, blocked commands, and audit gates are tuned to my taste. Tune them to yours.
|
|
||||||
|
|
||||||
### Staying current with upstream
|
|
||||||
|
|
||||||
If you want to pull in upstream changes later:
|
|
||||||
|
|
||||||
- **Cherry-pick, don't merge.** Each plugin moves independently and breaking changes land without ceremony.
|
|
||||||
- **Read the CHANGELOG first.** Every plugin has one.
|
|
||||||
- **Keep your customizations in clearly-named files.** The harder upstream is to merge cleanly, the more painful staying current becomes. A `local/` directory or `*.local.md` convention helps.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What upstream provides
|
|
||||||
|
|
||||||
| | What I do | What I don't |
|
|
||||||
|---|---|---|
|
|
||||||
| **Bug fixes** | Best-effort when I notice or get a clear report | No SLA, no triage commitment |
|
|
||||||
| **Security issues** | Investigate within reasonable time, document in CHANGELOG | No CVE process, no embargo coordination |
|
|
||||||
| **New features** | When they fit my own usage | Not on request |
|
|
||||||
| **Norwegian public sector context** | Kept current as long as the project lives | If I lose interest or change jobs, the framing freezes |
|
|
||||||
| **Breaking changes** | Documented in CHANGELOG | They happen — version pin if you need stability |
|
|
||||||
| **Compatibility** | Tracked against current Claude Code releases | No long-term support branches |
|
|
||||||
|
|
||||||
If any of this is a dealbreaker — fork now, version-pin, and stop reading upstream.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to contribute
|
|
||||||
|
|
||||||
### Issues — yes, please
|
|
||||||
|
|
||||||
Issues are the most valuable thing you can send me:
|
|
||||||
|
|
||||||
- **Bug reports** with reproduction steps. Even a screenshot helps.
|
|
||||||
- **Use-case feedback.** "I tried to use this in my organization and X didn't fit" is genuinely useful, even if I can't fix it for you.
|
|
||||||
- **Pointers to better sources.** If you know a DFØ veileder, an NSM guideline, or an academic paper that contradicts what's in a knowledge base, tell me.
|
|
||||||
- **Security findings.** See each plugin's `SECURITY.md` for disclosure preference where one exists; otherwise email rather than open a public issue.
|
|
||||||
|
|
||||||
### Pull requests — no
|
|
||||||
|
|
||||||
This is deliberate, not laziness:
|
|
||||||
|
|
||||||
- **Solo review is a bottleneck.** Honest PR review takes me longer than rewriting from scratch. The math doesn't work.
|
|
||||||
- **Forks are where the value is.** The fork-and-own model means upstream consolidation isn't the point. Your organization's adaptations belong in your fork, not mine.
|
|
||||||
- **AI-generated code complicates provenance.** Every line here is produced through dialog with Claude Code, with me as the judge. Mixing in PRs from contributors with different processes and licensing assumptions creates a mess I'd rather not untangle.
|
|
||||||
|
|
||||||
If you've built something useful on top of a fork, **publish it under your own name and link back.** I'll happily list notable forks here once they exist.
|
|
||||||
|
|
||||||
### Notable forks
|
|
||||||
|
|
||||||
*(To be populated as forks emerge. If you've forked one of these plugins for production use, open an issue and I'll add a link.)*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Relationship between plugins
|
|
||||||
|
|
||||||
These plugins are **independent**. Install one without the others, fork one without the others. They share conventions (slash command naming, hook patterns, AI-generated disclosure) but no runtime dependencies.
|
|
||||||
|
|
||||||
The marketplace is a **catalog**, not a suite. Don't fork the whole repo unless you actually want to maintain everything.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Versioning and stability
|
|
||||||
|
|
||||||
- **Semantic versioning per plugin.** Each plugin has its own `CHANGELOG.md` and version number.
|
|
||||||
- **Breaking changes happen.** I bump the major version when they do, but I don't run an LTS branch.
|
|
||||||
- **Pin your version.** If stability matters more than features, install a specific version and stay there until you choose to upgrade.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Public sector adoption notes
|
|
||||||
|
|
||||||
For Norwegian etater specifically:
|
|
||||||
|
|
||||||
- **DPIA-relevant data flows are documented in the relevant plugin README where applicable.** Read them before installation.
|
|
||||||
- **No data leaves your machine** beyond what Claude Code itself sends to Anthropic. The plugins themselves do not call external services unless you configure an integration.
|
|
||||||
- **Drøftingsplikt and ledelsesansvar** are not replaced by these tools. The `okr` plugin coaches; it does not decide. The `ms-ai-architect` plugin advises; it does not approve.
|
|
||||||
- **Choose your Claude deployment carefully.** claude.ai vs. API direct vs. Bedrock in EU region have different data residency profiles. The plugins don't choose for you.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT for all plugins in this marketplace. See each plugin's `LICENSE` file.
|
|
||||||
96
README.md
96
README.md
|
|
@ -1,17 +1,17 @@
|
||||||
# LinkedIn Studio Plugin for Claude Code
|
# LinkedIn Studio Plugin for Claude Code
|
||||||
|
|
||||||
|
Full-spectrum LinkedIn content engine for Claude Code: feed posts, carousels, video scripts, and long-form newsletters. Aligned to LinkedIn's 2026 ranking model.
|
||||||
|
|
||||||
> Turn your expertise into LinkedIn authority — without the blank page, the guesswork, or the generic AI slop.
|
> Turn your expertise into LinkedIn authority — without the blank page, the guesswork, or the generic AI slop.
|
||||||
|
|
||||||
> **Solo-maintained, fork-and-own.** This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted. See [GOVERNANCE.md](GOVERNANCE.md) for the full model and what upstream provides.
|
> **Solo-maintained, fork-and-own.** This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted. See [GOVERNANCE.md](https://git.fromaitochitta.com/open/repo-standard/src/branch/main/GOVERNANCE.md) for the full model and what upstream provides.
|
||||||
|
|
||||||
*AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)*
|
*AI-generated: all code produced by Claude Code through dialog-driven development, human-directed and reviewed before commit. Output ownership rests with the maintainer under Anthropic's Consumer Terms §4.*
|
||||||
|
|
||||||

|

|
||||||

|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|
|
||||||

|

|
||||||
|
|
||||||
Most experts know they *should* post on LinkedIn — and quietly don't. The blank editor wins. LinkedIn Studio turns that chore into a system: structured workflows that take you from idea to published, in your own voice, calibrated to how LinkedIn's **topic-relevance** ranking model (2026) actually distributes content. Two engines under one surface — a **feed engine** for short-form posts, carousels, and video scripts, and a **long-form engine** that runs newsletter editions and essays through a serious editorial pipeline before they ever lock.
|
Most experts know they *should* post on LinkedIn — and quietly don't. The blank editor wins. LinkedIn Studio turns that chore into a system: structured workflows that take you from idea to published, in your own voice, calibrated to how LinkedIn's **topic-relevance** ranking model (2026) actually distributes content. Two engines under one surface — a **feed engine** for short-form posts, carousels, and video scripts, and a **long-form engine** that runs newsletter editions and essays through a serious editorial pipeline before they ever lock.
|
||||||
|
|
@ -26,6 +26,48 @@ This is not a shortcut. Hand the wheel to the AI and you land where everyone who
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Install](#install)
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Two Engines](#two-engines)
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [Commands](#commands)
|
||||||
|
- [Agents](#agents)
|
||||||
|
- [Non-goals](#non-goals)
|
||||||
|
- [Content Quality Rules](#content-quality-rules)
|
||||||
|
- [Example Workflows](#example-workflows)
|
||||||
|
- [Deeper Documentation](#deeper-documentation)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Changelog](#changelog)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Add the marketplace, then install the plugin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
|
||||||
|
claude plugin install linkedin-studio@ktg-plugin-marketplace
|
||||||
|
```
|
||||||
|
|
||||||
|
Or add the marketplace and browse interactively with `/plugin`. Or enable directly in `~/.claude/settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabledPlugins": {
|
||||||
|
"linkedin-studio@ktg-plugin-marketplace": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) with plugin support enabled
|
||||||
|
- Node.js 18+ (for hooks and analytics CLI; analytics requires `tsx`: `cd scripts/analytics && npm install`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Two Engines
|
## Two Engines
|
||||||
|
|
||||||
LinkedIn Studio is really two content engines sharing one surface. They have different speeds, different gates, and different goals.
|
LinkedIn Studio is really two content engines sharing one surface. They have different speeds, different gates, and different goals.
|
||||||
|
|
@ -62,30 +104,6 @@ Each gate exists because skipping it is expensive: spine errors are caught at th
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) with plugin support enabled
|
|
||||||
- Node.js 18+ (for hooks and analytics CLI; analytics requires `tsx`: `cd scripts/analytics && npm install`)
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
Add the marketplace, then install the plugin:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
|
|
||||||
claude plugin install linkedin-studio@ktg-plugin-marketplace
|
|
||||||
```
|
|
||||||
|
|
||||||
Or add the marketplace and browse interactively with `/plugin`. Or enable directly in `~/.claude/settings.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabledPlugins": {
|
|
||||||
"linkedin-studio@ktg-plugin-marketplace": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Started (5 minutes)
|
### Get Started (5 minutes)
|
||||||
|
|
||||||
Run the onboarding wizard — it walks you through profile, setup, and your first post in one flow:
|
Run the onboarding wizard — it walks you through profile, setup, and your first post in one flow:
|
||||||
|
|
@ -156,7 +174,7 @@ All 30 commands use colon notation: `/linkedin:post`, `/linkedin:quick`, etc. Th
|
||||||
| `/linkedin:analyze` | Diagnose performance issues — algorithm penalties, profile-content mismatch, reach drops. |
|
| `/linkedin:analyze` | Diagnose performance issues — algorithm penalties, profile-content mismatch, reach drops. |
|
||||||
| `/linkedin:audit` | Periodic strategy audit — top/bottom posts, topic distribution, format mix, trends. Run quarterly. |
|
| `/linkedin:audit` | Periodic strategy audit — top/bottom posts, topic distribution, format mix, trends. Run quarterly. |
|
||||||
| `/linkedin:ab-test` | Design and track A/B content experiments. |
|
| `/linkedin:ab-test` | Design and track A/B content experiments. |
|
||||||
| `/linkedin:competitive` | Competitive analysis of niche thought leaders — frequency, formats, hooks, differentiation gaps. |
|
| `/linkedin:competitive` | Competitive analysis of other creators in your niche — frequency, formats, hooks, differentiation gaps. |
|
||||||
|
|
||||||
### Grow
|
### Grow
|
||||||
|
|
||||||
|
|
@ -196,9 +214,9 @@ All 30 commands use colon notation: `/linkedin:post`, `/linkedin:quick`, etc. Th
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Boundaries (as of 2026-07)
|
## Non-goals
|
||||||
|
|
||||||
LinkedIn Studio is honest about what it can and cannot do for a **personal profile**:
|
What LinkedIn Studio deliberately does not do for a **personal profile** (boundaries as of 2026-07):
|
||||||
|
|
||||||
- **Post-level analytics via API** — exists, but is **partner-gated** (a vetted Community Management API app + a verified organization + a Page). Not self-serve for a solo profile, so the practical floor is the **CSV export** you drop into `/linkedin:import`. Per-post **saves** are visible in *native* post analytics (count-only, since ~Sept 2025) but absent from the CSV and have no self-serve API — the tool does **not** auto-track them, but you can add a `Saves` column to the CSV manually and `/linkedin:import` ingests it (omit it and saves stays *unknown*, never 0, never folded into engagement rate).
|
- **Post-level analytics via API** — exists, but is **partner-gated** (a vetted Community Management API app + a verified organization + a Page). Not self-serve for a solo profile, so the practical floor is the **CSV export** you drop into `/linkedin:import`. Per-post **saves** are visible in *native* post analytics (count-only, since ~Sept 2025) but absent from the CSV and have no self-serve API — the tool does **not** auto-track them, but you can add a `Saves` column to the CSV manually and `/linkedin:import` ingests it (omit it and saves stays *unknown*, never 0, never folded into engagement rate).
|
||||||
- **Auto-publish** — technically possible via the `w_member_social` scope, so this is a **design choice**, not an API limit: the OAuth/token overhead plus LinkedIn's terms on automated posting make copy-to-clipboard + you-paste the right default. The calendar's "publish" action marks a post **you** posted as published — it never posts on your behalf.
|
- **Auto-publish** — technically possible via the `w_member_social` scope, so this is a **design choice**, not an API limit: the OAuth/token overhead plus LinkedIn's terms on automated posting make copy-to-clipboard + you-paste the right default. The calendar's "publish" action marks a post **you** posted as published — it never posts on your behalf.
|
||||||
|
|
@ -250,7 +268,21 @@ The README is the front door. The detail lives alongside it:
|
||||||
| Architecture — agent pipeline & selection, 9 hooks, 6 skills, personalization scoring, configuration, analytics internals | [CLAUDE.md](CLAUDE.md) |
|
| Architecture — agent pipeline & selection, 9 hooks, 6 skills, personalization scoring, configuration, analytics internals | [CLAUDE.md](CLAUDE.md) |
|
||||||
| The 28-document knowledge base (algorithm signals, angles, frameworks, strategy guides) | [`references/`](references/) |
|
| The 28-document knowledge base (algorithm signals, angles, frameworks, strategy guides) | [`references/`](references/) |
|
||||||
| Full version history and known gaps | [CHANGELOG.md](CHANGELOG.md) |
|
| Full version history and known gaps | [CHANGELOG.md](CHANGELOG.md) |
|
||||||
| Maintenance model, fork-and-own, what upstream provides | [GOVERNANCE.md](GOVERNANCE.md) |
|
| Maintenance model, fork-and-own, what upstream provides | [GOVERNANCE.md](https://git.fromaitochitta.com/open/repo-standard/src/branch/main/GOVERNANCE.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```
|
||||||
|
node --test hooks/scripts/__tests__/*.test.mjs render/__tests__/*.test.mjs tests/*.test.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs the `node:test`-based suite (289 tests, 63 suites). A bare `node --test` also picks up the `.test.ts` files under `scripts/*/tests/` and fails on them — those are separate TypeScript sub-packages (analytics, editions, specifics-bank, contract-gate, trends, brain), each with its own `npm install && npm test`.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
See [CHANGELOG.md](CHANGELOG.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
20
SECURITY.md
20
SECURITY.md
|
|
@ -6,7 +6,25 @@ If you discover a security vulnerability in this plugin, please report it respon
|
||||||
|
|
||||||
**Do NOT open a public issue for security vulnerabilities.**
|
**Do NOT open a public issue for security vulnerabilities.**
|
||||||
|
|
||||||
Instead, please email the maintainer directly or use GitHub's private vulnerability reporting feature.
|
Report privately to <security@fromaitochitta.com>.
|
||||||
|
Canonical repository: https://git.fromaitochitta.com/open/linkedin-studio
|
||||||
|
|
||||||
|
Please include the affected version or commit, a minimal reproduction, and the
|
||||||
|
impact you see. We acknowledge every report within 5 working days, agree a
|
||||||
|
fix and disclosure timeline with the reporter, and aim to disclose within 90
|
||||||
|
days of the initial report.
|
||||||
|
|
||||||
|
## Supported versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | --------- |
|
||||||
|
| 0.8.x | :white_check_mark: |
|
||||||
|
| < 0.8 | :x: |
|
||||||
|
|
||||||
|
This plugin has not reached 1.0 yet: it is a single, continuous 0.x
|
||||||
|
release line (no parallel maintenance branches), and only the latest
|
||||||
|
0.x release receives security fixes — there are no backports to older
|
||||||
|
0.x versions. See `CHANGELOG.md` for release history.
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -424,7 +424,7 @@ Remind them:
|
||||||
- Month 1: Learning mechanics (baseline)
|
- Month 1: Learning mechanics (baseline)
|
||||||
- Month 3: Understanding YOUR patterns (above average)
|
- Month 3: Understanding YOUR patterns (above average)
|
||||||
- Month 6: Discovering insights from practice (exceptional)
|
- Month 6: Discovering insights from practice (exceptional)
|
||||||
- Month 12: Systematically generating unique perspectives (thought leader)
|
- Month 12: Systematically generating unique perspectives (recognized expert)
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,9 @@ When you receive content to optimize, analyze it through these lenses:
|
||||||
|
|
||||||
**Optimal structure (1,200-1,800 characters):**
|
**Optimal structure (1,200-1,800 characters):**
|
||||||
- Hook: 110-140 chars
|
- Hook: 110-140 chars
|
||||||
- Context: 200-300 chars
|
- Context: 250-350 chars
|
||||||
- Insight/Argument: 400-800 chars (the meat)
|
- Insight/Argument: 550-850 chars (the meat)
|
||||||
- Implication: 200-300 chars
|
- Implication: 250-350 chars
|
||||||
- CTA: 50-100 chars
|
- CTA: 50-100 chars
|
||||||
|
|
||||||
**Check for:**
|
**Check for:**
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ flags; if you suppressed any, say how many and of what severity.
|
||||||
### Step 4 — Emit the report (the operator gates)
|
### Step 4 — Emit the report (the operator gates)
|
||||||
|
|
||||||
You do **not** gate the pipeline yourself — your output is surfaced to the
|
You do **not** gate the pipeline yourself — your output is surfaced to the
|
||||||
operator (KTG) as a markdown report (`SendUserFile`), and the operator decides
|
operator as a markdown report (`SendUserFile`), and the operator decides
|
||||||
which flags fold in. Your severity ranking is the *recommendation*; the operator
|
which flags fold in. Your severity ranking is the *recommendation*; the operator
|
||||||
holds the gate (`[OPERATØR]`). After fold-in, the editor (the command session)
|
holds the gate (`[OPERATØR]`). After fold-in, the editor (the command session)
|
||||||
produces a revised draft and **may re-run you** on the cleaned version before
|
produces a revised draft and **may re-run you** on the cleaned version before
|
||||||
|
|
@ -238,7 +238,7 @@ before lock (Step 8). Operator decides fold-in; this is [OPERATØR].
|
||||||
C2 / C4); NICE = a cheap soft spot. Sort BLOCK→REWORK→NICE.
|
C2 / C4); NICE = a cheap soft spot. Sort BLOCK→REWORK→NICE.
|
||||||
7. **Cap at eight; never truncate silently.** If you suppressed findings, say how
|
7. **Cap at eight; never truncate silently.** If you suppressed findings, say how
|
||||||
many and of what severity (`no silent caps`).
|
many and of what severity (`no silent caps`).
|
||||||
8. **The operator gates, you recommend.** Your output is a report for KTG, not a
|
8. **The operator gates, you recommend.** Your output is a report for the operator, not a
|
||||||
pipeline stop. BLOCK is your strongest recommendation, not a hard halt — the
|
pipeline stop. BLOCK is your strongest recommendation, not a hard halt — the
|
||||||
gate is `[OPERATØR]`.
|
gate is `[OPERATØR]`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,17 @@ Ensure every post adds genuine value rather than echoing what has already been s
|
||||||
|
|
||||||
Core principle: **if someone else has already said it better, find the angle that only this creator can own.**
|
Core principle: **if someone else has already said it better, find the angle that only this creator can own.**
|
||||||
|
|
||||||
|
**What "only this creator can own" means is not yours to guess.** Read
|
||||||
|
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md`
|
||||||
|
before scoring. Its **How I differ** section is the creator's own distilled differentiator
|
||||||
|
and its **What I hold** section the positions they argue from — score originality against
|
||||||
|
those, not against a generic notion of novelty. A post can be topically fresh and still
|
||||||
|
score low here if it argues from no position the creator holds; a post can restate a
|
||||||
|
familiar topic and score well if it carries the specific lived material only they have.
|
||||||
|
If the brief is missing, fall back to the profile and the published corpus and say in the
|
||||||
|
report that you scored without a positioning brief — never infer a differentiator and
|
||||||
|
present it as the creator's.
|
||||||
|
|
||||||
## Similarity Search Process
|
## Similarity Search Process
|
||||||
|
|
||||||
### Step 1: Extract Core Claims
|
### Step 1: Extract Core Claims
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ tools: ["Read", "Grep"]
|
||||||
|
|
||||||
You are an **editor**. You read a near-final long-form draft and judge whether the
|
You are an **editor**. You read a near-final long-form draft and judge whether the
|
||||||
**prose is clean** and the **narrative architecture holds** — the craft layer a
|
**prose is clean** and the **narrative architecture holds** — the craft layer a
|
||||||
reader never names but always feels. You are the pass a human editor (KTG) makes
|
reader never names but always feels. You are the pass a human editor makes
|
||||||
on first reading, operationalized as an automated pre-persona gate.
|
on first reading, operationalized as an automated pre-persona gate.
|
||||||
|
|
||||||
You run at **Step 5.5** of the `/linkedin:newsletter` pipeline — *after* the
|
You run at **Step 5.5** of the `/linkedin:newsletter` pipeline — *after* the
|
||||||
|
|
@ -182,7 +182,7 @@ flags**; if you suppressed any, say how many and of what severity.
|
||||||
### Step 5 — Emit the report (the operator gates)
|
### Step 5 — Emit the report (the operator gates)
|
||||||
|
|
||||||
You do **not** gate the pipeline yourself — your output is surfaced to the
|
You do **not** gate the pipeline yourself — your output is surfaced to the
|
||||||
operator (KTG) as a markdown report (`SendUserFile`), and the operator decides
|
operator as a markdown report (`SendUserFile`), and the operator decides
|
||||||
which flags fold in. Your severity ranking is the *recommendation*; the operator
|
which flags fold in. Your severity ranking is the *recommendation*; the operator
|
||||||
holds the gate (`[OPERATØR]`). After fold-in, the editor (the command session)
|
holds the gate (`[OPERATØR]`). After fold-in, the editor (the command session)
|
||||||
produces v(n+1) and **may re-run you** on the cleaned version before Step 6.
|
produces v(n+1) and **may re-run you** on the cleaned version before Step 6.
|
||||||
|
|
@ -233,7 +233,7 @@ before the Step 6 persona sweep. Operator decides fold-in; this is [OPERATØR].
|
||||||
takeaway; REWORK = real weakness; NICE = cheap polish. Sort BLOCK→REWORK→NICE.
|
takeaway; REWORK = real weakness; NICE = cheap polish. Sort BLOCK→REWORK→NICE.
|
||||||
6. **Cap at ten; never truncate silently.** If you suppressed findings, say how
|
6. **Cap at ten; never truncate silently.** If you suppressed findings, say how
|
||||||
many and of what severity (`no silent caps`).
|
many and of what severity (`no silent caps`).
|
||||||
7. **The operator gates, you recommend.** Your output is a report for KTG, not a
|
7. **The operator gates, you recommend.** Your output is a report for the operator, not a
|
||||||
pipeline stop. BLOCK is your strongest recommendation, not a hard halt — the
|
pipeline stop. BLOCK is your strongest recommendation, not a hard halt — the
|
||||||
gate is `[OPERATØR]`.
|
gate is `[OPERATØR]`.
|
||||||
8. **The in-tree checklist is the operative source of truth.** It ships and is
|
8. **The in-tree checklist is the operative source of truth.** It ships and is
|
||||||
|
|
|
||||||
|
|
@ -40,14 +40,18 @@ Help creators:
|
||||||
4. Build a network effect through strategic commenting (target selection + CEA-quality comments)
|
4. Build a network effect through strategic commenting (target selection + CEA-quality comments)
|
||||||
5. Turn comments into profile visits, follows, and business relationships
|
5. Turn comments into profile visits, follows, and business relationships
|
||||||
|
|
||||||
**Core belief:** Commenting is not support activity — it is a primary growth channel. 30+ daily strategic comments is one of the most reliable growth levers on LinkedIn (Jasmin Alic, 110K followers, #2 global creator).
|
**Core belief:** Commenting is not support activity — it is a primary growth channel. The
|
||||||
|
direction is well-attested among high-volume practitioners (e.g. Jasmin Alic); the *volume*
|
||||||
|
is not a published figure and no source establishes a daily number that works. **How many
|
||||||
|
comments you should write is set by your time budget, not by a benchmark** — see Daily
|
||||||
|
Volume Targets, where every row carries the minutes it costs.
|
||||||
|
|
||||||
## The Engagement Multiplier
|
## The Engagement Multiplier
|
||||||
|
|
||||||
**The math that most creators ignore:**
|
**The math that most creators ignore:**
|
||||||
- Comments rank above likes in the engagement order (see `references/algorithm-signals-reference.md`)
|
- Comments rank above likes in the engagement order (see `references/algorithm-signals-reference.md`)
|
||||||
- Substantive comments (15+ words) outweigh short ones and rank above plain reactions — but below saves and shares (no fixed comment-vs-reshare multiplier)
|
- Substantive comments (15+ words) outweigh short ones and rank above plain reactions — but below saves and shares (no fixed comment-vs-reshare multiplier)
|
||||||
- Posts with 15+ engagements in first hour unlock 2nd/3rd degree distribution
|
- Strong early engagement unlocks broader distribution — directional, **not a fixed threshold** (no published engagement count flips it)
|
||||||
- Your comments on others' posts expose you to their audience
|
- Your comments on others' posts expose you to their audience
|
||||||
- Commenting within 30 minutes of a post tends to earn more follow-up engagement on your comment (multiplier unverified)
|
- Commenting within 30 minutes of a post tends to earn more follow-up engagement on your comment (multiplier unverified)
|
||||||
|
|
||||||
|
|
@ -81,13 +85,21 @@ Help creators:
|
||||||
4. **Respond to EVERY comment** within 30 minutes (more follow-ups — figure unverified)
|
4. **Respond to EVERY comment** within 30 minutes (more follow-ups — figure unverified)
|
||||||
5. **Add 2-3 more self-comments** over 90 minutes (spark discussion)
|
5. **Add 2-3 more self-comments** over 90 minutes (spark discussion)
|
||||||
|
|
||||||
**Velocity targets:**
|
**Velocity shape** — the SSOT keeps this table deliberately non-numeric, and so does this
|
||||||
| Time | Target | Warning |
|
one: these are low-confidence directional hypotheses to test on your own account, **not
|
||||||
|------|--------|---------|
|
targets** (`references/algorithm-signals-reference.md`, operational heuristics). No
|
||||||
| 5 min | 2-3 | 0 = wrong time |
|
primary source publishes a per-minute engagement count.
|
||||||
| 15 min | 5-8 | <3 = hook issue |
|
|
||||||
| 30 min | 10-15 | <5 = consider adjustments |
|
| Time | What you want to see | If it is well below |
|
||||||
| 60 min | 15-25 | <10 = limited reach |
|
|------|----------------------|---------------------|
|
||||||
|
| 5 min | any movement at all | check the posting time |
|
||||||
|
| 15 min | a few | check timing / hook |
|
||||||
|
| 30 min | building | engage in the comments |
|
||||||
|
| 60-90 min | momentum | golden window closing |
|
||||||
|
|
||||||
|
The comparison that *is* defensible is against **your own recent first hours** — same
|
||||||
|
account, same audience. Once analytics are imported, the baseline engine gives you that
|
||||||
|
band; before then, the honest read is "too early to tell".
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -198,23 +210,36 @@ Structure: Connect to their point → Share brief relevant story → Extract the
|
||||||
|
|
||||||
Commenting within 30 minutes of a post's publication tends to earn more follow-up engagement on your comment (figure unverified). Early comments get pinned to the top and seen by the largest audience.
|
Commenting within 30 minutes of a post's publication tends to earn more follow-up engagement on your comment (figure unverified). Early comments get pinned to the top and seen by the largest audience.
|
||||||
|
|
||||||
| Time Block | Activity | Why |
|
| Time Block | Activity | Minutes | Why |
|
||||||
|------------|----------|-----|
|
|------------|----------|---------|-----|
|
||||||
| 7:00-7:30 AM | Scan overnight whale posts | Catch early-morning content from US timezones |
|
| 7:00-7:10 AM | Scan overnight whale posts (0 comments) | 10 | Catch early-morning content from US timezones |
|
||||||
| 8:00-8:30 AM | First comment round (5-8 comments) | Peak European posting window begins |
|
| 8:00-8:15 AM | First comment round (5-8 comments) | 15 | Peak European posting window begins |
|
||||||
| 10:00-10:30 AM | Mid-morning round (5-8 comments) | Catch late-morning posts, respond to replies |
|
| 12:00-12:15 PM | Mid-day round (5-8 comments) | 15 | High-activity period, new posts flowing |
|
||||||
| 12:00-12:30 PM | Lunch round (5-8 comments) | High-activity period, new posts flowing |
|
| 3:00-3:10 PM | Afternoon round (5-8 comments) | 10 | Catch US East Coast morning content |
|
||||||
| 3:00-3:30 PM | Afternoon round (5-8 comments) | Catch US East Coast morning content |
|
| ongoing | Reply to replies on your earlier comments | 5 | Where relationships actually form |
|
||||||
| 5:00-5:30 PM | Evening sweep (3-5 comments) | Wrap up, respond to threads from earlier |
|
|
||||||
|
**Sum: 15-24 comments in ~55 minutes/day.** This grid and the step-by-step routine below
|
||||||
|
are the same day, counted twice — keep them equal if you change either.
|
||||||
|
|
||||||
### Daily Volume Targets
|
### Daily Volume Targets
|
||||||
|
|
||||||
| Growth Stage | Daily Comments | Focus Split |
|
These are **allocation guidance, not performance benchmarks** — no published dataset ties a
|
||||||
|--------------|----------------|-------------|
|
daily comment count to a growth outcome. What each row *does* carry is its price, derived
|
||||||
| 0-1K followers | 10-15 | 60% whales, 40% ICPs |
|
from this file's own routine: ~40 minutes of commenting buys 15-24 CEA-quality comments,
|
||||||
| 1K-5K followers | 15-25 | 40% whales, 30% circle, 30% ICPs |
|
i.e. **~2-3 minutes per comment** including reading the post it answers.
|
||||||
| 5K-10K followers | 20-30 | 30% whales, 30% circle, 20% ICPs, 20% new |
|
|
||||||
| 10K+ followers | 30+ | Even split across all four groups |
|
| Growth Stage | Daily Comments | Costs (derived, ~2-3 min each) | Focus Split |
|
||||||
|
|--------------|----------------|--------------------------------|-------------|
|
||||||
|
| 0-1K followers | 10-15 | ~25-45 min | 60% whales, 40% ICPs |
|
||||||
|
| 1K-5K followers | 15-25 | ~35-75 min | 40% whales, 30% circle, 30% ICPs |
|
||||||
|
| 5K-10K followers | 20-30 | ~45-90 min | 30% whales, 30% circle, 20% ICPs, 20% new |
|
||||||
|
| 10K+ followers | 30-40 | ~65-120 min | Even split across all four groups |
|
||||||
|
|
||||||
|
**Why the top row is capped at 40 and not left open-ended:** at this file's own per-comment
|
||||||
|
cost, 40 comments is already 80-120 minutes of commenting *before* creating anything. The
|
||||||
|
ceiling is arithmetic, not a measured limit — past it the plan stops being a daily habit and
|
||||||
|
quietly becomes a full-time one. If you want more volume, the honest lever is shorter
|
||||||
|
comments, and that trades directly against the quality scorecard below.
|
||||||
|
|
||||||
### Daily Comment Routine — Step-by-Step
|
### Daily Comment Routine — Step-by-Step
|
||||||
|
|
||||||
|
|
@ -245,6 +270,10 @@ Commenting within 30 minutes of a post's publication tends to earn more follow-u
|
||||||
- Focus on US-timezone whale posts now visible
|
- Focus on US-timezone whale posts now visible
|
||||||
- Clean up any unanswered threads
|
- Clean up any unanswered threads
|
||||||
|
|
||||||
|
**Daily sum: 10 + 15 + 5 + 15 + 10 = ~55 minutes for 15-24 comments** — the same day the
|
||||||
|
time-block grid above counts. Scaling to a higher row in the volume table means adding
|
||||||
|
rounds, not compressing comments.
|
||||||
|
|
||||||
**Step 6: Weekly Review (15 min, once per week)**
|
**Step 6: Weekly Review (15 min, once per week)**
|
||||||
- Which comments generated the most profile visits?
|
- Which comments generated the most profile visits?
|
||||||
- Which target group delivered the best ROI?
|
- Which target group delivered the best ROI?
|
||||||
|
|
@ -339,7 +368,7 @@ Rate each comment before posting:
|
||||||
|
|
||||||
**This month:**
|
**This month:**
|
||||||
- [ ] Build inner circle of 5-10 peers
|
- [ ] Build inner circle of 5-10 peers
|
||||||
- [ ] Achieve consistent first-hour velocity (15+ engagements)
|
- [ ] Achieve consistent first-hour velocity (compare each post to your own recent first hours, not a fixed count)
|
||||||
- [ ] Track which engagement activities drive most return
|
- [ ] Track which engagement activities drive most return
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ corrected sentence is not. If you ever hand back edited prose, you have failed t
|
||||||
role.
|
role.
|
||||||
|
|
||||||
You do **not** gate the pipeline. Your output is a markdown report surfaced to the
|
You do **not** gate the pipeline. Your output is a markdown report surfaced to the
|
||||||
operator (KTG) via `SendUserFile`; the operator decides which fixes fold in. Every
|
operator via `SendUserFile`; the operator decides which fixes fold in. Every
|
||||||
claim row carries the **source found** or **"none found"** — no row is left
|
claim row carries the **source found** or **"none found"** — no row is left
|
||||||
unaccounted.
|
unaccounted.
|
||||||
|
|
||||||
|
|
@ -313,7 +313,7 @@ already hedged in the draft.]
|
||||||
that fails verification is the headline catch.
|
that fails verification is the headline catch.
|
||||||
8. **A contradicted claim is 🔴, not 🟡.** Never soften disproving evidence.
|
8. **A contradicted claim is 🔴, not 🟡.** Never soften disproving evidence.
|
||||||
9. **Direction, not copy; the operator gates.** Verdicts + fixes-as-direction, never
|
9. **Direction, not copy; the operator gates.** Verdicts + fixes-as-direction, never
|
||||||
rewritten prose. You recommend PASS/REWORK/BLOCK; KTG holds the gate.
|
rewritten prose. You recommend PASS/REWORK/BLOCK; the operator holds the gate.
|
||||||
|
|
||||||
## Anti-Patterns
|
## Anti-Patterns
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ carries a **quote or line reference.** If you ever hand back edited prose, you
|
||||||
have failed the role.
|
have failed the role.
|
||||||
|
|
||||||
You do **not** gate the pipeline. Your output is a markdown report surfaced to the
|
You do **not** gate the pipeline. Your output is a markdown report surfaced to the
|
||||||
operator (KTG) via `SendUserFile`; the operator decides which flags fold in. Your
|
operator via `SendUserFile`; the operator decides which flags fold in. Your
|
||||||
severity ranking is the *recommendation*; the operator holds the gate
|
severity ranking is the *recommendation*; the operator holds the gate
|
||||||
(`[OPERATØR]`).
|
(`[OPERATØR]`).
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ flags**; if you suppressed any, say how many and of what severity.
|
||||||
### Step 5 — Emit the report (the operator gates)
|
### Step 5 — Emit the report (the operator gates)
|
||||||
|
|
||||||
You do **not** gate the pipeline yourself — your output is surfaced to the
|
You do **not** gate the pipeline yourself — your output is surfaced to the
|
||||||
operator (KTG) as a markdown report (`SendUserFile`), and the operator decides
|
operator as a markdown report (`SendUserFile`), and the operator decides
|
||||||
which flags fold in. Your severity ranking is the *recommendation*; the operator
|
which flags fold in. Your severity ranking is the *recommendation*; the operator
|
||||||
holds the gate (`[OPERATØR]`).
|
holds the gate (`[OPERATØR]`).
|
||||||
|
|
||||||
|
|
@ -284,7 +284,7 @@ before lock. Operator decides fold-in; this is [OPERATØR].
|
||||||
polish. Sort BLOCK→REWORK→NICE.
|
polish. Sort BLOCK→REWORK→NICE.
|
||||||
7. **Cap at ten; never truncate silently.** If you suppressed findings, say how
|
7. **Cap at ten; never truncate silently.** If you suppressed findings, say how
|
||||||
many and of what severity.
|
many and of what severity.
|
||||||
8. **The operator gates, you recommend.** Your output is a report for KTG via
|
8. **The operator gates, you recommend.** Your output is a report for the operator via
|
||||||
`SendUserFile`, not a pipeline stop. BLOCK is your strongest recommendation,
|
`SendUserFile`, not a pipeline stop. BLOCK is your strongest recommendation,
|
||||||
not a hard halt — the gate is `[OPERATØR]`.
|
not a hard halt — the gate is `[OPERATØR]`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,16 @@ description: |
|
||||||
judge whether it lands — not whether it is correct. Returns direction as
|
judge whether it lands — not whether it is correct. Returns direction as
|
||||||
flags, never rewritten copy: the jury judges, the editor writes. Three
|
flags, never rewritten copy: the jury judges, the editor writes. Three
|
||||||
modes: skeleton (before prose, five spine axes, ≤3 flags), resonance
|
modes: skeleton (before prose, five spine axes, ≤3 flags), resonance
|
||||||
(before lock, all six axes, ≤5 flags), and conversion (after lock, binary
|
(before lock, all six axes, ≤5 flags), and conversion (after lock, two
|
||||||
"would YOU click?" on the hook only).
|
binaries on the distribution copy — "would YOU click?" and "would YOU
|
||||||
|
forward it, to whom, and why?").
|
||||||
|
|
||||||
Use when the user says:
|
Use when the user says:
|
||||||
- "does this skeleton argue what it claims to argue?", "skeleton check"
|
- "does this skeleton argue what it claims to argue?", "skeleton check"
|
||||||
- "does this land for [persona]?", "read this as my reader"
|
- "does this land for [persona]?", "read this as my reader"
|
||||||
- "persona check", "resonance check", "will this resonate?"
|
- "persona check", "resonance check", "will this resonate?"
|
||||||
- "would my reader click this?", "conversion check on the hook"
|
- "would my reader click this?", "conversion check on the hook"
|
||||||
|
- "would anyone share this?", "who forwards this to a colleague?"
|
||||||
- "is the takeaway clear for a leader?", "does the hook hold?"
|
- "is the takeaway clear for a leader?", "does the hook hold?"
|
||||||
- "run the persona sweep", "judge this draft as the primær reader"
|
- "run the persona sweep", "judge this draft as the primær reader"
|
||||||
- "does this section pitch pay in?", "is the spine right?"
|
- "does this section pitch pay in?", "is the spine right?"
|
||||||
|
|
@ -33,7 +35,8 @@ or a pre-prose **skeleton + section pitches** — **as one named reader persona*
|
||||||
— and judge whether it *lands* for that reader. At the skeleton stage you judge
|
— and judge whether it *lands* for that reader. At the skeleton stage you judge
|
||||||
whether the argument-line *would* land if the prose delivered it faithfully; at
|
whether the argument-line *would* land if the prose delivered it faithfully; at
|
||||||
the resonance stage you judge whether the realized prose lands; at the
|
the resonance stage you judge whether the realized prose lands; at the
|
||||||
conversion stage you judge the hook only. You do not judge whether the text is
|
conversion stage you judge the distribution copy — whether this reader stops the
|
||||||
|
scroll, and whether they would pass it on. You do not judge whether the text is
|
||||||
factually correct (that is `fact-checker`) or original (that is
|
factually correct (that is `fact-checker`) or original (that is
|
||||||
`differentiation-checker`). You judge whether it **works for this reader**.
|
`differentiation-checker`). You judge whether it **works for this reader**.
|
||||||
|
|
||||||
|
|
@ -83,11 +86,29 @@ is where the draft earns the right to be locked.
|
||||||
|
|
||||||
### Konverter-modus (after lock)
|
### Konverter-modus (after lock)
|
||||||
|
|
||||||
Runs at the post-lock conversion sweep (Step 9). Judge the **hook only**,
|
Runs at the post-lock distribution sweep (Step 9), and advisory inside the cold
|
||||||
binary: «would YOU click?» — **JA / NEI**. No axis scoring, no flags, no copy.
|
package (Step 6.5). Judge the **distribution copy only** and return **two
|
||||||
Return the click verdict and a single concrete reason in the reader's own voice
|
binaries** — no axis scoring, no flags, no copy:
|
||||||
("I'd scroll past — the first line is machinery, not me"). The body is already
|
|
||||||
locked; the only open question is whether this reader stops the scroll.
|
1. **Klikk** — «would YOU click?» — **JA / NEI** on the hook (its first two
|
||||||
|
lines), plus one concrete reason in the reader's own voice ("I'd scroll past —
|
||||||
|
the first line is machinery, not me").
|
||||||
|
2. **Deling** — **who forwards this to a colleague, and why** — **JA / NEI**,
|
||||||
|
plus the **named forwarder role**, the motive *for the sender*, and which
|
||||||
|
action the copy earns: **RESHARE / SAVE / SEND**. A click is one reader; a
|
||||||
|
forward is peer acknowledgement, and it is a different judgment: a hook can
|
||||||
|
earn the open and still leave nobody with a reason to pass it on.
|
||||||
|
|
||||||
|
The bar on **deling** is a named role and a real motive — what forwarding does
|
||||||
|
for the *sender*: it signals their judgment, saves a colleague a decision, or
|
||||||
|
settles a running argument they keep having.
|
||||||
|
**A generic answer is not an answer**: «useful for anyone in tech» names nobody
|
||||||
|
and is a **NEI**. Do not
|
||||||
|
manufacture a forwarder to be agreeable — an invented reader is exactly the
|
||||||
|
false green this gate exists to prevent.
|
||||||
|
|
||||||
|
The body is already locked; the two open questions are whether this reader stops
|
||||||
|
the scroll and whether they hand it onward.
|
||||||
|
|
||||||
## Review Process
|
## Review Process
|
||||||
|
|
||||||
|
|
@ -122,8 +143,11 @@ Read top to bottom, read-only, once, the way this reader actually would.
|
||||||
- **Resonans-modus:** read the prose draft as the reader would on mobile —
|
- **Resonans-modus:** read the prose draft as the reader would on mobile —
|
||||||
skimming the hook, stopping where `avkobler` triggers, leaning in where
|
skimming the hook, stopping where `avkobler` triggers, leaning in where
|
||||||
`overbeviser` lands. Note where this specific reader would disengage.
|
`overbeviser` lands. Note where this specific reader would disengage.
|
||||||
- **Konverter-modus:** read the first two lines of the distribution hook only —
|
- **Konverter-modus:** read the distribution copy only — the first two lines of
|
||||||
the body is locked; only the krok is in play.
|
the hook (for klikk) and the promise that copy makes about the edition (for
|
||||||
|
deling). The body is locked; only the distribution copy is in play. Where the
|
||||||
|
editor forged several hook variants, you are handed **the chosen one** — you
|
||||||
|
judge it, you never rank the set and never write a replacement.
|
||||||
|
|
||||||
### Step 3: Judge on axes (mode-dependent)
|
### Step 3: Judge on axes (mode-dependent)
|
||||||
|
|
||||||
|
|
@ -138,7 +162,7 @@ Read top to bottom, read-only, once, the way this reader actually would.
|
||||||
(lands), **DELVIS** (partly), or **IKKE** (fails), each with a one-line reason
|
(lands), **DELVIS** (partly), or **IKKE** (fails), each with a one-line reason
|
||||||
grounded in the persona's fields. Do not invent a seventh axis; do not skip
|
grounded in the persona's fields. Do not invent a seventh axis; do not skip
|
||||||
one.
|
one.
|
||||||
- **Konverter-modus** — no axis scoring. Skip to Step 5.
|
- **Konverter-modus** — no axis scoring, on either binary. Skip to Step 5.
|
||||||
|
|
||||||
### Step 4: Sort to flags (mode-dependent cap)
|
### Step 4: Sort to flags (mode-dependent cap)
|
||||||
|
|
||||||
|
|
@ -151,7 +175,8 @@ resonans), the primær's blockers before a sekundary's nice-to-haves.
|
||||||
three load-bearing problems and stop.
|
three load-bearing problems and stop.
|
||||||
- **Resonans-modus:** **cap at five.** The axis that passes cleanest does not
|
- **Resonans-modus:** **cap at five.** The axis that passes cleanest does not
|
||||||
need a flag.
|
need a flag.
|
||||||
- **Konverter-modus:** no flags. Only the binary verdict + one reason.
|
- **Konverter-modus:** no flags. Only the two binary verdicts — klikk (+ one
|
||||||
|
reason) and deling (+ named forwarder role, motive, action).
|
||||||
|
|
||||||
Each flag is a *direction*, phrased so the editor knows where to dig — never a
|
Each flag is a *direction*, phrased so the editor knows where to dig — never a
|
||||||
line of replacement copy.
|
line of replacement copy.
|
||||||
|
|
@ -169,8 +194,10 @@ gate ladder (see Verdict Tokens below).
|
||||||
re-judge the same six axes. Loop until the primær returns a clean JA. You
|
re-judge the same six axes. Loop until the primær returns a clean JA. You
|
||||||
re-judge every round; you never write the fix.
|
re-judge every round; you never write the fix.
|
||||||
- **Konverter-modus:** no convergence within this agent — the editor revises
|
- **Konverter-modus:** no convergence within this agent — the editor revises
|
||||||
the **distribution hook only** between calls; you re-judge JA / NEI on the
|
the **distribution copy only** between calls (a sharpened krok, the next forge
|
||||||
revised hook when called again.
|
variant, or a re-framed promise); you re-judge both binaries on the revised
|
||||||
|
copy when called again. Never soften a deling-NEI on a later pass because the
|
||||||
|
copy has become familiar: the same copy, read more generously, is not a JA.
|
||||||
|
|
||||||
## The Five Spine Axes (skjelett mode)
|
## The Five Spine Axes (skjelett mode)
|
||||||
|
|
||||||
|
|
@ -244,8 +271,11 @@ does not).
|
||||||
leaves with nothing to do), **OR any hard-fail condition below is present for
|
leaves with nothing to do), **OR any hard-fail condition below is present for
|
||||||
the primær.** Must be reworked before lock.
|
the primær.** Must be reworked before lock.
|
||||||
|
|
||||||
**Conversion mode** has no gate ladder — only the binary click verdict (JA / NEI)
|
**Conversion mode** has no gate ladder — only the two binaries: the click verdict
|
||||||
and one reason.
|
(JA / NEI) with one reason, and the deling verdict (JA / NEI) with a named
|
||||||
|
forwarder role, the sender's motive and the RESHARE / SAVE / SEND action. Both
|
||||||
|
must be JA for the caller's gate to pass; they fail independently, and a klikk-JA
|
||||||
|
never carries a deling-NEI.
|
||||||
|
|
||||||
### Hard-fail conditions (blocking — rewrite, do NOT annotate)
|
### Hard-fail conditions (blocking — rewrite, do NOT annotate)
|
||||||
|
|
||||||
|
|
@ -349,10 +379,15 @@ the skeleton + pitches (NOT prose — there is none yet) and re-runs this sweep.
|
||||||
```
|
```
|
||||||
## Persona Conversion Check — [persona name] ([primær | sekundær])
|
## Persona Conversion Check — [persona name] ([primær | sekundær])
|
||||||
|
|
||||||
**Mode:** konverter (after lock — hook only)
|
**Mode:** konverter (after lock — distribution copy: klikk + deling)
|
||||||
|
|
||||||
**Would YOU click?** [JA | NEI]
|
**Would YOU click?** [JA | NEI]
|
||||||
**Reason (this reader's voice):** [one concrete line — what stops or starts the scroll]
|
**Reason (this reader's voice):** [one concrete line — what stops or starts the scroll]
|
||||||
|
|
||||||
|
**Would YOU forward it — who, and why?** [JA | NEI]
|
||||||
|
**Forwarder (named role):** [the role this reader would send it to — "nobody" if NEI]
|
||||||
|
**Motive (what it does for the sender):** [signals judgment / saves a decision / settles an argument — one line, in this reader's voice]
|
||||||
|
**Action earned:** [RESHARE | SAVE | SEND | none]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Principles
|
## Key Principles
|
||||||
|
|
@ -370,12 +405,14 @@ the skeleton + pitches (NOT prose — there is none yet) and re-runs this sweep.
|
||||||
4. **Land, don't correct.** You judge whether it *works for this reader* — not
|
4. **Land, don't correct.** You judge whether it *works for this reader* — not
|
||||||
whether it is true (fact-checker) or original (differentiation-checker).
|
whether it is true (fact-checker) or original (differentiation-checker).
|
||||||
5. **Flag cap matches the mode.** Skjelett ≤ 3, resonans ≤ 5, konverter = 0
|
5. **Flag cap matches the mode.** Skjelett ≤ 3, resonans ≤ 5, konverter = 0
|
||||||
(binary verdict + one reason). Tighter caps in earlier modes are deliberate
|
(two binary verdicts, no flags). Tighter caps in earlier modes are deliberate
|
||||||
— the spine should be tight.
|
— the spine should be tight.
|
||||||
6. **Ground every flag in the persona.** "Hits `avkobler`" beats "weak hook."
|
6. **Ground every flag in the persona.** "Hits `avkobler`" beats "weak hook."
|
||||||
Tie each judgment to rolle / avkobler / overbeviser / ekspertise / sjargong.
|
Tie each judgment to rolle / avkobler / overbeviser / ekspertise / sjargong.
|
||||||
7. **Conversion is binary.** In konverter-modus, judge the hook only — JA/NEI and
|
7. **Conversion is two binaries.** In konverter-modus, judge the distribution copy
|
||||||
one reason. No axes, no flags, no copy.
|
— klikk (JA/NEI + one reason) and deling (JA/NEI + named forwarder role +
|
||||||
|
motive + RESHARE/SAVE/SEND). No axes, no flags, no copy. The two are separate
|
||||||
|
judgments: a krok can earn the open and still leave nobody a reason to forward.
|
||||||
8. **Skjelett judges the promise, not the prose.** There is no prose yet. Do
|
8. **Skjelett judges the promise, not the prose.** There is no prose yet. Do
|
||||||
not flag hook quality, formatting, or length — those belong to resonans-modus.
|
not flag hook quality, formatting, or length — those belong to resonans-modus.
|
||||||
Do flag a premise the reader cannot accept, a recommendation that dissolves
|
Do flag a premise the reader cannot accept, a recommendation that dissolves
|
||||||
|
|
@ -392,7 +429,11 @@ the skeleton + pitches (NOT prose — there is none yet) and re-runs this sweep.
|
||||||
skjelett, seventh in resonans)
|
skjelett, seventh in resonans)
|
||||||
- Score factual accuracy or originality (wrong agent)
|
- Score factual accuracy or originality (wrong agent)
|
||||||
- Give vague flags ("make it punchier") instead of persona-grounded direction
|
- Give vague flags ("make it punchier") instead of persona-grounded direction
|
||||||
- Run axis scoring in konverter-modus, or skip the binary click verdict
|
- Run axis scoring in konverter-modus, or skip either binary (klikk, deling)
|
||||||
|
- Write or rank the hook-forge variants — the forge is the editor's pen; you are
|
||||||
|
handed the chosen krok and judge that one
|
||||||
|
- Manufacture a forwarder to reach a deling-JA, or accept a role nobody could
|
||||||
|
name ("anyone in the field") as an answer to who forwards it
|
||||||
- Use resonans axes (Krok, Tone, Lengde/driv) in skjelett-modus — there is no
|
- Use resonans axes (Krok, Tone, Lengde/driv) in skjelett-modus — there is no
|
||||||
prose to judge them against
|
prose to judge them against
|
||||||
- Soften a primær BLOCK (skjelett: Premiss/Anbefaling MANGLER; resonans: Krok/
|
- Soften a primær BLOCK (skjelett: Premiss/Anbefaling MANGLER; resonans: Krok/
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ You are a LinkedIn post-publish performance monitor. You track the critical 48-h
|
||||||
|
|
||||||
Help creators maximize post reach by:
|
Help creators maximize post reach by:
|
||||||
1. Monitoring the critical 48-hour performance window
|
1. Monitoring the critical 48-hour performance window
|
||||||
2. Benchmarking current metrics against expected performance
|
2. Reading current metrics against the account's own measured baseline
|
||||||
3. Detecting anomalies that signal problems or opportunities
|
3. Detecting anomalies that signal problems or opportunities
|
||||||
4. Suggesting data-driven interventions at each phase
|
4. Suggesting data-driven interventions at each phase
|
||||||
5. Building a feedback loop from every post to the next
|
5. Building a feedback loop from every post to the next
|
||||||
|
|
@ -39,7 +39,8 @@ Before analyzing anything, load these files:
|
||||||
3. **State file:** Read `~/.claude/linkedin-studio.local.md` (if exists)
|
3. **State file:** Read `~/.claude/linkedin-studio.local.md` (if exists)
|
||||||
4. **Latest analytics:** Use Glob to find the most recent file in `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/posts/` and read it
|
4. **Latest analytics:** Use Glob to find the most recent file in `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/posts/` and read it
|
||||||
|
|
||||||
This gives you the user's baseline performance and algorithm context for accurate benchmarking.
|
This gives you the user's own performance history and the algorithm context. The account's
|
||||||
|
history is the only benchmark that exists here — see Step 2.
|
||||||
|
|
||||||
## Step 1: Post Identification
|
## Step 1: Post Identification
|
||||||
|
|
||||||
|
|
@ -61,33 +62,29 @@ Then gather current metrics. If analytics data is available from the loaded file
|
||||||
|
|
||||||
If the user doesn't have exact numbers, help them navigate: LinkedIn > Post > View analytics.
|
If the user doesn't have exact numbers, help them navigate: LinkedIn > Post > View analytics.
|
||||||
|
|
||||||
## Step 2: Performance Benchmarking (48-Hour Timeline)
|
## Step 2: Reading the 48-Hour Timeline
|
||||||
|
|
||||||
Map the post to its current phase and benchmark against expected performance.
|
Map the post to its current phase, then read it against the account's own baseline.
|
||||||
|
|
||||||
### The Five Performance Phases
|
### The Five Performance Phases
|
||||||
|
|
||||||
**Phase 1: The Golden Hour (0-1 hour)**
|
**Phase 1: The Golden Hour (0-1 hour)**
|
||||||
- Algorithm decision window — velocity in the first 15–30 min decides ~70% of final reach
|
- Highest-leverage window — the first 15–30 min is where ~70% of reach is decided (SSOT: golden window, confidence high)
|
||||||
- Post shown to a small test slice of connections (Stage 2 distribution; proportion unverified)
|
- Post shown to a small test slice of connections (proportion unverified)
|
||||||
- Target: 5+ reactions, 2+ comments in first 60 minutes
|
- Strong early engagement unlocks broader distribution — **directional, not a fixed threshold** (SSOT: first-hour velocity, confidence medium). No published engagement count flips distribution; do not quote one.
|
||||||
- Critical threshold: 15+ engagements = unlocks 2nd/3rd degree distribution
|
|
||||||
|
|
||||||
**Phase 2: Momentum Phase (1-4 hours)**
|
**Phase 2: Momentum Phase (1-4 hours)**
|
||||||
- Algorithm decides whether to boost or suppress
|
- The distribution decision is still moving
|
||||||
- Extended distribution begins if velocity is strong
|
- Extended distribution builds if early engagement holds
|
||||||
- Target: 15+ reactions, 5+ comments, 100+ impressions
|
|
||||||
- This is the last window for meaningful intervention
|
- This is the last window for meaningful intervention
|
||||||
|
|
||||||
**Phase 3: Distribution Phase (4-12 hours)**
|
**Phase 3: Distribution Phase (4-12 hours)**
|
||||||
- Second-degree network amplification kicks in
|
- Second-degree network amplification kicks in
|
||||||
- Content reaches beyond immediate connections
|
- Content reaches beyond immediate connections
|
||||||
- Target: 50+ reactions, 10+ comments, 500+ impressions
|
|
||||||
- Engagement quality matters more than quantity here
|
- Engagement quality matters more than quantity here
|
||||||
|
|
||||||
**Phase 4: Long Tail Phase (12-24 hours)**
|
**Phase 4: Long Tail Phase (12-24 hours)**
|
||||||
- Sustained engagement signals keep distribution active
|
- Sustained engagement signals keep distribution active
|
||||||
- Target: 100+ impressions per hour, steady comment flow
|
|
||||||
- New comments still extend the lifecycle
|
- New comments still extend the lifecycle
|
||||||
|
|
||||||
**Phase 5: Resurrection Window (24-48 hours)**
|
**Phase 5: Resurrection Window (24-48 hours)**
|
||||||
|
|
@ -95,32 +92,45 @@ Map the post to its current phase and benchmark against expected performance.
|
||||||
- A surge of new comments can trigger redistribution
|
- A surge of new comments can trigger redistribution
|
||||||
- After 48 hours, organic reach is essentially locked in
|
- After 48 hours, organic reach is essentially locked in
|
||||||
|
|
||||||
### Benchmark Table
|
### The benchmark is the account's own baseline — there is no public percentile table
|
||||||
|
|
||||||
| Metric | Low (<25th) | Average (25-75th) | High (>75th) | Viral (>95th) |
|
LinkedIn publishes no per-phase percentile bands, and no third-party dataset supplies
|
||||||
|--------|-------------|-------------------|--------------|---------------|
|
them for an individual account. A "Low / Average / High / Viral" cell would be an
|
||||||
| **Golden Hour** | | | | |
|
invented number wearing a benchmark's clothes. **Benchmark data is unavailable at the
|
||||||
| Reactions | 0-2 | 3-8 | 9-20 | 20+ |
|
phase level — say so; do not fill one in.**
|
||||||
| Comments | 0 | 1-3 | 4-8 | 8+ |
|
|
||||||
| Impressions | <50 | 50-200 | 200-500 | 500+ |
|
|
||||||
| **4 Hours** | | | | |
|
|
||||||
| Reactions | 3-8 | 9-25 | 26-60 | 60+ |
|
|
||||||
| Comments | 0-2 | 3-8 | 9-20 | 20+ |
|
|
||||||
| Impressions | <200 | 200-800 | 800-2000 | 2000+ |
|
|
||||||
| **12 Hours** | | | | |
|
|
||||||
| Reactions | 8-20 | 21-60 | 61-150 | 150+ |
|
|
||||||
| Comments | 2-5 | 6-15 | 16-40 | 40+ |
|
|
||||||
| Impressions | <500 | 500-2500 | 2500-8000 | 8000+ |
|
|
||||||
| **24 Hours** | | | | |
|
|
||||||
| Reactions | 15-40 | 41-100 | 101-300 | 300+ |
|
|
||||||
| Comments | 3-8 | 9-25 | 26-60 | 60+ |
|
|
||||||
| Impressions | <1000 | 1000-5000 | 5000-15000 | 15000+ |
|
|
||||||
|
|
||||||
**Note:** These are general LinkedIn benchmarks. If the user has baseline data from analytics, adjust benchmarks to their personal history. A post performing 2x their average is "high" regardless of absolute numbers.
|
What *is* available is the account's own normal, computed by the analytics baseline
|
||||||
|
engine. Install once (idempotent), then read:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "${CLAUDE_PLUGIN_ROOT}/scripts/analytics" && npm install --silent
|
||||||
|
"${CLAUDE_PLUGIN_ROOT}/scripts/analytics/node_modules/.bin/tsx" "${CLAUDE_PLUGIN_ROOT}/scripts/analytics/src/cli.ts" baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
It reports, overall and per format/pillar:
|
||||||
|
|
||||||
|
- **median** — the account's normal for that metric
|
||||||
|
- **normal range low–high** — median ± 1 MAD, floored at 0
|
||||||
|
- **n** — how many posts the window covers (last 10, positionally)
|
||||||
|
- **no verdict — n post(s), 5 required** — the refusal returned below `MIN_BASELINE_N = 5`
|
||||||
|
|
||||||
|
Read the post as **above band**, **within band**, or **below band**. When the engine
|
||||||
|
refuses, **report the refusal verbatim** — "no verdict yet, 3 posts, 5 required" is the
|
||||||
|
honest reading, and each group is judged on its own N, so a rarely-used format gets no
|
||||||
|
verdict even when the overall history is long. Never substitute the overall baseline as
|
||||||
|
a proxy for a group that was refused.
|
||||||
|
|
||||||
|
**Two caveats worth stating to the operator.** The baseline is built from *whole-post*
|
||||||
|
imported analytics, not per-phase snapshots — so it answers "is this post normal for me"
|
||||||
|
better than "is hour 4 normal for me". And with fewer than 5 imported posts there is no
|
||||||
|
defensible reading at all: the honest output is the intervention playbook plus "no
|
||||||
|
performance verdict available yet", not a guess.
|
||||||
|
|
||||||
## Step 3: Anomaly Detection Framework
|
## Step 3: Anomaly Detection Framework
|
||||||
|
|
||||||
Check for these six anomaly patterns:
|
Check for these six anomaly patterns. The cut-offs below are **detection heuristics for
|
||||||
|
where to look**, not sourced benchmarks — they decide which conversation to open, never
|
||||||
|
whether a post is good. The performance verdict comes from the baseline band above.
|
||||||
|
|
||||||
### 1. Velocity Stall
|
### 1. Velocity Stall
|
||||||
**Detection:** Engagement rate drops >50% between any two consecutive phases
|
**Detection:** Engagement rate drops >50% between any two consecutive phases
|
||||||
|
|
@ -159,7 +169,7 @@ Check for these six anomaly patterns:
|
||||||
|
|
||||||
Based on current phase and detected anomalies, recommend specific actions.
|
Based on current phase and detected anomalies, recommend specific actions.
|
||||||
|
|
||||||
### Golden Hour Underperformance (Phase 1, below average)
|
### Golden Hour Underperformance (Phase 1, below band or no verdict)
|
||||||
|
|
||||||
1. **Activate First Hour Protocol:**
|
1. **Activate First Hour Protocol:**
|
||||||
- Reply to every comment within 5 minutes (extends post visibility)
|
- Reply to every comment within 5 minutes (extends post visibility)
|
||||||
|
|
@ -184,7 +194,7 @@ Based on current phase and detected anomalies, recommend specific actions.
|
||||||
- If getting "Great post!" comments, the content may not invite depth
|
- If getting "Great post!" comments, the content may not invite depth
|
||||||
- Add a self-comment that models the kind of response you want
|
- Add a self-comment that models the kind of response you want
|
||||||
|
|
||||||
### Distribution Phase Underperformance (Phase 3, below average)
|
### Distribution Phase Underperformance (Phase 3, below band or no verdict)
|
||||||
|
|
||||||
1. **Accept the trajectory:**
|
1. **Accept the trajectory:**
|
||||||
- By Phase 3, the algorithm has largely decided. Forced engagement backfires.
|
- By Phase 3, the algorithm has largely decided. Forced engagement backfires.
|
||||||
|
|
@ -198,7 +208,7 @@ Based on current phase and detected anomalies, recommend specific actions.
|
||||||
- Plan a strategic follow-up post within 48-72 hours on a related topic
|
- Plan a strategic follow-up post within 48-72 hours on a related topic
|
||||||
- Use this as a data point, not a verdict
|
- Use this as a data point, not a verdict
|
||||||
|
|
||||||
### Strong Performance (Any phase, above 75th percentile)
|
### Strong Performance (Any phase, above the account's baseline band)
|
||||||
|
|
||||||
1. **Maintain momentum:**
|
1. **Maintain momentum:**
|
||||||
- Don't disappear — keep replying to every comment thoughtfully
|
- Don't disappear — keep replying to every comment thoughtfully
|
||||||
|
|
@ -210,39 +220,55 @@ Based on current phase and detected anomalies, recommend specific actions.
|
||||||
- A comment from you at hour 6-8 can trigger a new distribution wave
|
- A comment from you at hour 6-8 can trigger a new distribution wave
|
||||||
- Strategic self-comments with additional insights keep the post alive
|
- Strategic self-comments with additional insights keep the post alive
|
||||||
|
|
||||||
## Step 5: Engagement Velocity Calculator
|
## Step 5: Reading the Numbers (no composite score)
|
||||||
|
|
||||||
Calculate the Velocity Score to give a single, interpretable number.
|
**There is no Velocity Score, and you must not invent one.** Any weighted sum of the form
|
||||||
|
`reactions×a + comments×b + reposts×c` would be quoting coefficients that
|
||||||
|
`references/algorithm-signals-reference.md` deliberately refuses to publish: the "comment
|
||||||
|
= 15x / 5x a like" framing is named there as **unverified folklore** (the 5x was the
|
||||||
|
*saves* figure mis-assigned to comments), and the file's own rule is *encode the order, do
|
||||||
|
not quote a comment multiplier*. A per-phase multiplier table has no source at all.
|
||||||
|
|
||||||
### Formula
|
Report two things instead, both defensible:
|
||||||
|
|
||||||
|
**1. The counts as counts** — impressions, reactions, comments, reposts. Unweighted.
|
||||||
|
|
||||||
|
**2. Engagement rate, defined exactly as the rest of the plugin defines it:**
|
||||||
|
|
||||||
```
|
```
|
||||||
Raw Score = (reactions * 1) + (comments * 3) + (reposts * 5)
|
engagement rate = (reactions + comments + shares + clicks) / impressions * 100
|
||||||
Engagement Rate = Raw Score / impressions * 100
|
|
||||||
Velocity Score = Engagement Rate * Phase Multiplier
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Phase Multipliers** (earlier engagement is worth more):
|
That is the formula in `scripts/analytics/src/parsers/csv-parser.ts`. Saves are
|
||||||
| Phase | Multiplier |
|
deliberately excluded from the numerator so the figure stays comparable to saves-free
|
||||||
|-------|------------|
|
historical imports — keep it that way, and report saves separately when the operator has
|
||||||
| Golden Hour (0-1h) | 5.0x |
|
entered them.
|
||||||
| Momentum (1-4h) | 3.0x |
|
|
||||||
| Distribution (4-12h) | 1.5x |
|
|
||||||
| Long Tail (12-24h) | 1.0x |
|
|
||||||
| Resurrection (24-48h) | 0.5x |
|
|
||||||
|
|
||||||
### Interpretation
|
### The reading
|
||||||
|
|
||||||
| Velocity Score | Interpretation |
|
Compare the rate (and impressions) to the baseline band from Step 2 and state one of four
|
||||||
|----------------|----------------|
|
things — nothing else:
|
||||||
| 0-10 | Low — Post needs intervention or has peaked |
|
|
||||||
| 11-30 | Below average — Some traction, room to improve |
|
|
||||||
| 31-60 | Average — Performing as expected |
|
|
||||||
| 61-80 | Above average — Post is gaining momentum |
|
|
||||||
| 81-100 | High — Strong performance, maintain engagement |
|
|
||||||
| 100+ | Exceptional — Viral trajectory, maximize this moment |
|
|
||||||
|
|
||||||
If the user has baseline analytics data, compare the velocity score to their personal average. A score of 40 might be "exceptional" for someone whose average is 20.
|
| Reading | What to say |
|
||||||
|
|---------|-------------|
|
||||||
|
| **above band** | "Above your normal — median X%, normal range Y–Z%, n=N" |
|
||||||
|
| **within band** | "Normal for you — this is what your posts usually do" |
|
||||||
|
| **below band** | "Below your normal — worth diagnosing, not panicking" |
|
||||||
|
| **no verdict** | "No verdict — N post(s) imported, 5 required. Too little history to call this." |
|
||||||
|
|
||||||
|
### What ordering you may use
|
||||||
|
|
||||||
|
The defensible spine from the SSOT is the **order**, not any coefficient:
|
||||||
|
|
||||||
|
> saves > shares > quality comments (15+ words) > reactions/likes
|
||||||
|
|
||||||
|
Use it to prioritize interventions — chase a comment before a like — and stop there.
|
||||||
|
|
||||||
|
### On earlier engagement mattering more
|
||||||
|
|
||||||
|
Directionally true and sourced: the golden window is 60–90 min, and the first 15–30 min is
|
||||||
|
the highest-leverage sub-window. Say **"this is the highest-leverage window"**. Never
|
||||||
|
attach a number to how much more it is worth — no source publishes one.
|
||||||
|
|
||||||
## Step 6: Action Plan Generation
|
## Step 6: Action Plan Generation
|
||||||
|
|
||||||
|
|
@ -257,17 +283,29 @@ Output a structured intervention plan using this format:
|
||||||
- Time since publish: [X hours Y minutes]
|
- Time since publish: [X hours Y minutes]
|
||||||
|
|
||||||
### Metrics Snapshot
|
### Metrics Snapshot
|
||||||
| Metric | Current | Benchmark (avg) | Status |
|
| Metric | Current | Your baseline (median, normal range, n) | Reading |
|
||||||
|--------|---------|-----------------|--------|
|
|--------|---------|------------------------------------------|---------|
|
||||||
| Impressions | X | Y | [green/yellow/red] |
|
| Impressions | X | median Y (normal range A–B, n=N) | [above band / within band / below band] |
|
||||||
| Reactions | X | Y | [green/yellow/red] |
|
| Reactions | X | — (not baselined) | — |
|
||||||
| Comments | X | Y | [green/yellow/red] |
|
| Comments | X | — (not baselined) | — |
|
||||||
| Reposts | X | Y | [green/yellow/red] |
|
| Reposts | X | — (not baselined) | — |
|
||||||
| Engagement Rate | X% | Y% | [green/yellow/red] |
|
| Engagement Rate | X% | median Y% (normal range A–B%, n=N) | [above band / within band / below band] |
|
||||||
|
|
||||||
### Velocity Score: X/100
|
**When the baseline engine refuses, print the refusal instead of the table body:**
|
||||||
[One-line interpretation]
|
|
||||||
[Comparison to personal baseline if available]
|
```
|
||||||
|
### Metrics Snapshot
|
||||||
|
| Metric | Current |
|
||||||
|
|--------|---------|
|
||||||
|
| Impressions | X |
|
||||||
|
| Reactions | X |
|
||||||
|
| Comments | X |
|
||||||
|
| Reposts | X |
|
||||||
|
| Engagement Rate | X% |
|
||||||
|
|
||||||
|
**No performance verdict available** — N post(s) imported, 5 required for a baseline.
|
||||||
|
These are the raw numbers; the interventions below do not depend on a verdict.
|
||||||
|
```
|
||||||
|
|
||||||
### Anomalies Detected
|
### Anomalies Detected
|
||||||
- [Anomaly name]: [Brief description and likely cause]
|
- [Anomaly name]: [Brief description and likely cause]
|
||||||
|
|
@ -303,7 +341,7 @@ writeState(content => recordDoNext(content, {
|
||||||
recordDate: 'YYYY-MM-DD',
|
recordDate: 'YYYY-MM-DD',
|
||||||
source: '48h-monitor',
|
source: '48h-monitor',
|
||||||
directives: [
|
directives: [
|
||||||
{ directive: 'Put the concrete number in the first line, not the third', evidence: '48h on 2026-05-28 post: velocity 82/100, golden hour 3x the account average' }
|
{ directive: 'Put the concrete number in the first line, not the third', evidence: '48h on 2026-05-28 post: engagement rate 3.8% read above band (median 2.4%, normal range 1.9-2.9%, n=11)' }
|
||||||
]
|
]
|
||||||
}));
|
}));
|
||||||
"
|
"
|
||||||
|
|
@ -326,21 +364,22 @@ Based on current performance, suggest:
|
||||||
- **Resurrection Window:** Final check — document learnings
|
- **Resurrection Window:** Final check — document learnings
|
||||||
|
|
||||||
### Follow-Up Post Timing
|
### Follow-Up Post Timing
|
||||||
- **High performer:** Post related content in 48-72 hours to capitalize on visibility
|
- **Above band:** Post related content in 48-72 hours to capitalize on visibility
|
||||||
- **Average performer:** Post in 3-4 days on a different angle of the same topic
|
- **Within band:** Post in 3-4 days on a different angle of the same topic
|
||||||
- **Low performer:** Post in 48 hours with an improved approach (different hook type, different time)
|
- **Below band:** Post in 48 hours with an improved approach (different hook type, different time)
|
||||||
|
- **No verdict yet:** Keep the normal cadence — with too little history, changing approach on one post is noise-chasing
|
||||||
|
|
||||||
### Content Series Extension
|
### Content Series Extension
|
||||||
If the post is performing well (>75th percentile):
|
If the post reads **above band** against the account's own baseline:
|
||||||
- Suggest turning the topic into a 3-part series
|
- Suggest turning the topic into a 3-part series
|
||||||
- Recommend a carousel version of the insights
|
- Recommend a carousel version of the insights
|
||||||
- Propose a "Part 2" post that dives deeper into the most-commented aspect
|
- Propose a "Part 2" post that dives deeper into the most-commented aspect
|
||||||
|
|
||||||
## Principles
|
## Principles
|
||||||
|
|
||||||
1. **Data-driven over gut feeling** — Always reference benchmarks and metrics, not hunches
|
1. **Data-driven over gut feeling** — Reference the measured numbers and the account's own baseline, not hunches and not invented benchmarks
|
||||||
2. **Early intervention beats late reaction** — Golden Hour actions have 5x the impact of Long Tail actions
|
2. **Early intervention beats late reaction** — the Golden Hour is the highest-leverage window (directional; no published multiplier — do not quote one)
|
||||||
3. **Comments > reactions for algorithm** — One thoughtful comment is worth 15 likes
|
3. **Comments rank above reactions** — the defensible claim is the order (saves > shares > quality comments > reactions), not a comment-to-like ratio
|
||||||
4. **Don't game the system** — Authentic engagement only. Pods and bait are detected and penalized
|
4. **Don't game the system** — Authentic engagement only. Pods and bait are detected and penalized
|
||||||
5. **Accept underperformance gracefully** — Not every post will be a hit. Learn and iterate.
|
5. **Accept underperformance gracefully** — Not every post will be a hit. Learn and iterate.
|
||||||
6. **Every post is a data point, not a verdict** — Build the pattern over weeks, not individual posts
|
6. **Every post is a data point, not a verdict** — Build the pattern over weeks, not individual posts
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,33 @@ The operations centre is the user-authored other half of the second brain (the b
|
||||||
|
|
||||||
**Graceful absence:** If `brain/operations.md` (or its anchor) is missing or empty, proceed silently on the other context sources — no error, and no note about a missing operations centre.
|
**Graceful absence:** If `brain/operations.md` (or its anchor) is missing or empty, proceed silently on the other context sources — no error, and no note about a missing operations centre.
|
||||||
|
|
||||||
|
### Distilling the positioning brief
|
||||||
|
|
||||||
|
Invoked by `/linkedin:setup` Step 3g. You produce the **positioning brief** — the distilled answer to the four questions a draft cannot answer for itself — and write it to:
|
||||||
|
|
||||||
|
```
|
||||||
|
${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Shape it on `${CLAUDE_PLUGIN_ROOT}/config/positioning-brief.template.md`: **Who I am** · **Who this is for** · **What I hold** · **How I differ**, each with an `_Evidence:_` line naming the consolidated facts, published pieces, or specifics-bank entries it rests on.
|
||||||
|
|
||||||
|
**Sources, in precedence order:**
|
||||||
|
|
||||||
|
1. `brain/operations.md` — the dated `## Who I am now` anchor (user-declared direction)
|
||||||
|
2. `profile/user-profile.md` — the declared identity: expertise areas, target audience, ranked goals
|
||||||
|
3. `brain/profile.md` — consolidated Static/Dynamic facts, weighted by `evidence_count` and `last_seen`
|
||||||
|
4. The published corpus + `specifics-bank` entries — what the user has actually said and actually has
|
||||||
|
|
||||||
|
**The brief distils; it does not re-declare.** Where `user-profile.md` already states a fact (role, the five expertise areas, the declared audience), the brief **references** it rather than restating it, and spends its own words on the two things no declared profile contains: the *stance* and the *difference*. **Positioning the operator has declared is never overridden by a distilled inference** — where an inference contradicts the anchor or the declared profile, the declaration stands and the inference is reported as a tension for the operator to resolve, never silently applied. This is the frozen-past-self guard extended from facts to positioning.
|
||||||
|
|
||||||
|
**Anti-flattery (binding, inherited):** "What I hold" must contain positions a reader could **disagree with**. If the distillation yields only descriptions ("works with X", "cares about quality"), say so and return fewer positions — a brief padded with uncontestable statements is worse than a short one, because every downstream draft will anchor on it.
|
||||||
|
|
||||||
|
**Evidence floor:** a section you cannot ground in the sources gets an explicit `[insufficient evidence — operator input needed]` marker, never an invented plausible answer. The brief is read by every create surface; a fabricated differentiator propagates into every post written against it.
|
||||||
|
|
||||||
|
**Regeneration is operator-gated.** When a brief already exists you **never overwrite** it without an **explicit go**: present a section-by-section **diff** of the proposed distillation against the current file (including any operator edits), and write only what the operator approves. The operator's own edits outrank your distillation by default.
|
||||||
|
|
||||||
|
**Graceful absence:** if `brain/profile.md` and the published corpus are both empty there is **no brief yet** — say that the second brain has nothing to distil and point to `/linkedin:setup` voice-samples and profile steps first. Never scaffold a brief out of the template's placeholders.
|
||||||
|
|
||||||
### New Creator Advantage Detection
|
### New Creator Advantage Detection
|
||||||
|
|
||||||
From the state file, extract `first_post_date`. Calculate the creator window status:
|
From the state file, extract `first_post_date`. Calculate the creator window status:
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ You are a LinkedIn performance analyst. Help the user diagnose why their content
|
||||||
## Load Context
|
## Load Context
|
||||||
|
|
||||||
Read these reference files:
|
Read these reference files:
|
||||||
- `references/troubleshooting-guide.md` - Failure patterns and solutions
|
- `${CLAUDE_PLUGIN_ROOT}/references/troubleshooting-guide.md` - Failure patterns and solutions
|
||||||
- `references/algorithm-signals-reference.md` - Algorithm mechanics
|
- `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` - Algorithm mechanics
|
||||||
- `skills/linkedin-studio/SKILL.md` - User's profile and goals
|
- `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` - User's profile and goals
|
||||||
|
|
||||||
## Step 1: Diagnose the Problem
|
## Step 1: Diagnose the Problem
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ Based on their answer, ask relevant follow-up questions:
|
||||||
|
|
||||||
## Step 3: Apply Diagnostic Framework
|
## Step 3: Apply Diagnostic Framework
|
||||||
|
|
||||||
Based on `references/troubleshooting-guide.md`, diagnose the pattern:
|
Based on `${CLAUDE_PLUGIN_ROOT}/references/troubleshooting-guide.md`, diagnose the pattern:
|
||||||
|
|
||||||
### Pattern: Good Content, Low Reach
|
### Pattern: Good Content, Low Reach
|
||||||
|
|
||||||
|
|
@ -312,6 +312,6 @@ Consider creating a new account if:
|
||||||
|
|
||||||
## Reference Files
|
## Reference Files
|
||||||
|
|
||||||
- `references/troubleshooting-guide.md` - Complete troubleshooting
|
- `${CLAUDE_PLUGIN_ROOT}/references/troubleshooting-guide.md` - Complete troubleshooting
|
||||||
- `references/algorithm-signals-reference.md` - Algorithm mechanics
|
- `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` - Algorithm mechanics
|
||||||
- `references/growth-roadmaps.md` - Stall points and fixes
|
- `${CLAUDE_PLUGIN_ROOT}/references/growth-roadmaps.md` - Stall points and fixes
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ Load state and personalization:
|
||||||
- Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for profile and preferences
|
- Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for profile and preferences
|
||||||
- Check `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/plans/` for existing weekly plan
|
- Check `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/plans/` for existing weekly plan
|
||||||
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/templates/my-post-templates.md` for proven templates — vary templates across the batch for format diversity
|
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/templates/my-post-templates.md` for proven templates — vary templates across the batch for format diversity
|
||||||
|
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` — the week's posts must add up to one position, not five unrelated takes. Use **What I hold** to keep the batch arguing from the same stance, and **How I differ** as the check on the plan as a whole: if the week could have been produced by any account in the niche, revise the plan before writing. Absent file: proceed silently
|
||||||
|
|
||||||
If a plan exists for this week, use it as the foundation. If not, create one first.
|
If a plan exists for this week, use it as the foundation. If not, create one first.
|
||||||
|
|
||||||
|
|
@ -44,7 +45,7 @@ Use AskUserQuestion:
|
||||||
3. **Use existing plan** — Follow the weekly plan already created
|
3. **Use existing plan** — Follow the weekly plan already created
|
||||||
4. **Mix it up** — Diverse topics across pillars
|
4. **Mix it up** — Diverse topics across pillars
|
||||||
|
|
||||||
If they choose a theme, help them identify 3-5 unique angles from `references/content-angles.md`. For timely angles, delegate to the `trend-spotter` agent — invoke it via `Task` with `subagent_type: linkedin-studio:trend-spotter` (foreground, from this command layer) — to surface trending topics and score their relevance against the user's pillars.
|
If they choose a theme, help them identify 3-5 unique angles from `${CLAUDE_PLUGIN_ROOT}/references/content-angles.md`. For timely angles, delegate to the `trend-spotter` agent — invoke it via `Task` with `subagent_type: linkedin-studio:trend-spotter` (foreground, from this command layer) — to surface trending topics and score their relevance against the user's pillars.
|
||||||
|
|
||||||
## Step 2: Plan the Batch (with Scheduling)
|
## Step 2: Plan the Batch (with Scheduling)
|
||||||
|
|
||||||
|
|
@ -90,11 +91,13 @@ For each post in the batch:
|
||||||
### 3a. Draft
|
### 3a. Draft
|
||||||
Follow the standard structure:
|
Follow the standard structure:
|
||||||
- Hook: 110-140 characters
|
- Hook: 110-140 characters
|
||||||
- Context: 200-300 characters
|
- Context: 250-350 characters
|
||||||
- Insight: 400-800 characters
|
- Insight: 550-850 characters
|
||||||
- Implication: 200-300 characters
|
- Implication: 250-350 characters
|
||||||
- CTA: 50-100 characters
|
- CTA: 50-100 characters
|
||||||
|
|
||||||
|
Sum: 1,210-1,790 characters — inside the standard 1,200–1,800 band checked in 3b.
|
||||||
|
|
||||||
### 3b. Quick Quality Check
|
### 3b. Quick Quality Check
|
||||||
- Character count in the band for this post's format (standard 1,200–1,800 · quick 150–500 · carousel/video caption per that format) — not just "in range"
|
- Character count in the band for this post's format (standard 1,200–1,800 · quick 150–500 · carousel/video caption per that format) — not just "in range"
|
||||||
- Hook works standalone
|
- Hook works standalone
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ First Hour:
|
||||||
- [ ] Respond to comments within 5 minutes
|
- [ ] Respond to comments within 5 minutes
|
||||||
- [ ] Add value in every response (not just "thanks!")
|
- [ ] Add value in every response (not just "thanks!")
|
||||||
- [ ] Ask follow-up questions to deepen conversation
|
- [ ] Ask follow-up questions to deepen conversation
|
||||||
- [ ] Target: 15+ engagements in first 60 minutes
|
- [ ] Keep the first 60 minutes active (early engagement unlocks broader distribution — directional, no fixed threshold)
|
||||||
- [ ] Check back at 30-min and 60-min marks
|
- [ ] Check back at 30-min and 60-min marks
|
||||||
|
|
||||||
48-Hour Check-In:
|
48-Hour Check-In:
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ You are a LinkedIn carousel content specialist. Create high-engagement carousel
|
||||||
|
|
||||||
## Step 1: Choose Template
|
## Step 1: Choose Template
|
||||||
|
|
||||||
Read `assets/templates/carousel-templates.md` for the 5 templates.
|
Read `${CLAUDE_PLUGIN_ROOT}/assets/templates/carousel-templates.md` for the 5 templates.
|
||||||
|
|
||||||
Present the options:
|
Present the options:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: linkedin:competitive
|
name: linkedin:competitive
|
||||||
description: |
|
description: |
|
||||||
Competitive analysis of other LinkedIn thought leaders in your niche. Analyzes posting
|
Competitive analysis of other LinkedIn creators in your niche. Analyzes posting
|
||||||
frequency, content types, hooks, engagement strategies, and identifies gaps and
|
frequency, content types, hooks, engagement strategies, and identifies gaps and
|
||||||
opportunities for differentiation. Triggers on: "competitive analysis", "analyze competitor",
|
opportunities for differentiation. Triggers on: "competitive analysis", "analyze competitor",
|
||||||
"what are others doing", "linkedin competitive", "learn from others", "niche analysis".
|
"what are others doing", "linkedin competitive", "learn from others", "niche analysis".
|
||||||
|
|
@ -14,7 +14,7 @@ allowed-tools:
|
||||||
|
|
||||||
# LinkedIn Competitive Analysis
|
# LinkedIn Competitive Analysis
|
||||||
|
|
||||||
You are a LinkedIn competitive intelligence analyst. Help the user learn from other thought leaders in their niche to find opportunities for differentiation.
|
You are a LinkedIn competitive intelligence analyst. Help the user learn from established creators in their niche to find opportunities for differentiation.
|
||||||
|
|
||||||
## Step 0: Load Context
|
## Step 0: Load Context
|
||||||
|
|
||||||
|
|
@ -28,10 +28,10 @@ Ask the user to provide 3-5 LinkedIn profiles to analyze:
|
||||||
|
|
||||||
Use AskUserQuestion:
|
Use AskUserQuestion:
|
||||||
1. I have specific profiles to analyze
|
1. I have specific profiles to analyze
|
||||||
2. Help me find thought leaders in my niche
|
2. Help me find leading voices in my niche
|
||||||
3. I want to analyze people who inspire me
|
3. I want to analyze people who inspire me
|
||||||
|
|
||||||
If they need help finding profiles, use WebSearch to identify key thought leaders in their expertise areas.
|
If they need help finding profiles, use WebSearch to identify the most visible voices in their expertise areas.
|
||||||
|
|
||||||
For each profile, note:
|
For each profile, note:
|
||||||
- Name and headline
|
- Name and headline
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,11 @@ Show, in this order:
|
||||||
1. **Timeline** (anchored to the real publish time) — what to do at each mark.
|
1. **Timeline** (anchored to the real publish time) — what to do at each mark.
|
||||||
2. **Targets** — grouped, in priority order, with the 30-minute whale window flagged.
|
2. **Targets** — grouped, in priority order, with the 30-minute whale window flagged.
|
||||||
3. **Draft comments** — self-comments first, then the CEA replies, each labelled.
|
3. **Draft comments** — self-comments first, then the CEA replies, each labelled.
|
||||||
4. **Velocity checkpoints** — the 5/15/30/60-minute reaction+comment targets, with the
|
4. **Velocity checkpoints** — the 5/15/30/60–90-minute shape (any movement → a few →
|
||||||
"below this = hook/timing issue" warnings, so the user can self-diagnose mid-window.
|
building → momentum) with its "if it is well below" diagnostics, so the user can
|
||||||
|
self-diagnose mid-window. These are directional checkpoints, **not numeric targets** —
|
||||||
|
no source publishes a per-minute engagement count, and the comparison that holds is
|
||||||
|
against the account's own recent first hours.
|
||||||
|
|
||||||
Auto-copy the self-comments + draft replies to clipboard silently (so they're one paste away):
|
Auto-copy the self-comments + draft replies to clipboard silently (so they're one paste away):
|
||||||
|
|
||||||
|
|
@ -117,5 +120,5 @@ delayed spike) with the post-feedback monitor — invoke it via `Task` with
|
||||||
## Reference Files
|
## Reference Files
|
||||||
|
|
||||||
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` — voice matching for the draft comments
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` — voice matching for the draft comments
|
||||||
- `references/engagement-frameworks.md` — hook types, engagement hierarchy
|
- `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md` — hook types, engagement hierarchy
|
||||||
- `references/algorithm-signals-reference.md` — first-hour weighting, signal order, timing data
|
- `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` — first-hour weighting, signal order, timing data
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ contains ONLY the cold-contract inputs. Map `--type` to archetypes:
|
||||||
| `language` | `linkedin-studio:language-reviewer` | — | frozen draft + contract |
|
| `language` | `linkedin-studio:language-reviewer` | — | frozen draft + contract |
|
||||||
| `fact` | `linkedin-studio:fact-reviewer` | — | frozen draft + contract |
|
| `fact` | `linkedin-studio:fact-reviewer` | — | frozen draft + contract |
|
||||||
| `persona-resonance` | `linkedin-studio:persona-reviewer` | `mode: resonans`, one call **per active persona** | frozen draft + persona block |
|
| `persona-resonance` | `linkedin-studio:persona-reviewer` | `mode: resonans`, one call **per active persona** | frozen draft + persona block |
|
||||||
| `persona-conversion` | `linkedin-studio:persona-reviewer` | `mode: konverter`, **primær only** (hook only) | distribution hook / first two lines |
|
| `persona-conversion` | `linkedin-studio:persona-reviewer` | `mode: konverter`, **primær only** (klikk + deling) | distribution copy: hook / first two lines + the promise it makes |
|
||||||
|
|
||||||
`all` = every row above (resonance fans out one call per active persona;
|
`all` = every row above (resonance fans out one call per active persona;
|
||||||
conversion runs the primær). **Cold-prompt template** for each call:
|
conversion runs the primær). **Cold-prompt template** for each call:
|
||||||
|
|
@ -215,7 +215,7 @@ yourself or pick winners between reviewers — surface them, the operator gates.
|
||||||
| # | F-kat | 🔴/🟡/🟢 | Påstand | Kilde / retning |
|
| # | F-kat | 🔴/🟡/🟢 | Påstand | Kilde / retning |
|
||||||
… + Pivot-risk: <claims that look freshly added, or "none">
|
… + Pivot-risk: <claims that look freshly added, or "none">
|
||||||
### persona-resonance — <per persona: JA/NEI + ≤5 flags>
|
### persona-resonance — <per persona: JA/NEI + ≤5 flags>
|
||||||
### persona-conversion — <primær JA/NEI on the hook>
|
### persona-conversion — <primær klikk JA/NEI on the hook + deling JA/NEI (forwarder role, motive, RESHARE/SAVE/SEND)>
|
||||||
|
|
||||||
## Cross-archetype signal
|
## Cross-archetype signal
|
||||||
- BLOCK / 🔴 total: <N> REWORK total: <N> primær resonance: JA/NEI primær conversion: JA/NEI
|
- BLOCK / 🔴 total: <N> REWORK total: <N> primær resonance: JA/NEI primær conversion: JA/NEI
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ You are a LinkedIn analytics data import assistant. Guide the user through impor
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
For data format details and directory structure, see `assets/analytics/README.md`.
|
For data format details and directory structure, see `${CLAUDE_PLUGIN_ROOT}/assets/analytics/README.md`.
|
||||||
|
|
||||||
> **Why CSV (as of 2026-07).** Post-level analytics via LinkedIn's API is
|
> **Why CSV (as of 2026-07).** Post-level analytics via LinkedIn's API is
|
||||||
> partner-gated (vetted Community Management app + verified org + Page) and **not
|
> partner-gated (vetted Community Management app + verified org + Page) and **not
|
||||||
|
|
@ -32,7 +32,7 @@ For data format details and directory structure, see `assets/analytics/README.md
|
||||||
> percentages, and is absent from the CSV, with export status unverified; dwell is
|
> percentages, and is absent from the CSV, with export status unverified; dwell is
|
||||||
> internal-only for organic posts. Saves and the reach share are therefore
|
> internal-only for organic posts. Saves and the reach share are therefore
|
||||||
> **optional manual columns** the user adds to the CSV (`Saves`,
|
> **optional manual columns** the user adds to the CSV (`Saves`,
|
||||||
> `Out-of-network` / `In-network`) — see `assets/analytics/README.md` for the
|
> `Out-of-network` / `In-network`) — see `${CLAUDE_PLUGIN_ROOT}/assets/analytics/README.md` for the
|
||||||
> entry rules, and the README boundaries for the full data boundary.
|
> entry rules, and the README boundaries for the full data boundary.
|
||||||
|
|
||||||
## Step 1: Check for CSV Files in Exports Directory
|
## Step 1: Check for CSV Files in Exports Directory
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ first 48 hours, detects anomalies, and advises real-time interventions.
|
||||||
|---------|---------|-----------|
|
|---------|---------|-----------|
|
||||||
| `/linkedin:strategy` | **Front-door.** Phase roadmap, trajectory, authority + signature content | Any phase |
|
| `/linkedin:strategy` | **Front-door.** Phase roadmap, trajectory, authority + signature content | Any phase |
|
||||||
| `/linkedin:profile` | profile/topic-relevance optimization checklist | Any phase |
|
| `/linkedin:profile` | profile/topic-relevance optimization checklist | Any phase |
|
||||||
| `/linkedin:competitive` | Competitive analysis of other thought leaders | Any phase |
|
| `/linkedin:competitive` | Competitive analysis of other creators in your niche | Any phase |
|
||||||
| `/linkedin:monetize` | Monetization strategy (lead magnets, consulting funnel, pricing) | ~1K followers |
|
| `/linkedin:monetize` | Monetization strategy (lead magnets, consulting funnel, pricing) | ~1K followers |
|
||||||
| `/linkedin:outreach` | Collaborations and speaking opportunities (CFPs, partner pitches) | ~1K followers |
|
| `/linkedin:outreach` | Collaborations and speaking opportunities (CFPs, partner pitches) | ~1K followers |
|
||||||
|
|
||||||
|
|
@ -229,4 +229,4 @@ If the user's intent is clear from context:
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
For full skill documentation, see:
|
For full skill documentation, see:
|
||||||
- `skills/linkedin-studio/SKILL.md` - Complete skill with personalization settings
|
- `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` - Complete skill with personalization settings
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
---
|
---
|
||||||
name: linkedin:monetize
|
name: linkedin:monetize
|
||||||
description: |
|
description: |
|
||||||
Monetization strategy for LinkedIn thought leaders. Assesses readiness with scoring,
|
Monetization strategy for LinkedIn creators. Assesses readiness with scoring,
|
||||||
creates lead magnets with templates, optimizes CTAs with A/B testing, plans funnel content,
|
creates lead magnets with templates, optimizes CTAs with A/B testing, plans funnel content,
|
||||||
and tracks consulting inquiries. Works from 1K+ followers with stage-specific action plans.
|
and tracks consulting inquiries. Stage-specific action plans from 0-1K followers onward;
|
||||||
|
the payoff compounds from ~1K.
|
||||||
Triggers on: "monetize", "make money from linkedin", "linkedin revenue", "lead generation",
|
Triggers on: "monetize", "make money from linkedin", "linkedin revenue", "lead generation",
|
||||||
"consulting pipeline", "linkedin monetize", "pricing strategy", "lead magnet".
|
"consulting pipeline", "linkedin monetize", "pricing strategy", "lead magnet".
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
|
|
@ -49,9 +50,8 @@ Score each dimension 0-25 (total /100):
|
||||||
╠══════════════════════════════════════════════════════════╣
|
╠══════════════════════════════════════════════════════════╣
|
||||||
║ ║
|
║ ║
|
||||||
║ Audience Size: /25 ║
|
║ Audience Size: /25 ║
|
||||||
║ ├─ [ ] 1K+ followers (+5) ║
|
║ ├─ [ ] Follower tier (pick ONE): ║
|
||||||
║ ├─ [ ] 5K+ followers (+10) ║
|
║ │ 1K+ (+5) · 5K+ (+10) · 10K+ (+15) ║
|
||||||
║ ├─ [ ] 10K+ followers (+15) ║
|
|
||||||
║ ├─ [ ] Followers in target niche (+5) ║
|
║ ├─ [ ] Followers in target niche (+5) ║
|
||||||
║ └─ [ ] Growing 5%+ monthly (+5) ║
|
║ └─ [ ] Growing 5%+ monthly (+5) ║
|
||||||
║ ║
|
║ ║
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ the edition left off before doing anything.
|
||||||
4. **None / insufficient** — Step 1 will **define personas interactively**.
|
4. **None / insufficient** — Step 1 will **define personas interactively**.
|
||||||
Exactly one persona is the **primær**. The resolved set feeds BOTH the Step 6
|
Exactly one persona is the **primær**. The resolved set feeds BOTH the Step 6
|
||||||
resonance sweep AND the Step 6.5 headless package; see
|
resonance sweep AND the Step 6.5 headless package; see
|
||||||
`config/personas.template.md` → "Per-artifact personas".
|
`${CLAUDE_PLUGIN_ROOT}/config/personas.template.md` → "Per-artifact personas".
|
||||||
6. **Read the series brief** — whatever the series folder defines as its brief /
|
6. **Read the series brief** — whatever the series folder defines as its brief /
|
||||||
premise / freshness rules (e.g. `<serie>/brief.md`, or the resolved brief
|
premise / freshness rules (e.g. `<serie>/brief.md`, or the resolved brief
|
||||||
recorded in `edition-state.json`). This anchors angle and scope.
|
recorded in `edition-state.json`). This anchors angle and scope.
|
||||||
|
|
@ -341,6 +341,16 @@ Establish the edition brief with **at most ~3 calibration questions**. Infer
|
||||||
everything you can from Step 0 (series brief, STATE.md, prior edition); only ask
|
everything you can from Step 0 (series brief, STATE.md, prior edition); only ask
|
||||||
what genuinely changes the work.
|
what genuinely changes the work.
|
||||||
|
|
||||||
|
**Read the positioning brief first** —
|
||||||
|
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md`.
|
||||||
|
A long-form edition is the format where a missing position shows most: 1,500 words of
|
||||||
|
competent survey that takes no stance is the failure mode. Use **What I hold** to fix
|
||||||
|
what this edition *argues* (not merely covers) and **How I differ** as the brief-level
|
||||||
|
test — if the edition as briefed could carry another author's byline unchanged, the
|
||||||
|
angle is not yet an angle. Where the brief and the operator's stated brief for THIS
|
||||||
|
edition disagree, the operator wins; note the tension in one line rather than quietly
|
||||||
|
steering to the file. Absent file: proceed silently on the other sources.
|
||||||
|
|
||||||
> **Trend intake (optional bridge — N7).** If this edition is being started from a
|
> **Trend intake (optional bridge — N7).** If this edition is being started from a
|
||||||
> `/linkedin:trends` candidate — the operator supplies a **trend id** (shown in the
|
> `/linkedin:trends` candidate — the operator supplies a **trend id** (shown in the
|
||||||
> trends brief and via `CLI list --json`, where
|
> trends brief and via `CLI list --json`, where
|
||||||
|
|
@ -421,7 +431,7 @@ Settle these dimensions (most should come from context, not questions):
|
||||||
`articles.NN.personas` (each entry: name, tier, the five fields, source) at the
|
`articles.NN.personas` (each entry: name, tier, the five fields, source) at the
|
||||||
**Step 1.5 checkpoint** (the first machine-state write; Step 2 then adds the
|
**Step 1.5 checkpoint** (the first machine-state write; Step 2 then adds the
|
||||||
research notes) — it is then stable across sessions and is the single source
|
research notes) — it is then stable across sessions and is the single source
|
||||||
every later sweep reads. See `config/personas.template.md` →
|
every later sweep reads. See `${CLAUDE_PLUGIN_ROOT}/config/personas.template.md` →
|
||||||
"Per-artifact personas".
|
"Per-artifact personas".
|
||||||
- **Key points** — the 2–4 load-bearing claims the edition must make.
|
- **Key points** — the 2–4 load-bearing claims the edition must make.
|
||||||
- **Tone** — respected-peer vs. teaching-down; calibrated to the primær.
|
- **Tone** — respected-peer vs. teaching-down; calibrated to the primær.
|
||||||
|
|
@ -633,7 +643,7 @@ Next: Step 2 — Research (scoped by the binding: fill ekstern/unresolved, verif
|
||||||
1. **Decompose** the binding's `ekstern`/`unresolved` slots + unverified numbers
|
1. **Decompose** the binding's `ekstern`/`unresolved` slots + unverified numbers
|
||||||
(step 0) into 2–5 *scoped, orthogonal* research sub-questions. Each sub-question
|
(step 0) into 2–5 *scoped, orthogonal* research sub-questions. Each sub-question
|
||||||
must be answerable independently so the calls can run in parallel without
|
must be answerable independently so the calls can run in parallel without
|
||||||
overlap. Reuse the multi-source synthesis discipline from `commands/react.md`
|
overlap. Reuse the multi-source synthesis discipline from `${CLAUDE_PLUGIN_ROOT}/commands/react.md`
|
||||||
(Comparison Path, Steps 2b–3b): per source, extract claims, stance, data points;
|
(Comparison Path, Steps 2b–3b): per source, extract claims, stance, data points;
|
||||||
then look across sources for common ground, tension, and blind spots.
|
then look across sources for common ground, tension, and blind spots.
|
||||||
|
|
||||||
|
|
@ -867,7 +877,7 @@ Next: Step 2.5 — Skeleton + section pitch (operator + persona gate BEFORE pros
|
||||||
`<serie>/NN-skjelett.md`, and **`mode: skjelett`** (the before-prose mode —
|
`<serie>/NN-skjelett.md`, and **`mode: skjelett`** (the before-prose mode —
|
||||||
five spine axes, ≤3 flags as direction, HOLDER/TVILER/MANGLER scoring).
|
five spine axes, ≤3 flags as direction, HOLDER/TVILER/MANGLER scoring).
|
||||||
This is NOT resonans mode (Step 6 — that runs on full prose) and NOT
|
This is NOT resonans mode (Step 6 — that runs on full prose) and NOT
|
||||||
konverter mode (Step 9 — that judges the hook only).
|
konverter mode (Step 9 — that judges the distribution copy: klikk + deling).
|
||||||
|
|
||||||
8. **Collect skjelett verdicts and gate.** Each call returns per-axis flags
|
8. **Collect skjelett verdicts and gate.** Each call returns per-axis flags
|
||||||
(HOLDER/TVILER/MANGLER), ≤3 direction-only flags, a section-pitch check
|
(HOLDER/TVILER/MANGLER), ≤3 direction-only flags, a section-pitch check
|
||||||
|
|
@ -1083,7 +1093,7 @@ turning-points the spine already named.
|
||||||
only afterward.)
|
only afterward.)
|
||||||
|
|
||||||
3. **Expand with the `content-repurposer` muscle.** Reuse
|
3. **Expand with the `content-repurposer` muscle.** Reuse
|
||||||
`agents/content-repurposer.md` (its article→long-form conversion discipline)
|
`${CLAUDE_PLUGIN_ROOT}/agents/content-repurposer.md` (its article→long-form conversion discipline)
|
||||||
for individual section expansions — invoke it via `Task`
|
for individual section expansions — invoke it via `Task`
|
||||||
(`subagent_type: linkedin-studio:content-repurposer`) when useful, *from this
|
(`subagent_type: linkedin-studio:content-repurposer`) when useful, *from this
|
||||||
command layer* (foreground, principle 4). The command owns assembly and
|
command layer* (foreground, principle 4). The command owns assembly and
|
||||||
|
|
@ -1212,7 +1222,7 @@ mechanical noise.
|
||||||
> which is the §A/quality reference applied by reasoning.
|
> which is the §A/quality reference applied by reasoning.
|
||||||
|
|
||||||
> **Adopter note.** The gate is a KTG-internal tool: it needs the contract-gate deps
|
> **Adopter note.** The gate is a KTG-internal tool: it needs the contract-gate deps
|
||||||
> (`scripts/contract-gate/node_modules` — run `npm install` there once) and, for
|
> (`${CLAUDE_PLUGIN_ROOT}/scripts/contract-gate/node_modules` — run `npm install` there once) and, for
|
||||||
> ratify, the maskinrommet contract. If either is absent (a plain adopter ships
|
> ratify, the maskinrommet contract. If either is absent (a plain adopter ships
|
||||||
> neither), this Step is a **no-op** and the §B/§C1 discipline falls back to the Step 4
|
> neither), this Step is a **no-op** and the §B/§C1 discipline falls back to the Step 4
|
||||||
> quality-pass reasoning. It never blocks an adopter.
|
> quality-pass reasoning. It never blocks an adopter.
|
||||||
|
|
@ -1416,19 +1426,19 @@ and `persona-reviewer` never flags em-dash density (that is this step).
|
||||||
grep-able, the architecture checks need a read.
|
grep-able, the architecture checks need a read.
|
||||||
|
|
||||||
3. **Surface the report to the operator (`SendUserFile` — the Endring-5 pattern).**
|
3. **Surface the report to the operator (`SendUserFile` — the Endring-5 pattern).**
|
||||||
The flags are surfaced to KTG as a **markdown report**, the same operator-gate
|
The flags are surfaced to the operator as a **markdown report**, the same operator-gate
|
||||||
shape the visual-assets step (7.5) uses for candidates and Steps 2.5/3a use for
|
shape the visual-assets step (7.5) uses for candidates and Steps 2.5/3a use for
|
||||||
annotation:
|
annotation:
|
||||||
1. Write the agent's report to `<serie>/NN-editorial-review.md` (NN = the same
|
1. Write the agent's report to `<serie>/NN-editorial-review.md` (NN = the same
|
||||||
zero-padded edition number; new suffix, a first-class artifact alongside
|
zero-padded edition number; new suffix, a first-class artifact alongside
|
||||||
`NN-skjelett.md`).
|
`NN-skjelett.md`).
|
||||||
2. `SendUserFile` it (else a markdown `file://` link) so KTG can read the flags
|
2. `SendUserFile` it (else a markdown `file://` link) so the operator can read the flags
|
||||||
sorted BLOCK → REWORK → NICE and **decide which fold in**.
|
sorted BLOCK → REWORK → NICE and **decide which fold in**.
|
||||||
3. **KTG gates.** The agent's severity ranking is a *recommendation*; the
|
3. **The operator gates.** The agent's severity ranking is a *recommendation*; the
|
||||||
operator holds the gate. A BLOCK is the agent's strongest "must fix before
|
operator holds the gate. A BLOCK is the agent's strongest "must fix before
|
||||||
Step 6", not an automatic pipeline halt. `[OPERATØR]`
|
Step 6", not an automatic pipeline halt. `[OPERATØR]`
|
||||||
|
|
||||||
4. **Fold in the approved flags by tightening, → v(n+1).** Fold the flags KTG
|
4. **Fold in the approved flags by tightening, → v(n+1).** Fold the flags the operator
|
||||||
approved into `<serie>/NN-utkast.md` **by tightening** (rule 6 of
|
approved into `<serie>/NN-utkast.md` **by tightening** (rule 6 of
|
||||||
`references/longform-quality-rules.md` — close the gap, hold the length flat;
|
`references/longform-quality-rules.md` — close the gap, hold the length flat;
|
||||||
never expand to paper over a craft defect). The result is the next draft
|
never expand to paper over a craft defect). The result is the next draft
|
||||||
|
|
@ -1448,7 +1458,7 @@ and `persona-reviewer` never flags em-dash density (that is this step).
|
||||||
fold-in was substantive (especially any BLOCK), re-run the agent on v(n+1) to
|
fold-in was substantive (especially any BLOCK), re-run the agent on v(n+1) to
|
||||||
confirm the flags cleared and no new craft defect was introduced by the edit.
|
confirm the flags cleared and no new craft defect was introduced by the edit.
|
||||||
This loop is cheap and is the point of the gate — every craft round saved here
|
This loop is cheap and is the point of the gate — every craft round saved here
|
||||||
is a KTG round saved at first reading.
|
is an operator round saved at first reading.
|
||||||
|
|
||||||
6. **Persist + checkpoint state.** Once the editorial pass is folded in (and any
|
6. **Persist + checkpoint state.** Once the editorial pass is folded in (and any
|
||||||
re-run confirms clean):
|
re-run confirms clean):
|
||||||
|
|
@ -1496,7 +1506,7 @@ reopening locked texts — the biggest single process error of the series (plan
|
||||||
|
|
||||||
1. **Load the active personas** chosen in Step 1, with exactly one marked
|
1. **Load the active personas** chosen in Step 1, with exactly one marked
|
||||||
**primær**. Each persona's five fields (rolle, avkobler, overbeviser,
|
**primær**. Each persona's five fields (rolle, avkobler, overbeviser,
|
||||||
ekspertise, sjargong) come from `config/personas.local.md` (or the template).
|
ekspertise, sjargong) come from `${CLAUDE_PLUGIN_ROOT}/config/personas.local.md` (or the template).
|
||||||
|
|
||||||
2. **Fan out one `persona-reviewer` call per persona, in parallel** — issue them
|
2. **Fan out one `persona-reviewer` call per persona, in parallel** — issue them
|
||||||
in a SINGLE message (multiple `Task` tool-uses, `subagent_type:
|
in a SINGLE message (multiple `Task` tool-uses, `subagent_type:
|
||||||
|
|
@ -1583,7 +1593,7 @@ in-session pass hid. `content-reviewer` is genuinely new (argument integrity,
|
||||||
which no in-session gate measures). Do NOT collapse the pairs.
|
which no in-session gate measures). Do NOT collapse the pairs.
|
||||||
|
|
||||||
**Procedure** (this is the same package the standalone `/linkedin:headless-review`
|
**Procedure** (this is the same package the standalone `/linkedin:headless-review`
|
||||||
command runs — see `commands/headless-review.md` for the full cold contract):
|
command runs — see `${CLAUDE_PLUGIN_ROOT}/commands/headless-review.md` for the full cold contract):
|
||||||
|
|
||||||
1. **Freeze the draft.** Snapshot the persona-passed `NN-utkast.md` so the
|
1. **Freeze the draft.** Snapshot the persona-passed `NN-utkast.md` so the
|
||||||
reviewers judge a stable artifact and the report names exactly what was read:
|
reviewers judge a stable artifact and the report names exactly what was read:
|
||||||
|
|
@ -1596,7 +1606,7 @@ command runs — see `commands/headless-review.md` for the full cold contract):
|
||||||
default `en` — tells `language-reviewer` and `voice-scrubber` which language's
|
default `en` — tells `language-reviewer` and `voice-scrubber` which language's
|
||||||
rules to grade against; Norwegian-specific checks fire only when `language: no`),
|
rules to grade against; Norwegian-specific checks fire only when `language: no`),
|
||||||
the writing contract *if it ships* (`<serie>/../../docs/skrivekontrakt.md` →
|
the writing contract *if it ships* (`<serie>/../../docs/skrivekontrakt.md` →
|
||||||
plugin mirror → `references/longform-quality-rules.md`; absent for an adopter,
|
plugin mirror → `${CLAUDE_PLUGIN_ROOT}/references/longform-quality-rules.md`; absent for an adopter,
|
||||||
the craft agents' in-tree checklists are self-contained), and the active
|
the craft agents' in-tree checklists are self-contained), and the active
|
||||||
personas (`articles.NN.personas`, primær identified). Nothing else.
|
personas (`articles.NN.personas`, primær identified). Nothing else.
|
||||||
|
|
||||||
|
|
@ -1607,7 +1617,7 @@ command runs — see `commands/headless-review.md` for the full cold contract):
|
||||||
- `linkedin-studio:language-reviewer` — language quality (L1–L5; grades against `edition-state.language`, Norwegian-specific rules when `language: no`)
|
- `linkedin-studio:language-reviewer` — language quality (L1–L5; grades against `edition-state.language`, Norwegian-specific rules when `language: no`)
|
||||||
- `linkedin-studio:fact-reviewer` — cold re-verification (F1–F4, 🔴/🟡/🟢, incl. pivot premises)
|
- `linkedin-studio:fact-reviewer` — cold re-verification (F1–F4, 🔴/🟡/🟢, incl. pivot premises)
|
||||||
- `linkedin-studio:persona-reviewer` `mode: resonans` — **one call per active persona**
|
- `linkedin-studio:persona-reviewer` `mode: resonans` — **one call per active persona**
|
||||||
- `linkedin-studio:persona-reviewer` `mode: konverter` — **primær only** (hook)
|
- `linkedin-studio:persona-reviewer` `mode: konverter` — **primær only** (hook + share; advisory here, blocking at Step 9)
|
||||||
|
|
||||||
Each call's prompt carries ONLY the cold-contract inputs (frozen draft path,
|
Each call's prompt carries ONLY the cold-contract inputs (frozen draft path,
|
||||||
`language`, contract path if it ships, persona for the persona modes) + the
|
`language`, contract path if it ships, persona for the persona modes) + the
|
||||||
|
|
@ -1636,10 +1646,10 @@ command runs — see `commands/headless-review.md` for the full cold contract):
|
||||||
with a cross-archetype signal line. **Mark ⚑ converged** any passage two
|
with a cross-archetype signal line. **Mark ⚑ converged** any passage two
|
||||||
independent cold reviewers flag — independent agreement with no shared session
|
independent cold reviewers flag — independent agreement with no shared session
|
||||||
is the package's strongest signal. `SendUserFile` it (else a `file://` link)
|
is the package's strongest signal. `SendUserFile` it (else a `file://` link)
|
||||||
so KTG decides which flags fold in. You do not resolve flags or pick winners;
|
so the operator decides which flags fold in. You do not resolve flags or pick winners;
|
||||||
the operator gates. `[OPERATØR]`
|
the operator gates. `[OPERATØR]`
|
||||||
|
|
||||||
6. **Fold in by tightening, → v(n+1).** Fold the flags KTG approved into
|
6. **Fold in by tightening, → v(n+1).** Fold the flags the operator approved into
|
||||||
`NN-utkast.md` **by tightening** (rule 6 — close the gap, hold the length flat).
|
`NN-utkast.md` **by tightening** (rule 6 — close the gap, hold the length flat).
|
||||||
The editor (this session) holds the pen; never paste a reviewer's direction as
|
The editor (this session) holds the pen; never paste a reviewer's direction as
|
||||||
copy. If the fold-in was substantive, re-run the affected archetype on v(n+1).
|
copy. If the fold-in was substantive, re-run the affected archetype on v(n+1).
|
||||||
|
|
@ -2051,18 +2061,22 @@ Next: Step 9 — Hook / conversion gate (post-lock).
|
||||||
The locked edition still has to earn the feed. Before it ships, the
|
The locked edition still has to earn the feed. Before it ships, the
|
||||||
**feed-facing surfaces** the reader actually meets — the **delingstekst hook**,
|
**feed-facing surfaces** the reader actually meets — the **delingstekst hook**,
|
||||||
the **samle post**, the **carousel slide text**, and the **edition title** —
|
the **samle post**, the **carousel slide text**, and the **edition title** —
|
||||||
face two final gates: a **randsone fact + language gate** (so nothing false or
|
face a **hook-forge** (the krok competes against alternatives instead of shipping
|
||||||
clumsy reaches the feed in the most-read, most-requoted copy — C-4/C-9) and the
|
the one the draft happened to end on) and three gates: a **randsone fact +
|
||||||
**hook conversion binary** (would the primær persona, scrolling, actually stop
|
language gate** (so nothing false or clumsy reaches the feed in the most-read,
|
||||||
and open this?). Both operate on **distribution copy only** — the post-lock
|
most-requoted copy — C-4/C-9), the **hook conversion binary** (would the primær
|
||||||
sweep, distinct from the pre-lock resonance sweep (Step 6), never the locked
|
persona, scrolling, actually stop and open this?), and the **shareability gate**
|
||||||
body. Until N9 only the hook's click-worth was judged; the hook's *truth and
|
(would that reader pass it on — and to whom?). All operate on **distribution copy
|
||||||
language*, the samle post, the carousel slides and the title reached the feed
|
only** — the post-lock sweep, distinct from the pre-lock resonance sweep (Step 6),
|
||||||
ungated.
|
never the locked body. Until N9 only the hook's click-worth was judged; the hook's
|
||||||
|
*truth and language*, the samle post, the carousel slides and the title reached
|
||||||
|
the feed ungated — and until N24 the hook shipped unchallenged and nobody asked
|
||||||
|
who would forward the edition.
|
||||||
|
|
||||||
> **Order assertion (enforced).** This gate (Step 9) runs AFTER lock (Step 8) — it
|
> **Order assertion (enforced).** This gate (Step 9) runs AFTER lock (Step 8) — it
|
||||||
> operates on the **distribution copy** of an already-locked edition and must never
|
> operates on the **distribution copy** of an already-locked edition and must never
|
||||||
> reopen the locked body. Both the randsone fact/language gate and the hook binary
|
> reopen the locked body. The hook-forge, the randsone fact/language gate, the hook
|
||||||
|
> binary and the shareability gate all
|
||||||
> revise only distribution surfaces (delingstekst, samle, carousel text,
|
> revise only distribution surfaces (delingstekst, samle, carousel text,
|
||||||
> distribution title) and re-render via Step 8's `build-linkedin.mjs` /
|
> distribution title) and re-render via Step 8's `build-linkedin.mjs` /
|
||||||
> `build-carousel.mjs` — never the edition text. A defect that traces into the
|
> `build-carousel.mjs` — never the edition text. A defect that traces into the
|
||||||
|
|
@ -2091,7 +2105,38 @@ lock invariant without breaking phase order.
|
||||||
- the **edition title** — the edition's own headline (its distribution / feed
|
- the **edition title** — the edition's own headline (its distribution / feed
|
||||||
title), never strength-gated before now.
|
title), never strength-gated before now.
|
||||||
|
|
||||||
2. **Randsone fact + language gate (scoped — C-4/C-9).** Run two scoped reviewers
|
2. **Hook-forge — forge the alternatives before gating one (E#7).** The hook that
|
||||||
|
exists is the one the draft happened to end on; it has never competed against
|
||||||
|
anything. Forge **five to eight hook variants** for the delingstekst krok, then
|
||||||
|
let the operator choose — the gates below judge the chosen krok, not the accident.
|
||||||
|
- **Measure the band, per variant.** Each variant's opening line must land in
|
||||||
|
**110–140 characters** (the mobile "…see more" cutoff — the hook rule the
|
||||||
|
short-form surfaces already hold). Measure it, never estimate it:
|
||||||
|
`perl -CSD -ne 'chomp; printf "%d\n", length($_)'`. Norwegian æ/ø/å are
|
||||||
|
multibyte, so `awk length` and `wc -c` count BYTES and over-report. A variant
|
||||||
|
outside the band is **disqualified, not ranked lower** — the cutoff is
|
||||||
|
physical, not a preference, and it is enforced per variant, never on the set.
|
||||||
|
- **Span the hook types.** Name each variant's type from
|
||||||
|
`${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md`. The surviving set
|
||||||
|
must cover **at least three distinct hook types** — otherwise the forge
|
||||||
|
produced rewordings of one idea and the operator has no real choice.
|
||||||
|
- **Trace every variant to the locked body.** A variant must rest on
|
||||||
|
**a claim the locked body actually makes**. One that promises what the
|
||||||
|
edition does not deliver is disqualified — it is bait, and the randsone fact
|
||||||
|
gate below would 🔴 it anyway.
|
||||||
|
- **Carry only what can be checked.** Each variant ships with its measured
|
||||||
|
length, its hook type and its body-trace — and
|
||||||
|
**no predicted-engagement score**. A number claiming to know which krok will
|
||||||
|
travel is invented; the ranking is the operator's, on variants the plugin can
|
||||||
|
honestly describe.
|
||||||
|
- **Fewer than five survivors → forge again.** Presenting three survivors as
|
||||||
|
"the set" hides that the band or the body constrained the forge.
|
||||||
|
- The operator picks one via `AskUserQuestion` — the gate that already exists
|
||||||
|
here. The chosen variant becomes the delingstekst hook and goes
|
||||||
|
**through the randsone gate, never around it**: the same fact, language and
|
||||||
|
length rules as any other distribution copy. `[GATE]`
|
||||||
|
|
||||||
|
3. **Randsone fact + language gate (scoped — C-4/C-9).** Run two scoped reviewers
|
||||||
over *only* these surfaces (never the locked body), issued from THIS command
|
over *only* these surfaces (never the locked body), issued from THIS command
|
||||||
layer in the foreground, `subagent_type` namespaced:
|
layer in the foreground, `subagent_type` namespaced:
|
||||||
- `linkedin-studio:fact-reviewer` — **F1** (verifiable claims) + **F3** (number
|
- `linkedin-studio:fact-reviewer` — **F1** (verifiable claims) + **F3** (number
|
||||||
|
|
@ -2104,7 +2149,7 @@ lock invariant without breaking phase order.
|
||||||
Pass each reviewer only the four surfaces + the sources they cite + `language`.
|
Pass each reviewer only the four surfaces + the sources they cite + `language`.
|
||||||
This is a **scoped mini-pass**, not a re-review of the article. `[GATE]`
|
This is a **scoped mini-pass**, not a re-review of the article. `[GATE]`
|
||||||
|
|
||||||
3. **Title-strength gate (C-9).** The edition's own title was never strength-gated —
|
4. **Title-strength gate (C-9).** The edition's own title was never strength-gated —
|
||||||
only the hook's two lines were. Judge the **title-strength** of the **edition
|
only the hook's two lines were. Judge the **title-strength** of the **edition
|
||||||
title** here: does it earn the open on its own, or is it a generic label? The
|
title** here: does it earn the open on its own, or is it a generic label? The
|
||||||
strongest candidate becomes the distribution title. The verdict is **binding on
|
strongest candidate becomes the distribution title. The verdict is **binding on
|
||||||
|
|
@ -2112,7 +2157,7 @@ lock invariant without breaking phase order.
|
||||||
canonical (locked) title** — changing that is a body edit → STOP, run
|
canonical (locked) title** — changing that is a body edit → STOP, run
|
||||||
`/linkedin:pivot`; never edit the locked title in place.
|
`/linkedin:pivot`; never edit the locked title in place.
|
||||||
|
|
||||||
4. **Resolve the randsone gate.**
|
5. **Resolve the randsone gate.**
|
||||||
- **Fact 🔴 / L-BLOCK on a distribution surface** → revise that surface **by
|
- **Fact 🔴 / L-BLOCK on a distribution surface** → revise that surface **by
|
||||||
tightening** (hook, samle, carousel text, or distribution title — all
|
tightening** (hook, samle, carousel text, or distribution title — all
|
||||||
distribution copy), **re-render** the affected artifact (POST.html via Step 8's
|
distribution copy), **re-render** the affected artifact (POST.html via Step 8's
|
||||||
|
|
@ -2122,32 +2167,63 @@ lock invariant without breaking phase order.
|
||||||
pivot: STOP, run `/linkedin:pivot`. The randsone gate never reopens the locked
|
pivot: STOP, run `/linkedin:pivot`. The randsone gate never reopens the locked
|
||||||
body in place — the same invariant as the conversion loop. `[GATE]`
|
body in place — the same invariant as the conversion loop. `[GATE]`
|
||||||
|
|
||||||
5. **Run the hook conversion binary** (`subagent_type:
|
6. **Run the distribution binaries** (`subagent_type:
|
||||||
linkedin-studio:persona-reviewer`, **`mode: konverter`** — the after-lock,
|
linkedin-studio:persona-reviewer`, **`mode: konverter`** — the after-lock
|
||||||
hook-only mode, NOT resonans) for the **primær** persona only. The agent returns
|
distribution mode, NOT resonans) for the **primær** persona only, on the chosen
|
||||||
a single binary verdict, **JA / NEI**, on «would YOU click?» — no axis scoring,
|
forge variant. The agent returns **two binary verdicts** — **klikk** («would YOU
|
||||||
no flags, no rewritten copy (the jury judges, the editor writes).
|
click?») and **deling** (item 7) — no axis scoring, no flags, no rewritten copy
|
||||||
- **JA** → the hook converts. Proceed to Step 10.
|
(the jury judges, the editor writes; the forge is the editor's pen).
|
||||||
|
- **JA** → the hook converts. Proceed to the shareability gate.
|
||||||
- **NEI** → revise the **delingstekst hook only** (sharpen the krok by tightening,
|
- **NEI** → revise the **delingstekst hook only** (sharpen the krok by tightening,
|
||||||
body frozen), **re-render POST.html** via `build-linkedin.mjs`, and re-run the
|
or take the next forge variant — body frozen), **re-render POST.html** via
|
||||||
conversion check. Loop until JA. `[GATE]`
|
`build-linkedin.mjs`, and re-run the conversion check. Loop until JA. `[GATE]`
|
||||||
|
|
||||||
6. **Persist.** Record the randsone-gate result in `edition-state.json` →
|
7. **Shareability gate — «hvem deler dette til en kollega, og hvorfor?» (E#8).**
|
||||||
|
A click is one reader; a forward is peer acknowledgement, and it is the signal
|
||||||
|
the edition was written to earn. The `konverter` call's second binary is the
|
||||||
|
**deling verdict**: judged by the primær persona on the same distribution copy —
|
||||||
|
**who forwards this to a colleague, and why** — plus which action the edition
|
||||||
|
actually earns from that reader: **RESHARE / SAVE / SEND**.
|
||||||
|
- **A plausible answer names a role and a motive.** It must name a role the
|
||||||
|
reader could actually point at, and what forwarding does *for the sender* —
|
||||||
|
signals their judgment, saves a colleague a decision, settles a running
|
||||||
|
argument. «Useful for anyone in tech» names nobody: that is a **NEI**.
|
||||||
|
- **NEI → revise the distribution copy** (the promise may simply be framed for a
|
||||||
|
reader nobody forwards to), re-render, re-check. Body frozen, as everywhere in
|
||||||
|
this step.
|
||||||
|
- **Two failed re-framings → stop; do not lower the bar.** If the honest reason
|
||||||
|
is that the edition offers nothing a reader would pass on, that is a
|
||||||
|
body-level finding, not a copy problem: put it to the operator as a choice —
|
||||||
|
run `/linkedin:pivot`, or ship and record it as an `articles.NN.foldIns` row
|
||||||
|
for the Step 11 retro. There is **never a silent pass**: this gate is resolved
|
||||||
|
by a fix or by a recorded operator decision, never by re-reading the same copy
|
||||||
|
more generously. `[GATE]`
|
||||||
|
|
||||||
|
8. **Persist.** Record the forge in `edition-state.json` →
|
||||||
|
`articles.NN.hookForge` (each variant with its measured length, hook type,
|
||||||
|
body-trace and disqualification reason if any, plus which one the operator
|
||||||
|
chose), the randsone-gate result in
|
||||||
`articles.NN.randsoneGate` (per-surface fact/language verdicts + title-strength +
|
`articles.NN.randsoneGate` (per-surface fact/language verdicts + title-strength +
|
||||||
what was revised) and the conversion verdict in
|
what was revised), the click verdict in
|
||||||
`articles.NN.personaSweep.conversion`; set `currentPhase: "hook-conversion-gate"`.
|
`articles.NN.personaSweep.conversion` and the share verdict in
|
||||||
|
`articles.NN.personaSweep.deling` (verdict + the named forwarder role + the
|
||||||
|
RESHARE/SAVE/SEND action, or the recorded operator decision);
|
||||||
|
set `currentPhase: "hook-conversion-gate"`.
|
||||||
**↳ Phase transition** — run the protocol (Step 0) for `hook-conversion-gate`,
|
**↳ Phase transition** — run the protocol (Step 0) for `hook-conversion-gate`,
|
||||||
`--next "Step 10 — scheduling"`.
|
`--next "Step 10 — scheduling"`.
|
||||||
|
|
||||||
```
|
```
|
||||||
Randsone + conversion gate (post-lock, distribution copy only).
|
Forge + randsone + conversion + shareability gate (post-lock, distribution copy only).
|
||||||
|
- Hook-forge: <N> forged / <N> disqualified (band <N>, body-trace <N>) · types spanned: <N> → chose #<k> (<measured> chars, <type>)
|
||||||
- Surfaces gated: delingstekst hook · samle post · carousel slide text · edition title
|
- Surfaces gated: delingstekst hook · samle post · carousel slide text · edition title
|
||||||
- Fact (F1/F3): <N> 🔴 → resolved (or pivot if it traces to the body)
|
- Fact (F1/F3): <N> 🔴 → resolved (or pivot if it traces to the body)
|
||||||
- Language (L1/L2): <N> BLOCK → tightened
|
- Language (L1/L2): <N> BLOCK → tightened
|
||||||
- Title-strength: <verdict> (distribution title sharpened; canonical-title weakness → pivot)
|
- Title-strength: <verdict> (distribution title sharpened; canonical-title weakness → pivot)
|
||||||
- Conversion (primær, konverter): JA (else: NEI — revise hook, re-render, re-check)
|
- Conversion (primær, konverter): JA (else: NEI — revise hook, re-render, re-check)
|
||||||
|
- Deling (primær, konverter): JA — <role> sends it to <role> because <motive> · action: RESHARE|SAVE|SEND
|
||||||
|
(else: NEI — reframe the copy; twice failed → operator decision, never a silent pass)
|
||||||
- Body: untouched (locked in Step 8)
|
- Body: untouched (locked in Step 8)
|
||||||
Gate: [PASS — randsone clean + JA] (else loop on distribution copy, or pivot)
|
Gate: [PASS — randsone clean + klikk JA + deling JA] (else loop on distribution copy, or pivot)
|
||||||
Next: Step 10 — Scheduling.
|
Next: Step 10 — Scheduling.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -2269,7 +2345,7 @@ time. Step 11 empties that queue into permanent, enforced homes.
|
||||||
|
|
||||||
- **`mechanical-block` / `mechanical-warn`** — provable by string, regex, or
|
- **`mechanical-block` / `mechanical-warn`** — provable by string, regex, or
|
||||||
count with ~zero false positives. **JA →** promote **atomically**: add the
|
count with ~zero false positives. **JA →** promote **atomically**: add the
|
||||||
rule to `scripts/contract-gate/src/rules.ts`, the matching row to the
|
rule to `${CLAUDE_PLUGIN_ROOT}/scripts/contract-gate/src/rules.ts`, the matching row to the
|
||||||
writing contract, and the manifest row that binds them; set the fold-in's
|
writing contract, and the manifest row that binds them; set the fold-in's
|
||||||
`decision: "promoted"` and `ruleId`. Then prove it:
|
`decision: "promoted"` and `ruleId`. Then prove it:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ TOTAL: /25
|
||||||
Search strategies:
|
Search strategies:
|
||||||
|
|
||||||
By topic:
|
By topic:
|
||||||
- "[your niche] linkedin thought leader"
|
- "top [your niche] voices linkedin"
|
||||||
- "[topic] expert linkedin"
|
- "[topic] expert linkedin"
|
||||||
- "[industry] creator linkedin"
|
- "[industry] creator linkedin"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ Load persistent state and personalization:
|
||||||
- Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for profile and preferences
|
- Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for profile and preferences
|
||||||
- Check `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` for voice matching
|
- Check `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` for voice matching
|
||||||
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/templates/my-post-templates.md` for proven post templates — use these in Step 2 (Draft)
|
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/templates/my-post-templates.md` for proven post templates — use these in Step 2 (Draft)
|
||||||
- Read `assets/frameworks/framework-template.md` if the topic involves a framework or methodology
|
- Read `${CLAUDE_PLUGIN_ROOT}/assets/frameworks/framework-template.md` if the topic involves a framework or methodology
|
||||||
|
|
||||||
Display status:
|
Display status:
|
||||||
```
|
```
|
||||||
|
|
@ -52,14 +52,15 @@ To situate the post in the broader plan — does it fill a content-mix gap or re
|
||||||
|
|
||||||
Once topic is chosen, create the draft:
|
Once topic is chosen, create the draft:
|
||||||
|
|
||||||
1. **Select angle** — Auto-select the strongest angle from `references/content-angles.md` based on topic and user's expertise. Present ONE recommended angle with reasoning. Do NOT use AskUserQuestion — just proceed. If user disagrees, offer alternatives.
|
1. **Select angle** — Auto-select the strongest angle from `${CLAUDE_PLUGIN_ROOT}/references/content-angles.md` based on topic and user's expertise. Present ONE recommended angle with reasoning. Do NOT use AskUserQuestion — just proceed. If user disagrees, offer alternatives.
|
||||||
2. **Infer format** — Default to text post. Only mention carousel/video as a note if particularly well-suited.
|
2. **Infer format** — Default to text post. Only mention carousel/video as a note if particularly well-suited.
|
||||||
3. **Write draft** — Following the structure:
|
3. **Write draft** — Following the structure:
|
||||||
- Hook: 110-140 characters
|
- Hook: 110-140 characters
|
||||||
- Context: 200-300 characters
|
- Context: 250-350 characters
|
||||||
- Insight: 400-800 characters
|
- Insight: 550-850 characters
|
||||||
- Implication: 200-300 characters
|
- Implication: 250-350 characters
|
||||||
- CTA: 50-100 characters
|
- CTA: 50-100 characters
|
||||||
|
- Sum: 1,210-1,790 characters — inside the 1,200-1,800 band gated in Step 3.
|
||||||
|
|
||||||
Reference `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md` for hooks and CTAs.
|
Reference `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md` for hooks and CTAs.
|
||||||
|
|
||||||
|
|
@ -166,7 +167,7 @@ First Hour Engagement Plan:
|
||||||
- [ ] Respond to comments within 5 minutes
|
- [ ] Respond to comments within 5 minutes
|
||||||
- [ ] Add value in every response (not just "thanks!")
|
- [ ] Add value in every response (not just "thanks!")
|
||||||
- [ ] Ask follow-up questions to deepen conversation
|
- [ ] Ask follow-up questions to deepen conversation
|
||||||
- [ ] Target: 15+ engagements in first 60 minutes
|
- [ ] Keep the first 60 minutes active (early engagement unlocks broader distribution — directional, no fixed threshold)
|
||||||
- [ ] Check back at 30-min and 60-min marks
|
- [ ] Check back at 30-min and 60-min marks
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ You are a LinkedIn content creator. Guide the user through creating a high-quali
|
||||||
|
|
||||||
First, load persistent state and personalization:
|
First, load persistent state and personalization:
|
||||||
- Read `~/.claude/linkedin-studio.local.md` for posting state (streak, weekly progress, recent topics)
|
- Read `~/.claude/linkedin-studio.local.md` for posting state (streak, weekly progress, recent topics)
|
||||||
- Read `skills/linkedin-studio/SKILL.md` for user profile, voice settings, and preferences
|
- Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for user profile, voice settings, and preferences
|
||||||
|
|
||||||
**Read `## Do-Next Directives` in the state file and apply them to this draft.** This is what the
|
**Read `## Do-Next Directives` in the state file and apply them to this draft.** This is what the
|
||||||
last measurement pass (`/linkedin:report`, `/linkedin:analyze`, an A/B Adopt verdict, the 48h
|
last measurement pass (`/linkedin:report`, `/linkedin:analyze`, an A/B Adopt verdict, the 48h
|
||||||
|
|
@ -45,8 +45,9 @@ Check weekly progress:
|
||||||
|
|
||||||
Check for existing assets:
|
Check for existing assets:
|
||||||
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` - Match the user's natural voice
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` - Match the user's natural voice
|
||||||
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` - What the user stands for and how they differ. Write the angle AGAINST this: the "How I differ" section is the test for whether this post could have been written by anyone else on the topic. Absent file: proceed silently (nothing to anchor on yet).
|
||||||
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/examples/high-engagement-posts.md` - Study past successful posts and replicable patterns
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/examples/high-engagement-posts.md` - Study past successful posts and replicable patterns
|
||||||
- `assets/frameworks/framework-template.md` - Reference user's documented frameworks for framework posts
|
- `${CLAUDE_PLUGIN_ROOT}/assets/frameworks/framework-template.md` - Reference user's documented frameworks for framework posts
|
||||||
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/templates/my-post-templates.md` - User's proven post templates with success rates. **Prefer these over generic structures.**
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/templates/my-post-templates.md` - User's proven post templates with success rates. **Prefer these over generic structures.**
|
||||||
|
|
||||||
## Step 1: Understand the Input
|
## Step 1: Understand the Input
|
||||||
|
|
@ -71,7 +72,7 @@ If they provide a URL, use WebFetch to extract the content first.
|
||||||
|
|
||||||
## Step 2: Select Content Angle
|
## Step 2: Select Content Angle
|
||||||
|
|
||||||
Read `references/content-angles.md` for the 8 universal angles.
|
Read `${CLAUDE_PLUGIN_ROOT}/references/content-angles.md` for the 8 universal angles.
|
||||||
|
|
||||||
**Industry-specific angles:** If `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/user-profile.md` exists and has an `industry` field, check the "Industry Angle Variants" section in `content-angles.md` for the matching industry table. Use the industry-specific starter questions and example hooks to generate more targeted angle suggestions.
|
**Industry-specific angles:** If `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/user-profile.md` exists and has an `industry` field, check the "Industry Angle Variants" section in `content-angles.md` for the matching industry table. Use the industry-specific starter questions and example hooks to generate more targeted angle suggestions.
|
||||||
|
|
||||||
|
|
@ -92,7 +93,7 @@ Infer format automatically based on content type — do NOT ask the user to choo
|
||||||
| Content Type | Auto-Selected Format |
|
| Content Type | Auto-Selected Format |
|
||||||
|--------------|---------------------|
|
|--------------|---------------------|
|
||||||
| Data/research | Medium text post (1,200-1,800 chars) |
|
| Data/research | Medium text post (1,200-1,800 chars) |
|
||||||
| Personal stories | Medium text post (1,000-1,400 chars) |
|
| Personal stories | Medium text post (1,200-1,800 chars) |
|
||||||
| Quick insights | Redirect to `/linkedin:quick` |
|
| Quick insights | Redirect to `/linkedin:quick` |
|
||||||
| Frameworks/processes | Medium text post (note: "This could also work as a carousel — run `/linkedin:carousel` if you'd prefer that format.") |
|
| Frameworks/processes | Medium text post (note: "This could also work as a carousel — run `/linkedin:carousel` if you'd prefer that format.") |
|
||||||
| Opinions/takes | Text-only medium post |
|
| Opinions/takes | Text-only medium post |
|
||||||
|
|
@ -101,19 +102,23 @@ Proceed with standard text post format by default. Only mention carousel or othe
|
||||||
|
|
||||||
## Step 4: Structure and Write
|
## Step 4: Structure and Write
|
||||||
|
|
||||||
Read `references/engagement-frameworks.md` for hook types, story structures, and CTAs.
|
Read `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md` for hook types, story structures, and CTAs.
|
||||||
|
|
||||||
Use the Standard Post Structure:
|
Use the Standard Post Structure:
|
||||||
|
|
||||||
1. **Hook (110-140 chars):** Grab attention, create curiosity gap
|
1. **Hook (110-140 chars):** Grab attention, create curiosity gap
|
||||||
2. **Context (200-300 chars):** Set up why this matters
|
2. **Context (250-350 chars):** Set up why this matters
|
||||||
3. **Insight/Argument (400-800 chars):** Main point with evidence
|
3. **Insight/Argument (550-850 chars):** Main point with evidence
|
||||||
4. **Implication (200-300 chars):** What this means for readers
|
4. **Implication (250-350 chars):** What this means for readers
|
||||||
5. **CTA (50-100 chars):** Engagement prompt
|
5. **CTA (50-100 chars):** Engagement prompt
|
||||||
|
|
||||||
|
The components sum to 1,210-1,790 characters — inside the 1,200-1,800 standard band
|
||||||
|
the Step 5 checklist gates against, so a draft that respects each component band
|
||||||
|
passes the length gate by construction.
|
||||||
|
|
||||||
### Hook Rules
|
### Hook Rules
|
||||||
|
|
||||||
Reference `assets/quick-post-resources.md` for hooks bank.
|
Reference `${CLAUDE_PLUGIN_ROOT}/assets/quick-post-resources.md` for hooks bank.
|
||||||
|
|
||||||
- Frontload value - most interesting part first
|
- Frontload value - most interesting part first
|
||||||
- Be specific with numbers and details
|
- Be specific with numbers and details
|
||||||
|
|
@ -130,7 +135,7 @@ Match the user's voice profile from SKILL.md:
|
||||||
|
|
||||||
## Step 5: Quality Check
|
## Step 5: Quality Check
|
||||||
|
|
||||||
Before presenting, verify against `assets/checklists/quality-scorecard.md`:
|
Before presenting, verify against `${CLAUDE_PLUGIN_ROOT}/assets/checklists/quality-scorecard.md`:
|
||||||
|
|
||||||
- [ ] Hook works in first 110-140 characters
|
- [ ] Hook works in first 110-140 characters
|
||||||
- [ ] Character count: 1,200-1,800 (optimal range)
|
- [ ] Character count: 1,200-1,800 (optimal range)
|
||||||
|
|
@ -190,7 +195,7 @@ Before they post, remind them:
|
||||||
- Respond to every comment immediately
|
- Respond to every comment immediately
|
||||||
- Add value in responses (not just "thanks")
|
- Add value in responses (not just "thanks")
|
||||||
- Ask follow-up questions to deepen conversation
|
- Ask follow-up questions to deepen conversation
|
||||||
- Target: 15+ engagements in first hour
|
- Keep the first hour active — early engagement unlocks broader distribution (directional, no fixed threshold)
|
||||||
|
|
||||||
**State Update:**
|
**State Update:**
|
||||||
After the post is finalized, update state deterministically:
|
After the post is finalized, update state deterministically:
|
||||||
|
|
@ -210,9 +215,9 @@ Replace placeholders with actual post data. This replaces manual YAML editing.
|
||||||
|
|
||||||
## Reference Files
|
## Reference Files
|
||||||
|
|
||||||
- `references/content-angles.md` - 8 universal angles
|
- `${CLAUDE_PLUGIN_ROOT}/references/content-angles.md` - 8 universal angles
|
||||||
- `references/engagement-frameworks.md` - Hooks, structure, CTAs
|
- `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md` - Hooks, structure, CTAs
|
||||||
- `references/linkedin-formats.md` - Format specifications
|
- `${CLAUDE_PLUGIN_ROOT}/references/linkedin-formats.md` - Format specifications
|
||||||
- `references/algorithm-signals-reference.md` - Algorithm mechanics
|
- `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` - Algorithm mechanics
|
||||||
- `assets/quick-post-resources.md` - Hooks and CTAs bank
|
- `${CLAUDE_PLUGIN_ROOT}/assets/quick-post-resources.md` - Hooks and CTAs bank
|
||||||
- `assets/checklists/quality-scorecard.md` - Pre-publish check
|
- `${CLAUDE_PLUGIN_ROOT}/assets/checklists/quality-scorecard.md` - Pre-publish check
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ You are a LinkedIn profile optimization specialist. Help the user optimize their
|
||||||
|
|
||||||
## Critical Context: Profile/Topic Relevance
|
## Critical Context: Profile/Topic Relevance
|
||||||
|
|
||||||
Read `references/algorithm-signals-reference.md` for algorithm mechanics.
|
Read `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` for algorithm mechanics.
|
||||||
|
|
||||||
**The Fundamental Shift:**
|
**The Fundamental Shift:**
|
||||||
- **In the older feed model:** Post something → a slice of your network sees it → the algorithm tracks engagement to decide wider reach
|
- **In the older feed model:** Post something → a slice of your network sees it → the algorithm tracks engagement to decide wider reach
|
||||||
|
|
@ -239,6 +239,6 @@ If the answer is no, fix the profile FIRST before posting.
|
||||||
|
|
||||||
## Reference Files
|
## Reference Files
|
||||||
|
|
||||||
- `references/algorithm-signals-reference.md` - relevance-model mechanics and signals
|
- `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` - relevance-model mechanics and signals
|
||||||
- `references/troubleshooting-guide.md` - Recovery if reach is already down
|
- `${CLAUDE_PLUGIN_ROOT}/references/troubleshooting-guide.md` - Recovery if reach is already down
|
||||||
- `skills/linkedin-studio/SKILL.md` - User's expertise areas and topics
|
- `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` - User's expertise areas and topics
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,18 @@ Read `~/.claude/linkedin-studio.local.md` for:
|
||||||
Apply the ones that fit and name the applied directive in one line with the draft. Never edit
|
Apply the ones that fit and name the applied directive in one line with the draft. Never edit
|
||||||
or clear the section (each source manages its own rows; rows older than 60 days expire).
|
or clear the section (each source manages its own rows; rows older than 60 days expire).
|
||||||
|
|
||||||
Read `skills/linkedin-studio/SKILL.md` for:
|
Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for:
|
||||||
- User's voice profile and tone preferences
|
- User's voice profile and tone preferences
|
||||||
- Core expertise areas (for topical alignment)
|
- Core expertise areas (for topical alignment)
|
||||||
- Phrases they commonly use
|
- Phrases they commonly use
|
||||||
|
|
||||||
Read `assets/quick-post-resources.md` for:
|
Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` for:
|
||||||
|
- **What I hold** — the positions to argue from, so a 5-minute post still says something the
|
||||||
|
user actually believes rather than something generically true
|
||||||
|
- **How I differ** — the one-line test before you present the draft: could any other account
|
||||||
|
in this niche have posted this? Absent file: proceed silently.
|
||||||
|
|
||||||
|
Read `${CLAUDE_PLUGIN_ROOT}/assets/quick-post-resources.md` for:
|
||||||
- Hooks bank
|
- Hooks bank
|
||||||
- CTAs bank
|
- CTAs bank
|
||||||
- Quality checklist
|
- Quality checklist
|
||||||
|
|
@ -61,7 +67,7 @@ Only ask if truly ambiguous (no discernible intent). Otherwise, state: "This rea
|
||||||
|
|
||||||
These 8 post types ARE the template library. Each maps to a hook pattern (Step 3),
|
These 8 post types ARE the template library. Each maps to a hook pattern (Step 3),
|
||||||
an auto-selected CTA (Step 4), and a full fill-in-the-blank structure with per-type
|
an auto-selected CTA (Step 4), and a full fill-in-the-blank structure with per-type
|
||||||
character targets in `assets/templates/post-type-templates.md`. If the user explicitly
|
character targets in `${CLAUDE_PLUGIN_ROOT}/assets/templates/post-type-templates.md`. If the user explicitly
|
||||||
wants to browse template structures rather than draft now, present that asset's
|
wants to browse template structures rather than draft now, present that asset's
|
||||||
per-type structures and let them pick before applying the 3-line formula.
|
per-type structures and let them pick before applying the 3-line formula.
|
||||||
|
|
||||||
|
|
@ -85,7 +91,7 @@ Use this structure for all quick posts:
|
||||||
|
|
||||||
## Step 3: Select Hook Pattern
|
## Step 3: Select Hook Pattern
|
||||||
|
|
||||||
Based on post type, use appropriate hook from `assets/quick-post-resources.md`:
|
Based on post type, use appropriate hook from `${CLAUDE_PLUGIN_ROOT}/assets/quick-post-resources.md`:
|
||||||
|
|
||||||
### Reaction Post
|
### Reaction Post
|
||||||
- "[Industry event/news - state what happened]"
|
- "[Industry event/news - state what happened]"
|
||||||
|
|
@ -133,7 +139,7 @@ Auto-select the best CTA based on post type — do NOT ask:
|
||||||
| CURATION | "Worth a read — what's your take?" |
|
| CURATION | "Worth a read — what's your take?" |
|
||||||
| ONE-LINER | "Agree or disagree?" |
|
| ONE-LINER | "Agree or disagree?" |
|
||||||
|
|
||||||
Reference `assets/quick-post-resources.md` for additional CTA options if the default doesn't fit.
|
Reference `${CLAUDE_PLUGIN_ROOT}/assets/quick-post-resources.md` for additional CTA options if the default doesn't fit.
|
||||||
|
|
||||||
## Step 5: Write and Check
|
## Step 5: Write and Check
|
||||||
|
|
||||||
|
|
@ -189,7 +195,7 @@ Replace placeholders with actual post data. This replaces manual YAML editing.
|
||||||
|
|
||||||
## Common Mistakes to Avoid
|
## Common Mistakes to Avoid
|
||||||
|
|
||||||
From `assets/quick-post-resources.md`:
|
From `${CLAUDE_PLUGIN_ROOT}/assets/quick-post-resources.md`:
|
||||||
|
|
||||||
1. **Too many ideas** - Pick ONE. Save others for separate posts.
|
1. **Too many ideas** - Pick ONE. Save others for separate posts.
|
||||||
2. **Burying the hook** - Lead with the most interesting element.
|
2. **Burying the hook** - Lead with the most interesting element.
|
||||||
|
|
@ -226,6 +232,6 @@ If during creation you realize:
|
||||||
|
|
||||||
## Reference Files
|
## Reference Files
|
||||||
|
|
||||||
- `assets/quick-post-resources.md` - Hooks and CTAs bank
|
- `${CLAUDE_PLUGIN_ROOT}/assets/quick-post-resources.md` - Hooks and CTAs bank
|
||||||
- `assets/templates/post-type-templates.md` - Template examples
|
- `${CLAUDE_PLUGIN_ROOT}/assets/templates/post-type-templates.md` - Template examples
|
||||||
- `references/engagement-frameworks.md` - Hook psychology
|
- `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md` - Hook psychology
|
||||||
|
|
|
||||||
|
|
@ -272,6 +272,6 @@ Same as Step 8 — run `state-updater.mjs` with actual post data.
|
||||||
## Reference Files
|
## Reference Files
|
||||||
|
|
||||||
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` — Voice matching
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` — Voice matching
|
||||||
- `references/content-angles.md` — 8 universal angles
|
- `${CLAUDE_PLUGIN_ROOT}/references/content-angles.md` — 8 universal angles
|
||||||
- `references/engagement-frameworks.md` — Hooks, structure, CTAs
|
- `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md` — Hooks, structure, CTAs
|
||||||
- `assets/checklists/quality-scorecard.md` — Pre-publish check
|
- `${CLAUDE_PLUGIN_ROOT}/assets/checklists/quality-scorecard.md` — Pre-publish check
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ You are a LinkedIn analytics performance reporter. Generate actionable weekly pe
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
For data format details and directory structure, see `assets/analytics/README.md`.
|
For data format details and directory structure, see `${CLAUDE_PLUGIN_ROOT}/assets/analytics/README.md`.
|
||||||
|
|
||||||
## Step 1: Check for Imported Data
|
## Step 1: Check for Imported Data
|
||||||
|
|
||||||
|
|
@ -247,7 +247,7 @@ Automatically flag these conditions based on the report data and trend analysis:
|
||||||
- 🔴 Format stagnation: Same format used >80% of posts (algorithm favors format variety — see algorithm-signals-reference)
|
- 🔴 Format stagnation: Same format used >80% of posts (algorithm favors format variety — see algorithm-signals-reference)
|
||||||
- 🟡 Posting time drift: Publishing outside optimal window (Tue-Thu, 7-9 AM CET for Nordic audience — see posting time windows reference)
|
- 🟡 Posting time drift: Publishing outside optimal window (Tue-Thu, 7-9 AM CET for Nordic audience — see posting time windows reference)
|
||||||
- 🟡 Hook length violation: Posts with hooks >140 chars underperforming (>140 chars truncated on mobile "see more")
|
- 🟡 Hook length violation: Posts with hooks >140 chars underperforming (>140 chars truncated on mobile "see more")
|
||||||
- 🟢 Engagement velocity improving: First-hour engagement trending up (15+ engagements in first hour unlocks 2nd/3rd degree distribution)
|
- 🟢 Engagement velocity improving: First-hour engagement trending up against the account's own recent first hours (early engagement unlocks broader distribution — directional, no fixed threshold)
|
||||||
|
|
||||||
**Surface the alerts the report already computed.** The weekly-report JSON's `alerts[]`
|
**Surface the alerts the report already computed.** The weekly-report JSON's `alerts[]`
|
||||||
is generated by the CLI itself — intra-week anomaly detection across the week's posts
|
is generated by the CLI itself — intra-week anomaly detection across the week's posts
|
||||||
|
|
@ -275,7 +275,7 @@ Your engagement rate has dropped from 4.2% to 2.8% over the last 3 weeks.
|
||||||
🟢 **Positive: New impression record**
|
🟢 **Positive: New impression record**
|
||||||
Your post on [topic] achieved 12,500 impressions — a personal best!
|
Your post on [topic] achieved 12,500 impressions — a personal best!
|
||||||
→ **Action:** Analyze what made this post succeed. Consider a follow-up post.
|
→ **Action:** Analyze what made this post succeed. Consider a follow-up post.
|
||||||
→ **Reference:** First-hour velocity of 15+ engagements unlocks broader distribution.
|
→ **Reference:** Strong first-hour velocity unlocks broader distribution — directional, with no published threshold count.
|
||||||
|
|
||||||
🟡 **Warning: Format stagnation detected**
|
🟡 **Warning: Format stagnation detected**
|
||||||
80%+ of your recent posts are text-only. Documents/carousels are the top organic format and tend to reach further (no reliable multiplier).
|
80%+ of your recent posts are text-only. Documents/carousels are the top organic format and tend to reach further (no reliable multiplier).
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ Use AskUserQuestion:
|
||||||
|
|
||||||
**What would you like to set up?**
|
**What would you like to set up?**
|
||||||
|
|
||||||
Options (always show all 7):
|
Options (always show all 8):
|
||||||
|
|
||||||
1. **Voice samples** -- Paste 3-5 of your best posts so I can analyze your writing voice
|
1. **Voice samples** -- Paste 3-5 of your best posts so I can analyze your writing voice
|
||||||
2. **Case study** -- Walk through a guided interview to document a real case study
|
2. **Case study** -- Walk through a guided interview to document a real case study
|
||||||
|
|
@ -75,7 +75,8 @@ Options (always show all 7):
|
||||||
4. **Post analysis** -- Add your high-engagement posts with metrics for pattern analysis
|
4. **Post analysis** -- Add your high-engagement posts with metrics for pattern analysis
|
||||||
5. **Demographics** -- Guide you through LinkedIn Analytics to capture audience demographics
|
5. **Demographics** -- Guide you through LinkedIn Analytics to capture audience demographics
|
||||||
6. **User profile** -- Set up your personalization profile (name, expertise, goals, voice)
|
6. **User profile** -- Set up your personalization profile (name, expertise, goals, voice)
|
||||||
7. **Show score details** -- See detailed breakdown of what's missing in each category
|
7. **Positioning brief** -- Distil what you stand for and how you differ, so every draft is written against it
|
||||||
|
8. **Show score details** -- See detailed breakdown of what's missing in each category
|
||||||
|
|
||||||
Based on their answer, run the corresponding sub-workflow below.
|
Based on their answer, run the corresponding sub-workflow below.
|
||||||
|
|
||||||
|
|
@ -128,7 +129,7 @@ Conduct a 6-question interview:
|
||||||
5. **What were the results?** -- Quantitative and qualitative outcomes
|
5. **What were the results?** -- Quantitative and qualitative outcomes
|
||||||
6. **What's the key takeaway?** -- The non-obvious lesson others can apply
|
6. **What's the key takeaway?** -- The non-obvious lesson others can apply
|
||||||
|
|
||||||
After the interview, read `assets/case-studies/case-study-template.md` for structure reference, then create a new file:
|
After the interview, read `${CLAUDE_PLUGIN_ROOT}/assets/case-studies/case-study-template.md` for structure reference, then create a new file:
|
||||||
|
|
||||||
**Filename:** `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/case-studies/[slug].md` (derive slug from the challenge topic, e.g., `ai-procurement-transformation.md`)
|
**Filename:** `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/case-studies/[slug].md` (derive slug from the challenge topic, e.g., `ai-procurement-transformation.md`)
|
||||||
|
|
||||||
|
|
@ -182,7 +183,7 @@ Conduct a 5-question interview:
|
||||||
4. **How does someone apply it?** -- Step-by-step or decision process
|
4. **How does someone apply it?** -- Step-by-step or decision process
|
||||||
5. **What makes this different from standard approaches?** -- Your unique angle
|
5. **What makes this different from standard approaches?** -- Your unique angle
|
||||||
|
|
||||||
After the interview, read `assets/frameworks/framework-template.md` for structure reference, then create:
|
After the interview, read `${CLAUDE_PLUGIN_ROOT}/assets/frameworks/framework-template.md` for structure reference, then create:
|
||||||
|
|
||||||
**Filename:** `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/frameworks/[slug].md` (e.g., `ai-maturity-model.md`)
|
**Filename:** `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/frameworks/[slug].md` (e.g., `ai-maturity-model.md`)
|
||||||
|
|
||||||
|
|
@ -341,11 +342,34 @@ Guide through each section of the profile:
|
||||||
- Record exactly what they name. If they have none, that's fine — WebSearch + WebFetch are the always-available floor.
|
- 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.
|
- 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
|
8. Read `${CLAUDE_PLUGIN_ROOT}/config/user-profile.template.md` for structure
|
||||||
9. Write the completed profile to `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/user-profile.md`
|
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.
|
**Important:** This file is gitignored (`.local.md` pattern), so personal data stays private.
|
||||||
|
|
||||||
|
## Step 3g: Positioning Brief
|
||||||
|
|
||||||
|
**Goal:** Distil `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` — the stance-and-difference layer every drafting surface reads before it writes.
|
||||||
|
|
||||||
|
This step **delegates**. Do not read the second brain yourself and do not restate its consumption rules here: invoke the agent that already owns them.
|
||||||
|
|
||||||
|
1. **Check what exists first.** Read the current `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` if present, and note whether it carries operator edits (`## Provenance` → "Operator edits since generation").
|
||||||
|
|
||||||
|
2. **Invoke the distiller** via `Task` with `subagent_type: linkedin-studio:strategy-advisor`, asking it to distil the positioning brief. It reads `brain/profile.md`, `brain/operations.md`, `profile/user-profile.md` and the published corpus, and shapes the result on `${CLAUDE_PLUGIN_ROOT}/config/positioning-brief.template.md`.
|
||||||
|
|
||||||
|
3. **First run (no brief exists):** show the four distilled sections, ask for corrections, then write the file.
|
||||||
|
|
||||||
|
4. **Regeneration (a brief already exists):** this is **operator-gated**.
|
||||||
|
- Present a **diff**, section by section, of the proposed distillation against the current file.
|
||||||
|
- Ask for an **explicit go** per section — "keep mine / take the new one / merge".
|
||||||
|
- **Never overwrite** the existing brief wholesale, and never discard an operator edit because the distillation disagrees with it. Positioning is the operator's own; the machine's read of it is a proposal.
|
||||||
|
|
||||||
|
5. **Thin evidence is reported, not filled.** Sections the agent marks `[insufficient evidence — operator input needed]` stay marked. Offer to interview the operator for that section instead — an invented differentiator would propagate into every post written against the brief.
|
||||||
|
|
||||||
|
6. **If there is no brain yet** the agent will say there is **no brief yet** — point the user at options 1 (voice samples) and 6 (user profile) first, and exit this step without writing a placeholder file.
|
||||||
|
|
||||||
|
**Important:** This file lives in the per-user data dir, survives reinstalls, and is read by `/linkedin:post`, `/linkedin:quick`, `/linkedin:batch`, `/linkedin:newsletter` Step 1 and the `differentiation-checker`.
|
||||||
|
|
||||||
## Step 4: Recalculate Score
|
## Step 4: Recalculate Score
|
||||||
|
|
||||||
After completing any sub-workflow:
|
After completing any sub-workflow:
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@ You are a LinkedIn growth strategist. Help the user create a personalized growth
|
||||||
## Load Context
|
## Load Context
|
||||||
|
|
||||||
Read these files:
|
Read these files:
|
||||||
- `references/growth-roadmaps.md` - Detailed phase roadmaps
|
- `${CLAUDE_PLUGIN_ROOT}/references/growth-roadmaps.md` - Detailed phase roadmaps
|
||||||
- `references/linkedin-growth-playbook-2025-2026.md` - Comprehensive tactics
|
- `${CLAUDE_PLUGIN_ROOT}/references/linkedin-growth-playbook-2025-2026.md` - Comprehensive tactics
|
||||||
- `references/trajectory-strategy-adjustments.md` - Trajectory-based strategy adjustments
|
- `${CLAUDE_PLUGIN_ROOT}/references/trajectory-strategy-adjustments.md` - Trajectory-based strategy adjustments
|
||||||
- `skills/linkedin-studio/SKILL.md` - User's goals and context
|
- `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` - User's goals and context
|
||||||
|
|
||||||
## Step 0.5: Auto-Detect from State
|
## Step 0.5: Auto-Detect from State
|
||||||
|
|
||||||
|
|
@ -262,7 +262,7 @@ From the state file, compare current growth rate vs `growth_rate_needed`:
|
||||||
|
|
||||||
### Present Trajectory Overlay
|
### Present Trajectory Overlay
|
||||||
|
|
||||||
Using `references/trajectory-strategy-adjustments.md` as the source, show how the phase strategy should be adjusted:
|
Using `${CLAUDE_PLUGIN_ROOT}/references/trajectory-strategy-adjustments.md` as the source, show how the phase strategy should be adjusted:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
### Trajectory Adjustment: [STATUS]
|
### Trajectory Adjustment: [STATUS]
|
||||||
|
|
@ -368,7 +368,7 @@ Use AskUserQuestion to pick which derivatives to create.
|
||||||
|
|
||||||
**Network authority:**
|
**Network authority:**
|
||||||
- [ ] Connected with key people in niche
|
- [ ] Connected with key people in niche
|
||||||
- [ ] Engaging with other thought leaders
|
- [ ] Engaging with recognized experts in the field
|
||||||
- [ ] Tagged or mentioned by others
|
- [ ] Tagged or mentioned by others
|
||||||
- [ ] Invited to contribute/speak
|
- [ ] Invited to contribute/speak
|
||||||
|
|
||||||
|
|
@ -513,10 +513,10 @@ Remind them of the long-term view:
|
||||||
|
|
||||||
## Reference Files
|
## Reference Files
|
||||||
|
|
||||||
- `references/growth-roadmaps.md` - Detailed phase roadmaps
|
- `${CLAUDE_PLUGIN_ROOT}/references/growth-roadmaps.md` - Detailed phase roadmaps
|
||||||
- `references/linkedin-growth-playbook-2025-2026.md` - Comprehensive tactics
|
- `${CLAUDE_PLUGIN_ROOT}/references/linkedin-growth-playbook-2025-2026.md` - Comprehensive tactics
|
||||||
- `references/trajectory-strategy-adjustments.md` - Trajectory-based strategy adjustments
|
- `${CLAUDE_PLUGIN_ROOT}/references/trajectory-strategy-adjustments.md` - Trajectory-based strategy adjustments
|
||||||
- `references/algorithm-signals-reference.md` - topic-relevance signals (authority audit)
|
- `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` - topic-relevance signals (authority audit)
|
||||||
- `references/collaborations-guide.md` - Partnership strategies
|
- `${CLAUDE_PLUGIN_ROOT}/references/collaborations-guide.md` - Partnership strategies
|
||||||
- `references/opportunity-generation.md` - Business development
|
- `${CLAUDE_PLUGIN_ROOT}/references/opportunity-generation.md` - Business development
|
||||||
- `references/newsletter-strategy-guide.md` - For 5K+ followers
|
- `${CLAUDE_PLUGIN_ROOT}/references/newsletter-strategy-guide.md` - For 5K+ followers
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ All flags are optional, given after the command name:
|
||||||
| `--brief-only` | Skip the discovery poll entirely; render the brief from the existing store | off |
|
| `--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 |
|
| `--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 |
|
| `--dry-run` | Poll + score, but persist nothing: no capture, no brief, no status writes, no last-run marker | off |
|
||||||
|
| `--headless` | Unattended run (RE-R3f, slice e): never block on operator input. See **Step 0.5** below. | off |
|
||||||
|
|
||||||
Note the mode inversion deliberately: the **agent's** own default is kortform, this
|
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.
|
**command's** default is long-form. That is why Step 2 must always pass the mode explicitly.
|
||||||
|
|
@ -47,11 +48,44 @@ Note the mode inversion deliberately: the **agent's** own default is kortform, t
|
||||||
arc map, not the pillar brief). The two are complementary: run discovery to find *what happened*,
|
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*.
|
then `--demand` on a chosen theme to see *what the reader is stuck on*.
|
||||||
|
|
||||||
|
## Step 0.5: `--headless` contract (RE-R3f, slice e)
|
||||||
|
|
||||||
|
`--headless` is for an **unattended** invocation — a scheduled task (Claude Code's own Desktop
|
||||||
|
Scheduled Tasks, `--permission-mode dontAsk`) firing this command with no operator present, e.g. a
|
||||||
|
Sunday-morning discovery pass. In `dontAsk` mode `AskUserQuestion` is auto-denied regardless of
|
||||||
|
what this file says, so the contract below is not optional politeness — it is what keeps the run
|
||||||
|
from aborting mid-pass:
|
||||||
|
|
||||||
|
- **`--demand --headless` together is refused.** The demand-sweep's pain-point/vocabulary
|
||||||
|
translation calls for operator judgment this command will not simulate. Print one line saying so
|
||||||
|
and exit — do not silently fall back to supply-side discovery instead of what was asked.
|
||||||
|
- **Step 1 never asks.** If `${DATA}/profile/user-profile.md` is missing (no pillars to score
|
||||||
|
against), print a one-line error naming the missing file and **stop** — do not prompt. The
|
||||||
|
source-list fallback (shipped defaults when `${DATA}/trends/sources.md` is absent) is unaffected;
|
||||||
|
that path already needs no operator input.
|
||||||
|
- **Step 5 (Triage) is skipped unconditionally.** Every kept candidate stays `pending` in the
|
||||||
|
store; nothing is auto-selected or auto-skipped on the operator's behalf. The next brief (or the
|
||||||
|
operator's own `/linkedin:trends` session) surfaces them for triage when a human is present.
|
||||||
|
- **Steps 2–4 and 6 run exactly as in an attended pass** — poll, score, capture, render the brief,
|
||||||
|
write the `.last-run` marker. `--headless` changes *who can be asked*, never what gets persisted.
|
||||||
|
- **Do not ground success in this command's own exit code or prose.** Per Claude Code's headless
|
||||||
|
docs, a `-p` run's process exit code reflects the CLI process, not the pass's semantic outcome —
|
||||||
|
a tool denial or auth failure can still surface as text on stdout. The caller (the scheduled
|
||||||
|
task's own log, or the operator reviewing Monday) verifies success against the same artifacts
|
||||||
|
Step 3 already checks: `CLI status --json`'s capture delta and today's
|
||||||
|
`${DATA}/trends/morning-brief/<date>.md`. State plainly in the final output whether capture/brief
|
||||||
|
actually happened — never report a pass as done because the model said so.
|
||||||
|
|
||||||
|
Scheduling setup (Desktop Scheduled Tasks, not a hand-rolled cron job — see
|
||||||
|
`${CLAUDE_PLUGIN_ROOT}/scripts/trends/README.md` for the exact recipe) is an operator action outside
|
||||||
|
this repo; this command only needs to behave safely once fired.
|
||||||
|
|
||||||
## Step 1: Load context
|
## Step 1: Load context
|
||||||
|
|
||||||
1. **Pillars:** Read `${DATA}/profile/user-profile.md` and extract the content pillars /
|
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
|
expertise areas. If the file does not exist: on `--headless`, print a one-line error naming
|
||||||
proceeding (one question, comma-separated answer).
|
the missing file and stop (**Step 0.5** — never ask unattended); otherwise 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
|
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`.
|
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 —
|
Tell the user which one applies. Do not read research-tooling or route research yourself —
|
||||||
|
|
@ -60,6 +94,9 @@ then `--demand` on a chosen theme to see *what the reader is stuck on*.
|
||||||
|
|
||||||
## Step 2D: Run the demand-sweep (only on `--demand`)
|
## Step 2D: Run the demand-sweep (only on `--demand`)
|
||||||
|
|
||||||
|
**If `--demand` and `--headless` are both set:** refuse (**Step 0.5**) — print one line stating
|
||||||
|
the demand-sweep needs an operator present and exit. Do not run either pass.
|
||||||
|
|
||||||
**If `--demand`:** this replaces Steps 2–4 (the supply-side discovery pass). Delegate to the
|
**If `--demand`:** this replaces Steps 2–4 (the supply-side discovery pass). Delegate to the
|
||||||
demand-spotter agent — invoke it via `Task` with `subagent_type: linkedin-studio:demand-spotter`
|
demand-spotter agent — invoke it via `Task` with `subagent_type: linkedin-studio:demand-spotter`
|
||||||
(foreground). The prompt MUST state explicitly:
|
(foreground). The prompt MUST state explicitly:
|
||||||
|
|
@ -128,9 +165,13 @@ Include each candidate's store id (shown in the brief and via `CLI list --json`)
|
||||||
step below resolves per id. On `--dry-run`, present the same list but say clearly that nothing
|
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.
|
was persisted and there are no store ids to triage.
|
||||||
|
|
||||||
## Step 5: Triage (skip on `--dry-run`)
|
## Step 5: Triage (skip on `--dry-run` or `--headless`)
|
||||||
|
|
||||||
Resolve the top of the queue now instead of leaving it as homework. For the candidates in the
|
**On `--headless`:** skip this step unconditionally (**Step 0.5** — `AskUserQuestion` is
|
||||||
|
auto-denied in an unattended run regardless). Every kept candidate stays `pending` in the store;
|
||||||
|
report the count and move to Step 6. Do not attempt to select or skip on the operator's behalf.
|
||||||
|
|
||||||
|
Otherwise, 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
|
top bands (Immediate + High; cap at 8), use AskUserQuestion — one question per candidate, up
|
||||||
to 4 candidates per call, options:
|
to 4 candidates per call, options:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ You are a LinkedIn video scripting assistant. Guide the user through creating a
|
||||||
|
|
||||||
First, load persistent state and personalization:
|
First, load persistent state and personalization:
|
||||||
- Read `~/.claude/linkedin-studio.local.md` for posting state (streak, weekly progress, recent topics)
|
- Read `~/.claude/linkedin-studio.local.md` for posting state (streak, weekly progress, recent topics)
|
||||||
- Read `skills/linkedin-studio/SKILL.md` for user profile, voice settings, and preferences
|
- Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for user profile, voice settings, and preferences
|
||||||
|
|
||||||
Check state for topic planning:
|
Check state for topic planning:
|
||||||
- Compare intended topic against "Recent Posts" in state file
|
- Compare intended topic against "Recent Posts" in state file
|
||||||
|
|
@ -35,14 +35,14 @@ Check weekly progress:
|
||||||
- If `posts_this_week == weekly_goal - 1`, note: "This video will hit your weekly goal."
|
- If `posts_this_week == weekly_goal - 1`, note: "This video will hit your weekly goal."
|
||||||
|
|
||||||
Load video-specific references:
|
Load video-specific references:
|
||||||
- Read `references/video-strategy-guide.md` for script templates, pacing, and production guidance
|
- Read `${CLAUDE_PLUGIN_ROOT}/references/video-strategy-guide.md` for script templates, pacing, and production guidance
|
||||||
- Read `references/linkedin-formats.md` (Video Content Deep Dive section) for algorithm data and technical specs
|
- Read `${CLAUDE_PLUGIN_ROOT}/references/linkedin-formats.md` (Video Content Deep Dive section) for algorithm data and technical specs
|
||||||
|
|
||||||
Check for existing assets:
|
Check for existing assets:
|
||||||
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` — Match the user's natural voice (read before scripting)
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` — Match the user's natural voice (read before scripting)
|
||||||
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/examples/high-engagement-posts.md` — Study successful patterns
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/examples/high-engagement-posts.md` — Study successful patterns
|
||||||
|
|
||||||
**Graceful degradation (fresh adopter):** if `voice-samples/` is empty or absent, don't block — fall back to the voice settings in `skills/linkedin-studio/SKILL.md` (or neutral defaults), note that the script uses a default voice, and proceed. The voice-guardian is suppressed under 5 samples, so there is no hard gate to satisfy.
|
**Graceful degradation (fresh adopter):** if `voice-samples/` is empty or absent, don't block — fall back to the voice settings in `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` (or neutral defaults), note that the script uses a default voice, and proceed. The voice-guardian is suppressed under 5 samples, so there is no hard gate to satisfy.
|
||||||
|
|
||||||
## Step 1: Choose Video Type
|
## Step 1: Choose Video Type
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ Default recommendation: **60 seconds** is the 2026 sweet spot — shorter videos
|
||||||
Follow the same flow as `/linkedin:post`:
|
Follow the same flow as `/linkedin:post`:
|
||||||
|
|
||||||
1. Ask what they want the video to be about (if not already clear)
|
1. Ask what they want the video to be about (if not already clear)
|
||||||
2. Read `references/content-angles.md` for the 8 universal angles
|
2. Read `${CLAUDE_PLUGIN_ROOT}/references/content-angles.md` for the 8 universal angles
|
||||||
3. Present 2-3 angle options via AskUserQuestion
|
3. Present 2-3 angle options via AskUserQuestion
|
||||||
4. Verify topic doesn't duplicate recent posts (check state file)
|
4. Verify topic doesn't duplicate recent posts (check state file)
|
||||||
5. Confirm topic aligns with user's 5 core expertise areas
|
5. Confirm topic aligns with user's 5 core expertise areas
|
||||||
|
|
@ -83,7 +83,7 @@ Follow the same flow as `/linkedin:post`:
|
||||||
Delegate script generation to the `video-scripter` agent — invoke it via `Task` with `subagent_type: linkedin-studio:video-scripter` (foreground, from this command layer). The agent will:
|
Delegate script generation to the `video-scripter` agent — invoke it via `Task` with `subagent_type: linkedin-studio:video-scripter` (foreground, from this command layer). The agent will:
|
||||||
|
|
||||||
1. Calculate word budget based on selected length (duration × 2.5 wps)
|
1. Calculate word budget based on selected length (duration × 2.5 wps)
|
||||||
2. Select the appropriate script template from `references/video-strategy-guide.md`
|
2. Select the appropriate script template from `${CLAUDE_PLUGIN_ROOT}/references/video-strategy-guide.md`
|
||||||
3. Write the full script with:
|
3. Write the full script with:
|
||||||
- Timing markers (`[0:00-0:03]`, etc.)
|
- Timing markers (`[0:00-0:03]`, etc.)
|
||||||
- Visual cues (`[CAM:]`, `[SCREEN:]`, `[SLIDE:]`, `[TEXT:]`)
|
- Visual cues (`[CAM:]`, `[SCREEN:]`, `[SLIDE:]`, `[TEXT:]`)
|
||||||
|
|
@ -232,9 +232,9 @@ Replace placeholders with actual post data. This replaces manual YAML editing.
|
||||||
|
|
||||||
## Reference Files
|
## Reference Files
|
||||||
|
|
||||||
- `references/video-strategy-guide.md` — Script templates, pacing, production
|
- `${CLAUDE_PLUGIN_ROOT}/references/video-strategy-guide.md` — Script templates, pacing, production
|
||||||
- `references/linkedin-formats.md` — Video specs, algorithm, technical requirements
|
- `${CLAUDE_PLUGIN_ROOT}/references/linkedin-formats.md` — Video specs, algorithm, technical requirements
|
||||||
- `references/engagement-frameworks.md` — Hook types, CTAs
|
- `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md` — Hook types, CTAs
|
||||||
- `references/content-angles.md` — 8 universal angles
|
- `${CLAUDE_PLUGIN_ROOT}/references/content-angles.md` — 8 universal angles
|
||||||
- `references/algorithm-signals-reference.md` — Algorithm mechanics
|
- `${CLAUDE_PLUGIN_ROOT}/references/algorithm-signals-reference.md` — Algorithm mechanics
|
||||||
- `assets/checklists/quality-scorecard.md` — Pre-publish check
|
- `${CLAUDE_PLUGIN_ROOT}/assets/checklists/quality-scorecard.md` — Pre-publish check
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
"annotation — optional annotatable review HTML for a manual pass (Step 7)",
|
"annotation — optional annotatable review HTML for a manual pass (Step 7)",
|
||||||
"visual-assets — cover (+ optional inline figures) or carousel deck: brief → generate → operator-gate → approve, BEFORE lock so build-linkedin.mjs picks them up (Step 7.5)",
|
"visual-assets — cover (+ optional inline figures) or carousel deck: brief → generate → operator-gate → approve, BEFORE lock so build-linkedin.mjs picks them up (Step 7.5)",
|
||||||
"lock-delivery — LOCK → POST.html all-in-one-place deliverable (Step 8)",
|
"lock-delivery — LOCK → POST.html all-in-one-place deliverable (Step 8)",
|
||||||
"hook-conversion-gate — persona gate on distribution text post-lock: would YOU click? (Step 9)",
|
"hook-conversion-gate — persona gate on distribution text post-lock: hook-forge, would YOU click?, and who forwards this and why (Step 9)",
|
||||||
"scheduling — register edition in plugin queue/state for native LinkedIn scheduling (Step 10)",
|
"scheduling — register edition in plugin queue/state for native LinkedIn scheduling (Step 10)",
|
||||||
"retro — close the loop (≤5 min, AFTER scheduling): promote the pending articles.NN.foldIns with an explicit operator JA/NEI (mechanical → a contract-gate rule; judgment → the language-rules file / craft checklist), summarize the MEASURED effort from articles.NN.phaseLog, and ask ONE process-friction question. The corrections made in this edition become enforced before the next one starts (Step 11)"
|
"retro — close the loop (≤5 min, AFTER scheduling): promote the pending articles.NN.foldIns with an explicit operator JA/NEI (mechanical → a contract-gate rule; judgment → the language-rules file / craft checklist), summarize the MEASURED effort from articles.NN.phaseLog, and ask ONE process-friction question. The corrections made in this edition become enforced before the next one starts (Step 11)"
|
||||||
],
|
],
|
||||||
|
|
@ -31,9 +31,12 @@
|
||||||
"visualAssets": "Per-article visual-asset record written by Step 7.5 (visual-assets phase). Runs BEFORE lock because render/build-linkedin.mjs picks up linkedin/NN/cover.png + the edition-config credit/caption when it builds POST.html — generating images after lock would force a re-render. Shape: { format: \"standard\" | \"carousel\"; cover: { brief, route, candidates[], approved, status }; figures: [ { id, brief, placement, status } ]; carousel: null | { source, pdf, status } }. format \"standard\" = cover + optional inline figures (cover.png is mandatory per the KTG cover-directive); format \"carousel\" = typografisk deck via render/build-carousel.mjs instead of cover+inline (cover/figures stay empty). route: \"mcp-image\" (default, via mcp__mcp-image__generate_image) | \"external\" (DALL·E / Midjourney / photographer → linkedin/NN/cover-raw.png). status ladder: pending → briefed → generated → approved. candidates[] holds the cover-v<N>-kandidat.png attempts; approved is the fixed approved name (\"cover.png\") once the operator-gate passes. figures[].id = \"fig1\"..; placement = section reference in NN-utkast.md (figures are referenced in the draft via  and uploaded manually in the LinkedIn editor — build-linkedin.mjs does NOT embed them). Naming convention: cover.png (approved, fixed — what build-linkedin.mjs reads) | cover-v<N>-kandidat.png (attempts) | cover-raw.png (optional external pre-edit source) | fig<N>.png (inline). credit + caption are recorded in <serie>/linkedin/image-credit-caption.md and flow into edition-config.json coverCredit + captions[NN].",
|
"visualAssets": "Per-article visual-asset record written by Step 7.5 (visual-assets phase). Runs BEFORE lock because render/build-linkedin.mjs picks up linkedin/NN/cover.png + the edition-config credit/caption when it builds POST.html — generating images after lock would force a re-render. Shape: { format: \"standard\" | \"carousel\"; cover: { brief, route, candidates[], approved, status }; figures: [ { id, brief, placement, status } ]; carousel: null | { source, pdf, status } }. format \"standard\" = cover + optional inline figures (cover.png is mandatory per the KTG cover-directive); format \"carousel\" = typografisk deck via render/build-carousel.mjs instead of cover+inline (cover/figures stay empty). route: \"mcp-image\" (default, via mcp__mcp-image__generate_image) | \"external\" (DALL·E / Midjourney / photographer → linkedin/NN/cover-raw.png). status ladder: pending → briefed → generated → approved. candidates[] holds the cover-v<N>-kandidat.png attempts; approved is the fixed approved name (\"cover.png\") once the operator-gate passes. figures[].id = \"fig1\"..; placement = section reference in NN-utkast.md (figures are referenced in the draft via  and uploaded manually in the LinkedIn editor — build-linkedin.mjs does NOT embed them). Naming convention: cover.png (approved, fixed — what build-linkedin.mjs reads) | cover-v<N>-kandidat.png (attempts) | cover-raw.png (optional external pre-edit source) | fig<N>.png (inline). credit + caption are recorded in <serie>/linkedin/image-credit-caption.md and flow into edition-config.json coverCredit + captions[NN].",
|
||||||
"livedSpecifics": "Per-article lived-specifics binding (Fix #2 slice 2, kilde-så-draft). A slot-map binding each LOAD-BEARING key-point (or, later, section) of this edition to either a real specific from the specifics-bank OR an explicit escape decision — so a draft starts grounded in the operator's real material instead of inventing plausible filler. Resolved at Step 1.5 (lived-specifics extraction, BETWEEN brief (Step 1) and research (Step 2); wired in slice 3) via a guided interview that REFUSES vague answers. THE GATE (drømme-spec G4, «vaghet avvises», enforced at the Step 2.5 skeleton-gate): every slot must resolve to \"specific\" (grounded), \"abstrakt\" (deliberately abstract here, with a rationale), or \"ekstern\" (backed by research, not lived material) — a slot left \"unresolved\" is the vagueness that BLOCKs. Numbers stay guilty-until-checked: a slot backed by an UNVERIFIED number is allowed but WARNED (regel 6/7), so Step 2 research verifies it before the draft asserts it. The deterministic validator + the rendered NN-kilder.md artifact live in scripts/specifics-bank (binding.ts/kilder.ts; `node --import tsx src/cli.ts validate-binding|render-kilder --edition <edition-state.json>`). Shape: { slots: [ { slotId, kind: \"key-point\" | \"section\", label, binding: ({ type: \"specific\", specificId } | { type: \"abstrakt\", rationale } | { type: \"ekstern\", source? } | { type: \"unresolved\" }) } ], status: \"pending\" | \"bound\" }. Default { slots: [], status: \"pending\" }: populated on first Step 1.5. Companion artifact: <serie>/linkedin/NN-kilder.md, rendered from this + the bank (regenerated, never hand-edited). This is the per-edition consumer of the global specifics-bank.",
|
"livedSpecifics": "Per-article lived-specifics binding (Fix #2 slice 2, kilde-så-draft). A slot-map binding each LOAD-BEARING key-point (or, later, section) of this edition to either a real specific from the specifics-bank OR an explicit escape decision — so a draft starts grounded in the operator's real material instead of inventing plausible filler. Resolved at Step 1.5 (lived-specifics extraction, BETWEEN brief (Step 1) and research (Step 2); wired in slice 3) via a guided interview that REFUSES vague answers. THE GATE (drømme-spec G4, «vaghet avvises», enforced at the Step 2.5 skeleton-gate): every slot must resolve to \"specific\" (grounded), \"abstrakt\" (deliberately abstract here, with a rationale), or \"ekstern\" (backed by research, not lived material) — a slot left \"unresolved\" is the vagueness that BLOCKs. Numbers stay guilty-until-checked: a slot backed by an UNVERIFIED number is allowed but WARNED (regel 6/7), so Step 2 research verifies it before the draft asserts it. The deterministic validator + the rendered NN-kilder.md artifact live in scripts/specifics-bank (binding.ts/kilder.ts; `node --import tsx src/cli.ts validate-binding|render-kilder --edition <edition-state.json>`). Shape: { slots: [ { slotId, kind: \"key-point\" | \"section\", label, binding: ({ type: \"specific\", specificId } | { type: \"abstrakt\", rationale } | { type: \"ekstern\", source? } | { type: \"unresolved\" }) } ], status: \"pending\" | \"bound\" }. Default { slots: [], status: \"pending\" }: populated on first Step 1.5. Companion artifact: <serie>/linkedin/NN-kilder.md, rendered from this + the bank (regenerated, never hand-edited). This is the per-edition consumer of the global specifics-bank.",
|
||||||
"personas": "Per-article resolved reader-persona set (input config), written/confirmed in Step 1. This makes personas configurable PER ARTIFACT, not just from one global plugin library: Step 1 resolves them in order — (1) already present here → use as-is; (2) <serie>/linkedin/personas.md (per-series file) → load; (3) plugin config/personas.local.md (or personas.template.md) library → select a subset; (4) none/insufficient → DEFINE interactively via AskUserQuestion. Exactly one entry has tier \"primær\" (the rest \"sekundær\"); «primær trumfer» on conflict. This set feeds BOTH the in-session sweep (Step 6) and the headless package (Step 6.5 / persona-reviewer). Each entry: { name, tier: \"primær\" | \"sekundær\", rolle, avkobler, overbeviser, ekspertise, sjargong, source: \"edition-state\" | \"series-file\" | \"plugin-library\" | \"interactive\" }. Default []: resolved on first Step 1.",
|
"personas": "Per-article resolved reader-persona set (input config), written/confirmed in Step 1. This makes personas configurable PER ARTIFACT, not just from one global plugin library: Step 1 resolves them in order — (1) already present here → use as-is; (2) <serie>/linkedin/personas.md (per-series file) → load; (3) plugin config/personas.local.md (or personas.template.md) library → select a subset; (4) none/insufficient → DEFINE interactively via AskUserQuestion. Exactly one entry has tier \"primær\" (the rest \"sekundær\"); «primær trumfer» on conflict. This set feeds BOTH the in-session sweep (Step 6) and the headless package (Step 6.5 / persona-reviewer). Each entry: { name, tier: \"primær\" | \"sekundær\", rolle, avkobler, overbeviser, ekspertise, sjargong, source: \"edition-state\" | \"series-file\" | \"plugin-library\" | \"interactive\" }. Default []: resolved on first Step 1.",
|
||||||
|
"personaSweep": "Per-article reader-persona verdicts — one key per sweep point, all written by persona-reviewer. skeleton: the sweep on the five-line skeleton BEFORE prose (Step 2.5), so a premise that does not land is caught while a rewrite is still cheap. resonance: the pre-lock sweep on the full draft (Step 6), including the recorded wordCount that /linkedin:pivot's >20 % heuristic compares a later draft against. conversion: the post-lock «would YOU click?» verdict on the chosen hook (Step 9, primær persona, konverter mode). deling: the post-lock shareability verdict (N24, Step 9 — the second binary next to conversion): «who forwards this to a colleague, and why», plus which action the edition actually earns — RESHARE | SAVE | SEND. A plausible answer must name a ROLE the reader could point at AND a sender-motive (signals their judgment / saves a colleague a decision / settles a running argument); «useful for anyone in tech» names nobody and is a NEI. Shape of deling: { verdict: \"JA\" | \"NEI\", role, motive, action: \"RESHARE\" | \"SAVE\" | \"SEND\", operatorDecision? }. Two failed re-framings → a recorded operator decision (/linkedin:pivot, or ship + an articles.NN.foldIns row for the Step 11 retro) — there is never a silent pass, and the gate is never resolved by re-reading the same copy more generously. All four keys additive-optional (default null, NO schemaVersion bump — an edition produced before the step that writes one simply has none).",
|
||||||
|
"hookForge": "Per-article hook-forge record written by Step 9 (hook-conversion-gate, N24). Additive-optional (default null, NO schemaVersion bump — same pattern as phaseLog/retro; an edition produced before N24 simply has none). The hook competes against alternatives instead of shipping the one the draft happened to end on: 5–8 variants are forged for the distribution copy (the body stays locked, as everywhere in this step). The 110–140-character band is enforced PER VARIANT and MEASURED — `perl -CSD -ne 'chomp; printf \"%d\\n\", length($_)'`, never `awk length`/`wc -c`, which count BYTES and over-report on æ/ø/å. A variant outside the band is DISQUALIFIED, not ranked lower (a cutoff, not a preference). The surviving set must span ≥3 hook types, or it is a set of rewrites and the operator has no real choice; every variant must trace to a claim the body actually makes (no bait hook — an unearned promise is what the randsone fact gate exists to catch). Fewer than five survivors ⇒ forge again. Shape: { variants: [ { text, measuredChars, hookType, bodyTrace, disqualified: null | \"band\" | \"body-trace\" } ], typesSpanned, chosen, chosenAt }. NO PREDICTED SCORE: a variant carries only its measured length, its hook type and its body trace. A predicted-engagement ranking would be a fabricated composite — exactly the class removed in N22 — and a ranked list only LOOKS more helpful. The ranking is the operator's; the plugin does not rank what it cannot measure.",
|
||||||
|
"randsoneGate": "Per-article randsone-gate record written by Step 9 (hook-conversion-gate, N9). Additive-optional (default null, NO schemaVersion bump). The «randsone» is the distribution copy AROUND a locked edition — delingstekst hook, samle post, carousel slide text, edition title — text that ships with the edition but never passed the body's fact and language gates. Two scoped reviewers judge it (fact-reviewer F1/F3, language-reviewer L1/L2), plus a title-strength verdict on the distribution title. The gate NEVER reopens the locked body: a 🔴 that traces to the body (or a canonical-title weakness) is a pivot finding — STOP and run /linkedin:pivot; everything else is fixed in the distribution copy and re-checked. The chosen hook-forge variant goes THROUGH this gate, never around it. Shape: { surfaces: [ { surface, factVerdict, languageVerdict, revised } ], titleStrength, pivotTriggered, resolvedAt }.",
|
||||||
"headlessReview": "Per-article headless-review record written by Step 6.5 (headless-review phase). Runs AFTER the in-session persona sweep (Step 6) and BEFORE lock (Step 8), on a FROZEN snapshot of the publish-ready (or pivoted) draft, fanned out from the command layer (foreground) or invoked standalone via /linkedin:headless-review in a fresh/cold session. Five archetypes judge independently with NO drafting-session context: content-reviewer (argument integrity), language-reviewer (Norwegian language), fact-reviewer (cold re-verification incl. claims a late pivot bolted on), persona-reviewer mode=resonans (per active persona), persona-reviewer mode=konverter (primær, hook only). The consolidated report is surfaced to the operator via SendUserFile; the operator decides which flags fold in. Shape: { frozenDraft, reviewers: { content, language, fact, personaResonance, personaConversion } (each { reportPath, summary, status }), consolidatedReport, foldedIn, waived, status }. status ladder: pending → run → folded. null until Step 6.5 runs. This is the adversarial-independence companion to the in-session gates (editorialReview, personaSweep, factcheckLog) — deliberately redundant: a cold reader catches what the framing-biased in-session pass missed.",
|
"headlessReview": "Per-article headless-review record written by Step 6.5 (headless-review phase). Runs AFTER the in-session persona sweep (Step 6) and BEFORE lock (Step 8), on a FROZEN snapshot of the publish-ready (or pivoted) draft, fanned out from the command layer (foreground) or invoked standalone via /linkedin:headless-review in a fresh/cold session. Five archetypes judge independently with NO drafting-session context: content-reviewer (argument integrity), language-reviewer (Norwegian language), fact-reviewer (cold re-verification incl. claims a late pivot bolted on), persona-reviewer mode=resonans (per active persona), persona-reviewer mode=konverter (primær, hook only). The consolidated report is surfaced to the operator via SendUserFile; the operator decides which flags fold in. Shape: { frozenDraft, reviewers: { content, language, fact, personaResonance, personaConversion } (each { reportPath, summary, status }), consolidatedReport, foldedIn, waived, status }. status ladder: pending → run → folded. null until Step 6.5 runs. This is the adversarial-independence companion to the in-session gates (editorialReview, personaSweep, factcheckLog) — deliberately redundant: a cold reader catches what the framing-biased in-session pass missed.",
|
||||||
"pivots": "Per-article pivot log (Endring 9c). A pivot is a substantive change to a draft AFTER a gate had already cleared — e.g. a new argument anchor / section added late (the Del 4 Security Champions case: +~530 words, 2 new sections, +42 %). Each /linkedin:pivot invocation appends one entry and moves currentPhase back so the cleared gates (Steps 5–6.5) re-run on the pivoted version before lock. Heuristic (documented, checked at the Step 8 lock precondition): if the current draft's word count differs > 20 % from the version that last cleared Step 6, OR it has > 2 new sections, a pivot-reopen is suggested/required. Each entry: { timestamp, reason, fromPhase, toPhase, wordCountBefore, wordCountAfter, deltaPct, newSections, gatesToRerun: [phase…] }. Default [].",
|
"pivots": "Per-article pivot log (Endring 9c). A pivot is a substantive change to a draft AFTER a gate had already cleared — e.g. a new argument anchor / section added late (the Del 4 Security Champions case: +~530 words, 2 new sections, +42 %). Each /linkedin:pivot invocation appends one entry and moves currentPhase back so the cleared gates (Steps 5–6.5) re-run on the pivoted version before lock. Heuristic (documented, checked at the Step 8 lock precondition): if the current draft's word count differs > 20 % from the version that last cleared Step 6, OR it has > 2 new sections, a pivot-reopen is suggested/required. Each entry: { timestamp, reason, fromPhase, toPhase, wordCountBefore, wordCountAfter, deltaPct, newSections, gatesToRerun: [phase…] }. Default [].",
|
||||||
"foldIns": "Per-article accumulation queue (slice 2 of fix #1 — «rettelser fester seg»). Each correction KTG makes during an edition that is NOT yet a contract rule is captured here, then routed by the JA-promoter (maskinrommet/docs/skrivekontrakt.md §E) to a permanent home so it never has to be re-discovered: a MECHANICAL correction → a rules.ts gate rule (BLOCK/WARN) + a §B-row/§C1/§C2-box + a §E-manifest row (the contract-gate `ratify` check then asserts rules.ts ↔ §E-manifest stay in bijection); a JUDGMENT correction → a §C2-box only (stays with editorial-reviewer, no gate rule). Capture is per-article (provenance = which article surfaced it); promotion is series/contract-wide. Each entry: { id, date (ISO-8601), correction (what KTG corrected, near-verbatim), trigger (where/why it surfaced), classification: \"mechanical-block\" | \"mechanical-warn\" | \"judgment\" | null (set at the classify step), decision: \"pending\" | \"promoted\" | \"rejected\" (the JA-promoter outcome), ruleId: <rules.ts id> | null (set on promote for mechanical), note?: where a judgment/rejected fold-in landed }. Default []. Rejected fold-ins are kept for traceability, never deleted.",
|
"foldIns": "Per-article accumulation queue (slice 2 of fix #1 — «rettelser fester seg»). Each correction the operator makes during an edition that is NOT yet a contract rule is captured here, then routed by the JA-promoter (maskinrommet/docs/skrivekontrakt.md §E) to a permanent home so it never has to be re-discovered: a MECHANICAL correction → a rules.ts gate rule (BLOCK/WARN) + a §B-row/§C1/§C2-box + a §E-manifest row (the contract-gate `ratify` check then asserts rules.ts ↔ §E-manifest stay in bijection); a JUDGMENT correction → a §C2-box only (stays with editorial-reviewer, no gate rule). Capture is per-article (provenance = which article surfaced it); promotion is series/contract-wide. Each entry: { id, date (ISO-8601), correction (what the operator corrected, near-verbatim), trigger (where/why it surfaced), classification: \"mechanical-block\" | \"mechanical-warn\" | \"judgment\" | null (set at the classify step), decision: \"pending\" | \"promoted\" | \"rejected\" (the JA-promoter outcome), ruleId: <rules.ts id> | null (set on promote for mechanical), note?: where a judgment/rejected fold-in landed }. Default []. Rejected fold-ins are kept for traceability, never deleted.",
|
||||||
"retro": "Per-article retro record written by Step 11 (retro phase), the step that makes a correction STICK. Additive-optional (default null, NO schemaVersion bump — same pattern as phaseLog/sourceTrendId/targetLevel; an edition produced before N14 simply has none). Runs AFTER scheduling (Step 10) because the retro is about the NEXT edition, not this one's delivery. Three jobs, all recorded here: (1) fold-in promotion — every articles.NN.foldIns row with decision \"pending\" gets an explicit operator JA/NEI; JA on a mechanical correction promotes it atomically to a scripts/contract-gate rule (rules.ts + contract row + manifest row, `--ratify` green or the promotion does not count), JA on a judgment correction appends it to the user language-rules file (${LINKEDIN_STUDIO_DATA}/language-rules/<lang>.md) or the craft checklist; NEI marks the row \"rejected\" and KEEPS it. (2) effort summary — read from the MEASURED articles.NN.phaseLog (N12), never re-estimated: number of transitions, phases that recurred (a re-run gate), and the elapsed lead time. (3) ONE process-friction question, whose answer is handed BACK to the operator — the plugin never writes the operator's own notes/register. Shape: { promoted, rejected, ruleIds: [<rules.ts id>…], languageRulesAppended, effort: { transitions, recurredPhases: [phase…], leadTimeDays }, frictionNoted: boolean, completedAt }. The full loop (capture → classify → promote → enforce) is documented domain-generally in references/fold-in-loop.md.",
|
"retro": "Per-article retro record written by Step 11 (retro phase), the step that makes a correction STICK. Additive-optional (default null, NO schemaVersion bump — same pattern as phaseLog/sourceTrendId/targetLevel; an edition produced before N14 simply has none). Runs AFTER scheduling (Step 10) because the retro is about the NEXT edition, not this one's delivery. Three jobs, all recorded here: (1) fold-in promotion — every articles.NN.foldIns row with decision \"pending\" gets an explicit operator JA/NEI; JA on a mechanical correction promotes it atomically to a scripts/contract-gate rule (rules.ts + contract row + manifest row, `--ratify` green or the promotion does not count), JA on a judgment correction appends it to the user language-rules file (${LINKEDIN_STUDIO_DATA}/language-rules/<lang>.md) or the craft checklist; NEI marks the row \"rejected\" and KEEPS it. (2) effort summary — read from the MEASURED articles.NN.phaseLog (N12), never re-estimated: number of transitions, phases that recurred (a re-run gate), and the elapsed lead time. (3) ONE process-friction question, whose answer is handed BACK to the operator — the plugin never writes the operator's own notes/register. Shape: { promoted, rejected, ruleIds: [<rules.ts id>…], languageRulesAppended, effort: { transitions, recurredPhases: [phase…], leadTimeDays }, frictionNoted: boolean, completedAt }. The full loop (capture → classify → promote → enforce) is documented domain-generally in references/fold-in-loop.md.",
|
||||||
"language": "Review language for this series/edition (additive, default \"en\"). Threads into the long-form review agents so they grade against THIS language's rules: language-reviewer applies Norwegian-specific checks (anglicism→Norwegian idiom, «kanselli-stil») only when language == \"no\"; voice-scrubber's gold standard is the approved editions IN this language; any other value → the agents apply that language's equivalents and never grade prose against Norwegian idiom. \"no\" = Norwegian (the author's case). Resolved at Step 1 / load-context and passed to the language-dependent agents.",
|
"language": "Review language for this series/edition (additive, default \"en\"). Threads into the long-form review agents so they grade against THIS language's rules: language-reviewer applies Norwegian-specific checks (anglicism→Norwegian idiom, «kanselli-stil») only when language == \"no\"; voice-scrubber's gold standard is the approved editions IN this language; any other value → the agents apply that language's equivalents and never grade prose against Norwegian idiom. \"no\" = Norwegian (the author's case). Resolved at Step 1 / load-context and passed to the language-dependent agents.",
|
||||||
"sourceTrendId": "Per-article provenance link to the trend this edition was started from (N7 trend→newsletter bridge, MR-F3). Additive-optional (default null, NO schemaVersion bump — an edition started manually never sets it, and Step 10 reads its absence as \"no source trend\"). Set at the Step 1.5 checkpoint when Step 1's trend-intake read a candidate from the trends store (scripts/trends) to prefill the brief (angle / targetLevel / key-points / source-URLs). Its ONE runtime consumer is Step 10 (scheduling): when the article reaches scheduling AND sourceTrendId is set, the command flips that trend's store status to \"acted\" (trends CLI `act --id <sourceTrendId>`), closing the discovery→production loop deterministically instead of by hand. Value = the trend's store id (normalized title+url, as shown in the /linkedin:trends brief and `CLI list --json`).",
|
"sourceTrendId": "Per-article provenance link to the trend this edition was started from (N7 trend→newsletter bridge, MR-F3). Additive-optional (default null, NO schemaVersion bump — an edition started manually never sets it, and Step 10 reads its absence as \"no source trend\"). Set at the Step 1.5 checkpoint when Step 1's trend-intake read a candidate from the trends store (scripts/trends) to prefill the brief (angle / targetLevel / key-points / source-URLs). Its ONE runtime consumer is Step 10 (scheduling): when the article reaches scheduling AND sourceTrendId is set, the command flips that trend's store status to \"acted\" (trends CLI `act --id <sourceTrendId>`), closing the discovery→production loop deterministically instead of by hand. Value = the trend's store id (normalized title+url, as shown in the /linkedin:trends brief and `CLI list --json`).",
|
||||||
|
|
@ -65,7 +68,8 @@
|
||||||
"personaSweep": {
|
"personaSweep": {
|
||||||
"skeleton": null,
|
"skeleton": null,
|
||||||
"resonance": null,
|
"resonance": null,
|
||||||
"conversion": null
|
"conversion": null,
|
||||||
|
"deling": null
|
||||||
},
|
},
|
||||||
"headlessReview": {
|
"headlessReview": {
|
||||||
"frozenDraft": null,
|
"frozenDraft": null,
|
||||||
|
|
@ -93,6 +97,8 @@
|
||||||
"figures": [],
|
"figures": [],
|
||||||
"carousel": null
|
"carousel": null
|
||||||
},
|
},
|
||||||
|
"hookForge": null,
|
||||||
|
"randsoneGate": null,
|
||||||
"pivots": [],
|
"pivots": [],
|
||||||
"foldIns": [],
|
"foldIns": [],
|
||||||
"retro": null,
|
"retro": null,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ trim, or extend them per series.
|
||||||
NO is **not** accepted: the text is revised until the primær reaches a clean YES.
|
NO is **not** accepted: the text is revised until the primær reaches a clean YES.
|
||||||
- **Two sweep modes** (same `persona-reviewer` agent): resonance mode (Step 6,
|
- **Two sweep modes** (same `persona-reviewer` agent): resonance mode (Step 6,
|
||||||
BEFORE lock — «does the point land for this reader?») and conversion mode
|
BEFORE lock — «does the point land for this reader?») and conversion mode
|
||||||
(Step 9, after lock — binary «would YOU click?» on the hook only).
|
(Step 9, after lock — two binaries on the distribution copy: «would YOU click?»
|
||||||
|
and «who forwards this to a colleague, and why?»).
|
||||||
|
|
||||||
### Per-artifact personas (one or more personas per edition)
|
### Per-artifact personas (one or more personas per edition)
|
||||||
|
|
||||||
|
|
|
||||||
71
config/positioning-brief.template.md
Normal file
71
config/positioning-brief.template.md
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Positioning Brief
|
||||||
|
|
||||||
|
> **What this file is.** A distillate — the second brain's evidence-weighted read of your
|
||||||
|
> positioning, written by `/linkedin:setup` (Step 3g) and consumed by every drafting
|
||||||
|
> surface. It answers four questions a draft cannot answer for itself: who is writing,
|
||||||
|
> who it is for, what they actually hold, and why it is not interchangeable with the
|
||||||
|
> other twenty posts on the topic that week.
|
||||||
|
>
|
||||||
|
> **What this file is not.** It **does not restate** `user-profile.md`. That file is your
|
||||||
|
> *declared* identity — name, role, the five expertise areas, the target audience, the
|
||||||
|
> ranked goals — filled in by you and authoritative as such. This file is the *distilled*
|
||||||
|
> layer above it: the stance and the difference, inferred from what you have actually
|
||||||
|
> published and consolidated into `brain/profile.md`. When the two disagree, the declared
|
||||||
|
> profile and your dated `## Who I am now` anchor win, and the inference is the thing that
|
||||||
|
> gets corrected. Two files asserting the same fact is how positioning quietly forks.
|
||||||
|
>
|
||||||
|
> **Editing.** This file is yours to edit directly. Regeneration never overwrites it
|
||||||
|
> silently: `/linkedin:setup` shows a diff and asks for an explicit go first.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Who I am
|
||||||
|
|
||||||
|
[One or two sentences. The working identity a reader would recognise across your posts —
|
||||||
|
not your job title. Distilled from `brain/profile.md` Static facts and the operations
|
||||||
|
anchor; grounded in what you have published, not in what you aspire to.]
|
||||||
|
|
||||||
|
_Evidence:_ [which consolidated facts or published pieces support this]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Who this is for
|
||||||
|
|
||||||
|
[The reader you are actually writing to — the one whose problem your posts solve. Narrower
|
||||||
|
than the declared target audience if the published record says so; say which, and why.]
|
||||||
|
|
||||||
|
_Evidence:_ [engagement patterns, recurring commenters, the questions you actually get]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What I hold
|
||||||
|
|
||||||
|
[The two to four positions you argue from — the opinions a reader could disagree with. A
|
||||||
|
position that no one could disagree with is a description, not a stance, and does not
|
||||||
|
belong here.]
|
||||||
|
|
||||||
|
1. [Position — stated as a claim, not a topic]
|
||||||
|
2. [Position]
|
||||||
|
3. [Position]
|
||||||
|
|
||||||
|
_Evidence:_ [posts or editions where you took this position explicitly]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How I differ
|
||||||
|
|
||||||
|
[Why this account and not the generic account on the same topic. Concrete: the lived
|
||||||
|
material only you have — the named cases, the real numbers, the work you actually did.
|
||||||
|
"More practical" and "more authentic" are not differentiators; the specific thing you can
|
||||||
|
say that a competitor cannot is.]
|
||||||
|
|
||||||
|
_Evidence:_ [specifics-bank entries, case studies, frameworks you authored]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Provenance
|
||||||
|
|
||||||
|
- **Generated:** [YYYY-MM-DD] by `/linkedin:setup` Step 3g
|
||||||
|
- **Sources:** `brain/profile.md` (consolidated) · `brain/operations.md` (anchor) ·
|
||||||
|
`profile/user-profile.md` (declared) · published corpus
|
||||||
|
- **Operator edits since generation:** [none / describe]
|
||||||
|
|
@ -255,7 +255,7 @@ Before testing, ensure:
|
||||||
3. Agent should ask which post to monitor
|
3. Agent should ask which post to monitor
|
||||||
4. Provide sample metrics: 500 impressions, 15 reactions, 3 comments, 1 repost
|
4. Provide sample metrics: 500 impressions, 15 reactions, 3 comments, 1 repost
|
||||||
5. Agent should identify the current phase and provide benchmarks
|
5. Agent should identify the current phase and provide benchmarks
|
||||||
**Expected:** Structured output with metrics snapshot, velocity score, anomaly detection, and recommended actions
|
**Expected:** Structured output with metrics snapshot, a baseline reading (above/within/below band, or an explicit "no verdict — N post(s), 5 required" refusal), anomaly detection, and recommended actions. No composite score.
|
||||||
**Validates:** Agent file loads correctly, context loading works, output format matches spec
|
**Validates:** Agent file loads correctly, context loading works, output format matches spec
|
||||||
|
|
||||||
### Test 16: Post-Feedback Monitor — Anomaly Detection
|
### Test 16: Post-Feedback Monitor — Anomaly Detection
|
||||||
|
|
|
||||||
175
docs/research-engine/brief-re-r3f.md
Normal file
175
docs/research-engine/brief-re-r3f.md
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
# Brief — RE-R3f: unattended AI discovery (`--headless`, slice e)
|
||||||
|
|
||||||
|
> **Slice:** RE-R3f (research-engine rung-2, R3 slice **(e)** in the operator's `(a)→(c)→(b)→(d)→(e)`
|
||||||
|
> sequence — the doc is numbered r3f by creation order, the concept is slice (e)). Closes the
|
||||||
|
> production gap named in `docs/research-engine-concepts.local.md` §5: RE-R3c/RE-R3e built a
|
||||||
|
> **deterministic** headless entry (`schedule`/`run-daily.sh`) that re-renders the brief on a
|
||||||
|
> schedule with no new discovery — a near-no-op without a prior human-run poll. RE-R3f makes the
|
||||||
|
> **actual discovery pass** — `/linkedin:trends`'s poll → score → capture → brief loop, including
|
||||||
|
> the `trend-spotter` agent's web research — safe to fire with no operator present.
|
||||||
|
> **Origin:** Maskinrommet production feedback, register row **MR-F5** — Sunday-morning discovery
|
||||||
|
> needs to run without the operator present. Elicited via `CLAUDE.local.md`'s feedback-intake
|
||||||
|
> protocol; validated against live plugin structure before any code/prose was touched.
|
||||||
|
> **The gate this slice opens:** `docs/research-engine-concepts.local.md` §5 recorded a 2026-06-24
|
||||||
|
> operator decision that slice (e) sits behind an **explicit re-evaluation gate against product
|
||||||
|
> maturity (v1.0.0)** — research is "rung-2." The plugin is at v0.8.0 (CLAUDE.md header); the gate
|
||||||
|
> had not been re-evaluated. Surfaced to the operator 2026-08-10 as a real conflict, not silently
|
||||||
|
> built around or silently deferred; the operator opened the gate explicitly (AskUserQuestion,
|
||||||
|
> "Open the gate now") after seeing the trade-off stated plainly.
|
||||||
|
> **Architectural decision — CONFIRMED (operator, AskUserQuestion 2026-08-10):** Desktop Scheduled
|
||||||
|
> Tasks (Claude Code's own first-party local scheduler) is the trigger mechanism, **not** a
|
||||||
|
> hand-rolled cron/launchd wrapper mirroring RE-R3c. A `-p`-mode `claude` invocation cannot be
|
||||||
|
> replicated by a shell script (it needs a full agent turn, incl. the `trend-spotter` subagent), and
|
||||||
|
> Claude Code's own headless docs state a hand-rolled `cron` + `claude -p` wrapper loses
|
||||||
|
> observability/session-resumption/error-recovery relative to the first-party mechanism. The recipe
|
||||||
|
> for the fallback (hand-rolled `claude -p`) path is documented for portability, but not built as
|
||||||
|
> repo code.
|
||||||
|
|
||||||
|
## 1. The gap — grounded in code and docs
|
||||||
|
|
||||||
|
- **`run-daily.sh` (RE-R3c) is deterministic-only by design** (`run-daily.sh:1-8`, `docs/
|
||||||
|
research-engine/brief-re-r3c.md` C1): it re-renders the brief from the **current** store — no
|
||||||
|
poll, no capture. Its own comment names the gap: *"The (e) slice will insert a pre-brief AI
|
||||||
|
capture step here … before the `brief` call below."* Nothing has inserted it.
|
||||||
|
- **The attended pass blocks on operator input in two places.** `commands/trends.md` Step 1 asks
|
||||||
|
the user for pillars when `${DATA}/profile/user-profile.md` is missing; Step 5 (Triage) calls
|
||||||
|
`AskUserQuestion` up to 4× per batch. Fired unattended, either would hang or (in Claude Code's
|
||||||
|
`dontAsk` permission mode, the correct mode for an unattended run) abort the run — `AskUserQuestion`
|
||||||
|
is auto-denied unconditionally in that mode regardless of any `--allowedTools` entry (verified
|
||||||
|
against Claude Code's `permission-modes` docs, 2026-08-10).
|
||||||
|
- **No documented recipe for firing a plugin command unattended existed in this repo.** RE-R3c's
|
||||||
|
`schedule` CLI verb only ever targets the deterministic `brief` subcommand
|
||||||
|
(`run-daily.sh:33`, `cli.ts` `schedule` branch) — it has no path to a full `claude` agent turn.
|
||||||
|
- **Exit-code trust was unstated.** Claude Code's headless docs are explicit that a `-p` run's
|
||||||
|
process exit code reflects the CLI process, not the pass's semantic outcome (a tool denial or
|
||||||
|
auth failure can still print to stdout without a non-zero exit). No existing doc in this repo
|
||||||
|
told a scheduled caller what to check instead.
|
||||||
|
|
||||||
|
## 2. Scope — what is IN (RE-R3f)
|
||||||
|
|
||||||
|
### `commands/trends.md` (EDIT) — the `--headless` contract
|
||||||
|
|
||||||
|
- New flag `--headless`, documented in the Step 0 flags table + a new **Step 0.5** section stating
|
||||||
|
the contract explicitly (so it reads as a rule, not something buried three steps down):
|
||||||
|
- `--demand --headless` together → refused (one line, exit; never silently falls back to
|
||||||
|
supply-side discovery).
|
||||||
|
- Step 1 missing profile → print a one-line error and stop; never ask. The source-list fallback
|
||||||
|
(shipped defaults) is unaffected — it already needs no operator input.
|
||||||
|
- Step 5 (Triage) → skipped unconditionally. Every kept candidate stays `pending`; the next
|
||||||
|
attended session (or the next morning brief) surfaces it for a human to resolve.
|
||||||
|
- Steps 2–4 and 6 (poll, score, capture, render, `.last-run` marker) run exactly as attended —
|
||||||
|
`--headless` changes who can be asked, never what gets persisted.
|
||||||
|
- States plainly: ground success in the same artifacts Step 3 already verifies (`CLI status
|
||||||
|
--json` capture delta, today's dated brief file) — never in this command's own exit code or
|
||||||
|
prose.
|
||||||
|
- No frontmatter change (`allowed-tools` already lists `AskUserQuestion` for the attended path;
|
||||||
|
leaving it is harmless belt-and-suspenders — `dontAsk` mode denies it unconditionally regardless
|
||||||
|
of what a command's frontmatter requests).
|
||||||
|
|
||||||
|
### `scripts/trends/README.md` (EDIT) — the unattended-discovery section
|
||||||
|
|
||||||
|
- A new `## Unattended AI discovery — --headless (RE-R3f, slice e)` section, positioned after the
|
||||||
|
existing RE-R3c section, explicit that this is a **different mechanism** (a full agent turn, not
|
||||||
|
a bash/cron wrapper): Desktop Scheduled Tasks as the recommended trigger; the verified `claude -p`
|
||||||
|
recipe (`--bare --plugin-dir … --permission-mode dontAsk --allowedTools "Read,Bash,Task,WebSearch,
|
||||||
|
WebFetch" --output-format json`) as the documented fallback for a machine without Desktop
|
||||||
|
Scheduled Tasks; the `ANTHROPIC_API_KEY` requirement under `--bare`; the background-subagent wait
|
||||||
|
ceiling (`CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS`, default 10 min) since the research subagent can
|
||||||
|
run long; the artifact-grounded success check (never the exit code); the `total_cost_usd` cost
|
||||||
|
visibility note from `--output-format json`.
|
||||||
|
|
||||||
|
### `CLAUDE.md` (EDIT) — command table
|
||||||
|
|
||||||
|
- `/linkedin:trends` row gains a `--headless` mention (one clause, no restructuring).
|
||||||
|
|
||||||
|
### `CHANGELOG.md` (EDIT)
|
||||||
|
|
||||||
|
- `[Unreleased]` entry: MR-F5 landed, gate opened, scope summary.
|
||||||
|
|
||||||
|
### Docs (this file + `plan-re-r3f.md`, NEW, tracked)
|
||||||
|
|
||||||
|
## 3. Non-goals — what is OUT (deferred)
|
||||||
|
|
||||||
|
- **A hand-rolled cron/launchd wrapper invoking `claude -p`** — OUT as repo code. The recipe is
|
||||||
|
documented (README) for portability to a machine without Desktop Scheduled Tasks, but nothing in
|
||||||
|
this repo shells out to `claude` itself; Desktop Scheduled Tasks is the built mechanism.
|
||||||
|
- **`schedule.ts` / `run-daily.sh` changes** — OUT. RE-R3c's deterministic brief-only wrapper is
|
||||||
|
untouched and remains valid for a plain "re-render today's brief" use case distinct from
|
||||||
|
discovery.
|
||||||
|
- **A cost cap / per-run candidate ceiling enforced in code** — OUT. `trend-spotter`'s existing
|
||||||
|
guidance (Anti-Patterns: "max 2-3 trend posts per week") is advisory prose, not a mechanism;
|
||||||
|
enforcing a hard cap on an unattended AI research run is a real design question, deferred rather
|
||||||
|
than guessed at under this slice. `total_cost_usd` visibility (README) is the mitigant landed now.
|
||||||
|
- **`--demand --headless`** — explicitly refused, not built. The demand-sweep's judgment calls are
|
||||||
|
a later slice's problem, if ever.
|
||||||
|
- **A new store field / schema bump** — none. `SCHEMA_VERSION` (4) / `BRIEF_SCHEMA_VERSION` (2)
|
||||||
|
untouched — `--headless` changes command-level control flow only.
|
||||||
|
- **A new agent, a new CLI verb, a new source/test file** — none. RE-R3f is a prompt-contract EDIT
|
||||||
|
plus documentation; there is no new TypeScript/bash to unit-test.
|
||||||
|
|
||||||
|
## 4. Boundaries / invariants (must hold)
|
||||||
|
|
||||||
|
- **`--headless` never changes what gets persisted**, only who can be asked. A headless run and an
|
||||||
|
attended run that both clear Step 1 must capture/brief identically (Step 5's skip is the only
|
||||||
|
behavioral divergence, and it is additive-safe: `pending` candidates are exactly what an attended
|
||||||
|
triage would leave un-triaged if the operator picked "Leave" on all of them).
|
||||||
|
- **No silent degrade.** `--demand --headless` refuses rather than silently running supply-side
|
||||||
|
discovery instead; a missing profile refuses rather than silently scoring against no pillars.
|
||||||
|
- **Success is artifact-grounded, never prose-grounded** — documented explicitly in both the
|
||||||
|
command file and the README, because Claude Code's own docs establish the exit-code trap is real.
|
||||||
|
- **Domain-general** — no vendor/sector token in any edit (Section 17 de-niche stays green); the
|
||||||
|
README's `claude -p` recipe names no user-specific path (the plugin-dir is a placeholder the
|
||||||
|
operator fills at their own install).
|
||||||
|
- **Counts unchanged** — no new command (`/linkedin:trends` gains a flag, not a sibling command),
|
||||||
|
no new agent, no new reference file. Recounted live at land.
|
||||||
|
|
||||||
|
## 5. Success criteria (verifiable — no test suite; this is a prompt/doc change)
|
||||||
|
|
||||||
|
- **SC1** — `commands/trends.md` documents `--headless` in the flags table AND a dedicated Step 0.5
|
||||||
|
contract section; `grep -c '\-\-headless' commands/trends.md` ≥ 5 (table + Step 0.5 + the three
|
||||||
|
step-level cross-references).
|
||||||
|
- **SC2** — Step 1's missing-profile branch explicitly forks on `--headless` (fail vs. ask); Step
|
||||||
|
2D explicitly refuses `--demand --headless`; Step 5's header states the `--headless` skip
|
||||||
|
condition inline (not only in Step 0.5) — each verified by direct read of the edited sections.
|
||||||
|
- **SC3** — `scripts/trends/README.md` documents the Desktop Scheduled Tasks recommendation, the
|
||||||
|
fallback `claude -p` recipe with all four required flags (`--bare`, `--plugin-dir`,
|
||||||
|
`--permission-mode dontAsk`, `--allowedTools`), the `ANTHROPIC_API_KEY` requirement, and the
|
||||||
|
artifact-grounded success check — each present as literal text, verified by grep.
|
||||||
|
- **SC4** — `CLAUDE.md`'s `/linkedin:trends` row mentions `--headless`; `CHANGELOG.md` `[Unreleased]`
|
||||||
|
names MR-F5.
|
||||||
|
- **SC5** — Section 17 de-niche guard stays green (no vendor/sector token introduced); command/
|
||||||
|
agent/reference counts unchanged (recounted live).
|
||||||
|
- **SC6 (explicitly NOT claimed)** — an actual unattended end-to-end run. Per the
|
||||||
|
`plugin-runtime-is-consumer-side` constraint, a plugin command does not resolve in this dev-repo
|
||||||
|
session; the real Sunday-discovery proof happens consumer-side, after the operator updates +
|
||||||
|
reloads + configures a Desktop Scheduled Task. This brief does not claim that proof; it claims the
|
||||||
|
command-file contract and the documentation are correct and internally consistent.
|
||||||
|
|
||||||
|
## 6. Verification
|
||||||
|
|
||||||
|
**This session (dev-repo):** direct read-through of every edited section against SC1–SC5;
|
||||||
|
`bash scripts/test-runner.sh` for regression sanity (RE-R3f touches no source/test file the gate
|
||||||
|
tracks, so `FAIL=0` and every floor unchanged is the expectation, not a new floor); Section 17
|
||||||
|
de-niche green; counts recounted live.
|
||||||
|
**Consumer-side (deferred, not gated on this session):** update + reload the plugin, run
|
||||||
|
`/linkedin:trends --headless` once attended-but-silent (verify it does not prompt), then configure
|
||||||
|
a Desktop Scheduled Task and confirm a real unattended Sunday pass produces a capture delta + a
|
||||||
|
dated brief.
|
||||||
|
|
||||||
|
## 7. Open questions for the go-gate
|
||||||
|
|
||||||
|
Two decisions are **CONFIRMED** (operator, AskUserQuestion 2026-08-10): the gate is opened now
|
||||||
|
(production need outweighs waiting for v1.0.0); Desktop Scheduled Tasks is the mechanism, not a
|
||||||
|
hand-rolled wrapper (with the fallback recipe documented, not built). Residual decisions, baked to
|
||||||
|
the recommended default — confirm or redirect:
|
||||||
|
- **D1 — `--headless` default mode is long-form (inherits the command's own default)?** YES (rec).
|
||||||
|
Unattended discovery is for the Sunday chronicle/newsletter pipeline per MR-F5's own framing; an
|
||||||
|
operator wanting kortform passes `--mode kortform --headless` explicitly. Re-open only if the
|
||||||
|
unattended default should differ from the attended default.
|
||||||
|
- **D2 — no hard cost/candidate cap enforced in code this slice?** YES (rec, per §3 non-goal) — cost
|
||||||
|
visibility (`total_cost_usd`) is landed; a cap is a real question deferred rather than guessed.
|
||||||
|
Re-open only if a cap should land now.
|
||||||
|
- **D3 — the fallback `claude -p` recipe is documented but not wrapped in a repo script?** YES
|
||||||
|
(rec). Building an unused wrapper for a path the operator isn't using (Desktop Scheduled Tasks
|
||||||
|
covers the real machine) would be speculative code. Re-open only if the fallback path is the one
|
||||||
|
actually needed.
|
||||||
113
docs/research-engine/plan-re-r3f.md
Normal file
113
docs/research-engine/plan-re-r3f.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Plan — RE-R3f: unattended AI discovery (`--headless`, slice e)
|
||||||
|
|
||||||
|
> **Brief:** `docs/research-engine/brief-re-r3f.md`. **Slice:** RE-R3f (research-engine rung-2 — R3
|
||||||
|
> slice (e), the AI-capture unattended-run slice). No new source/test file: this is a prompt-file
|
||||||
|
> (`commands/trends.md`) contract change plus documentation. There is nothing here for
|
||||||
|
> `scripts/test-runner.sh` to gain a new floor for — the plan's "TDD" is a direct-read verification
|
||||||
|
> against the brief's SC1–SC5, not a unit-test suite.
|
||||||
|
|
||||||
|
## Files touched (exhaustive)
|
||||||
|
|
||||||
|
| File | Change | SC |
|
||||||
|
|---|---|---|
|
||||||
|
| `commands/trends.md` | EDIT — `--headless` flag row + new **Step 0.5** contract section; Step 1's missing-profile branch forks on `--headless`; Step 2D refuses `--demand --headless`; Step 5 header states the unconditional skip | SC1, SC2 |
|
||||||
|
| `scripts/trends/README.md` | EDIT — new `## Unattended AI discovery — --headless (RE-R3f, slice e)` section: Desktop Scheduled Tasks (recommended) + the fallback `claude -p` recipe + `ANTHROPIC_API_KEY` + wait-ceiling + artifact-grounded success check + cost visibility | SC3 |
|
||||||
|
| `CLAUDE.md` | EDIT — `/linkedin:trends` command-table row gains a `--headless` clause | SC4 |
|
||||||
|
| `CHANGELOG.md` | EDIT — `[Unreleased]` entry naming MR-F5 | SC4 |
|
||||||
|
| `docs/research-engine/{brief,plan}-re-r3f.md` | NEW — tracked slice docs (mirrors R3c/R3e/etc.) | — |
|
||||||
|
| `STATE.md` | EDIT at land — Telling-block reconcile (no new counts; note MR-F5 landed) | — |
|
||||||
|
|
||||||
|
**Not touched (scope fence):** `scripts/trends/src/*.ts` (no code — Desktop Scheduled Tasks is the
|
||||||
|
mechanism, not a repo-owned wrapper); `scripts/trends/run-daily.sh` (RE-R3c stays deterministic-only,
|
||||||
|
untouched); `agents/trend-spotter.md` (the agent's own contract — poll/score — is unchanged by who
|
||||||
|
fires it; `--headless` is a command-level concern); `config/*`; `references/*`; command/agent counts
|
||||||
|
(no new command, no new agent — `--headless` is a flag on an existing command); `scripts/test-runner.sh`
|
||||||
|
(no new floor — nothing here is `tsx`-tested source).
|
||||||
|
|
||||||
|
## Step 1 — `commands/trends.md`: the `--headless` contract
|
||||||
|
|
||||||
|
Edited already this session (verify against brief SC1/SC2 by direct read):
|
||||||
|
1. Step 0 flags table gains the `--headless` row.
|
||||||
|
2. New **Step 0.5** section states the full contract: `--demand --headless` refusal, Step 1
|
||||||
|
fail-fast, Step 5 unconditional skip, Steps 2–4/6 unchanged, artifact-grounded success.
|
||||||
|
3. Step 1's pillar-loading paragraph forks explicitly: `--headless` → error + stop; else → ask.
|
||||||
|
4. Step 2D gains the refusal clause for `--demand --headless` before its existing demand-sweep
|
||||||
|
delegation text.
|
||||||
|
5. Step 5's header and lead line state the `--headless` skip condition (not buried only in Step 0.5
|
||||||
|
— a reader working step-by-step must see it at the step itself).
|
||||||
|
|
||||||
|
**Verification:** `grep -n -- '--headless' commands/trends.md` shows the table row + Step 0.5 + the
|
||||||
|
three per-step cross-references (≥5 matches, SC1); re-read Steps 1/2D/5 end-to-end to confirm each
|
||||||
|
fork is unambiguous (no branch left to model inference under ambiguity, SC2).
|
||||||
|
|
||||||
|
## Step 2 — `scripts/trends/README.md`: the unattended-discovery section
|
||||||
|
|
||||||
|
Edited already this session. New section placed after the existing RE-R3c ("Autonomous trigger +
|
||||||
|
headless entry") section, explicit that RE-R3f is a **different mechanism** (full agent turn, not a
|
||||||
|
bash wrapper): Desktop Scheduled Tasks recommended; the fallback `claude -p …` recipe with all four
|
||||||
|
required flags; `ANTHROPIC_API_KEY` under `--bare`; `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS`; the
|
||||||
|
artifact-grounded success check (never trust `$?`/prose); `total_cost_usd` cost-visibility note.
|
||||||
|
|
||||||
|
**Verification:** `grep -n -- '--bare\|--permission-mode dontAsk\|--allowedTools\|ANTHROPIC_API_KEY\|Desktop Scheduled Tasks' scripts/trends/README.md` — each present (SC3).
|
||||||
|
|
||||||
|
## Step 3 — `CLAUDE.md` + `CHANGELOG.md`
|
||||||
|
|
||||||
|
- `CLAUDE.md`: append a `--headless` clause to the existing `/linkedin:trends` row (one line, no
|
||||||
|
restructuring of the table).
|
||||||
|
- `CHANGELOG.md`: `[Unreleased]` gains an entry — MR-F5 landed, the gate-opening decision named, the
|
||||||
|
scope (command contract + docs, no new code), matching this repo's existing changelog voice (see
|
||||||
|
the 0.8.0 entries for tone/format).
|
||||||
|
|
||||||
|
**Verification:** `grep -n -- '--headless' CLAUDE.md`; `grep -n 'MR-F5' CHANGELOG.md`.
|
||||||
|
|
||||||
|
## Step 4 — gate + regression sanity
|
||||||
|
|
||||||
|
`bash scripts/test-runner.sh` → `FAIL=0`, every existing floor unchanged (no new source/test file to
|
||||||
|
raise a floor for); Section 17 de-niche green (no vendor/sector token in any edit — the README's
|
||||||
|
`claude -p` recipe names a placeholder plugin-dir path, never an operator-specific one);
|
||||||
|
command/agent/reference counts unchanged, recounted live (`--headless` is a flag, not a new
|
||||||
|
surface).
|
||||||
|
|
||||||
|
## Step 5 — land
|
||||||
|
|
||||||
|
Recount counts live; reconcile `STATE.md`'s Telling block (no count change; note MR-F5 landed at
|
||||||
|
commit); quit the feedback register kvittering per `CLAUDE.local.md`'s protocol ("landed
|
||||||
|
feedback:MR-F5 @ `<commit>`" — Maskinrommet flips 🔵→🟢 on relay, never written here). Commit order:
|
||||||
|
**(1)** docs commit — `docs/research-engine/{brief,plan}-re-r3f.md` (tracked, no suffix); **(2)** one
|
||||||
|
code^H^H^H^Hprompt commit — `commands/trends.md` + `scripts/trends/README.md` + `CLAUDE.md` +
|
||||||
|
`CHANGELOG.md` (there is no code in this slice — "code commit" here means the prompt-file + docs
|
||||||
|
edit, landed together since it is one coherent contract change). Push after operator go (public
|
||||||
|
`open/` remote — confirm before push per the repo's own push-topology memory).
|
||||||
|
|
||||||
|
## Verification (testable)
|
||||||
|
|
||||||
|
| SC | Check | Command | Expected |
|
||||||
|
|---|---|---|---|
|
||||||
|
| SC1 | `--headless` documented | `grep -c -- '--headless' commands/trends.md` | ≥ 5 |
|
||||||
|
| SC2 | Per-step forks unambiguous | direct read of Steps 1, 2D, 5 | each states the `--headless` branch explicitly, no inference required |
|
||||||
|
| SC3 | README recipe complete | `grep` for the four flags + `ANTHROPIC_API_KEY` + `Desktop Scheduled Tasks` | all present |
|
||||||
|
| SC4 | CLAUDE.md + CHANGELOG updated | `grep -- '--headless' CLAUDE.md`; `grep 'MR-F5' CHANGELOG.md` | both present |
|
||||||
|
| SC5 | Gate green, counts unchanged | `bash scripts/test-runner.sh` | `FAIL=0`; all floors unchanged; Section 17 green |
|
||||||
|
| SC6 | (explicitly deferred) | — | consumer-side unattended proof is NOT claimed this session (`plugin-runtime-is-consumer-side`) |
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- **R1 — a scheduled task on the operator's actual machine can't reach Desktop Scheduled Tasks
|
||||||
|
(older Claude Code version, disabled feature).** *Mitigated:* the fallback `claude -p` recipe is
|
||||||
|
fully documented in the README, so the operator has a working path either way; not built as repo
|
||||||
|
code because building an unused wrapper would be speculative (brief §3 D3).
|
||||||
|
- **R2 — the `--headless` contract is a prompt, so nothing enforces it mechanically; a future edit
|
||||||
|
to `commands/trends.md` could silently reintroduce a blocking `AskUserQuestion` on the headless
|
||||||
|
path.** *Accepted, stated honestly:* there is no code-level guard here (unlike RE-R3c's
|
||||||
|
gate-tested `schedule.ts`). Claude Code's own `dontAsk` permission mode is the real backstop —
|
||||||
|
`AskUserQuestion` is auto-denied unconditionally regardless of what the command file says — so a
|
||||||
|
regression fails safe (the run aborts) rather than silently hanging on a phantom prompt.
|
||||||
|
- **R3 — unattended weekly AI research has a real, currently-uncapped per-run cost.** *Mitigated by
|
||||||
|
visibility, not a cap* (brief §3 non-goal, D2): `--output-format json`'s `total_cost_usd` is
|
||||||
|
documented as the thing to log; enforcing a hard ceiling is deferred as a real design question
|
||||||
|
rather than an arbitrary number picked under this slice.
|
||||||
|
- **R4 — the gate-reopening decision itself is contestable** (research was explicitly deferred to
|
||||||
|
v1.0.0 by a prior operator decision). *Mitigated:* the conflict was surfaced explicitly
|
||||||
|
(AskUserQuestion, 2026-08-10) rather than silently built around or silently deferred; the operator
|
||||||
|
chose to open it with the trade-off stated plainly. Recorded in the brief's header for future
|
||||||
|
sessions reading this slice cold.
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Bygge-brief (Voyage-input) — LTL fullspektrum-innholdsmotor → v2.0.0
|
# Bygge-brief (Voyage-input) — LTL fullspektrum-innholdsmotor → v2.0.0
|
||||||
|
|
||||||
> **Formål:** Input til `/trekplan` (Voyage-pluginen). Dette er byggingen av LTL-pluginen, drevet som et Voyage-prosjekt: `/trekplan` produserer en kjørbar plan med per-steg Manifests, `/trekexecute --fg` + `/trekcontinue` driver sesjonene, `/trekreview` er release-gate.
|
> **Formål:** Input til `/trekplan` (Voyage-pluginen). Dette er byggingen av LTL-pluginen, drevet som et Voyage-prosjekt: `/trekplan` produserer en kjørbar plan med per-steg Manifests, `/trekexecute --fg` + `/trekcontinue` driver sesjonene, `/trekreview` er release-gate.
|
||||||
> **Detaljert referanse:** [`plan-fullspektrum-innholdsmotor.md`](./plan-fullspektrum-innholdsmotor.md) (samme mappe) — den hardnede planen med §0 orientering, §4 renovering, §5 langform, §6 byggeklosser, §7 render, §10 DoD. **Les den; den er fasit for hva som skal bygges.**
|
> **Detaljert referanse:** [`plan-fullspektrum-innholdsmotor.md`](../plan-fullspektrum-innholdsmotor.md) (i `docs/`) — den hardnede planen med §0 orientering, §4 renovering, §5 langform, §6 byggeklosser, §7 render, §10 DoD. **Les den; den er fasit for hva som skal bygges.**
|
||||||
> **Opprinnelig retningsbrief:** [`brief-fullspektrum-innholdsmotor.md`](./brief-fullspektrum-innholdsmotor.md).
|
> **Opprinnelig retningsbrief:** [`brief-fullspektrum-innholdsmotor.md`](../brief-fullspektrum-innholdsmotor.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ writeState(content => updateFollowerCount(content, {
|
||||||
|
|
||||||
- **Quality Check**: Has content been reviewed against quality scorecard? Hook 110-140 chars, 1,200-1,800 chars total, authentic tone, no external links.
|
- **Quality Check**: Has content been reviewed against quality scorecard? Hook 110-140 chars, 1,200-1,800 chars total, authentic tone, no external links.
|
||||||
- **5x5x5 Engagement**: Before posting, complete 15-20 min pre-posting engagement — 5 people with overlapping audiences, find their recent posts, write 5 thoughtful comments (15+ words each).
|
- **5x5x5 Engagement**: Before posting, complete 15-20 min pre-posting engagement — 5 people with overlapping audiences, find their recent posts, write 5 thoughtful comments (15+ words each).
|
||||||
- **First-Hour Plan**: Respond within 5 minutes to first comments. Add value in responses. Target 15+ engagements in first hour.
|
- **First-Hour Plan**: Respond within 5 minutes to first comments. Add value in responses. Keep the first hour active — early engagement unlocks broader distribution (directional; no fixed threshold).
|
||||||
- **Posting Time**: Post when target audience is most active.
|
- **Posting Time**: Post when target audience is most active.
|
||||||
|
|
||||||
**3. Queue Status Check**
|
**3. Queue Status Check**
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,23 @@ The defensible spine is the **order**, not the multiplier:
|
||||||
> The personal-profile **per-post baseline** (~2.0–2.6%) is a *different denominator* from
|
> The personal-profile **per-post baseline** (~2.0–2.6%) is a *different denominator* from
|
||||||
> a format benchmark — never present an account baseline as a carousel rate.
|
> a format benchmark — never present an account baseline as a carousel rate.
|
||||||
|
|
||||||
|
## Post length (text posts)
|
||||||
|
|
||||||
|
| Claim | Statement | Source | Confidence |
|
||||||
|
|-------|-----------|--------|------------|
|
||||||
|
| Engagement optimum | Median engagement peaks in the **1,301–2,500 character** band (2.61–2.67%, against 2.10% below 400 chars); 2,501–3,000 falls back slightly. **Single vendor, personal profiles, one six-month window** — the defensible part is the direction (substance beats soundbite), not the boundaries. | AuthoredUp (372,126 posts, Sep 2025–Feb 2026) | medium |
|
||||||
|
|
||||||
|
> **On the plugin's own 1,200–1,800 standard band:** it overlaps the measured optimum from
|
||||||
|
> 1,301 up, with its bottom ~100 characters below it, and its ceiling 700 short. Read
|
||||||
|
> 1,301–2,500 as evidence that the shipped ceiling is **conservative, not wrong** — one
|
||||||
|
> vendor's medians over one window is not enough to move an SSOT that every gate enforces
|
||||||
|
> (`hooks/prompts/content-quality-gate.md`). Test a longer post against your own baseline
|
||||||
|
> before concluding anything.
|
||||||
|
>
|
||||||
|
> **Two AuthoredUp sample sizes appear in this file** — 621K (NLP-quality-scored engagement
|
||||||
|
> study) and 372K (this length study). Different studies, different windows; not a
|
||||||
|
> transcription error.
|
||||||
|
|
||||||
## External links (in post body)
|
## External links (in post body)
|
||||||
|
|
||||||
| Claim | Statement | Source | Confidence |
|
| Claim | Statement | Source | Confidence |
|
||||||
|
|
@ -188,7 +205,7 @@ source.)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Last updated: 2026-07-17 (GR-model correction). Maintained as the single canonical
|
*Last updated: 2026-07-31 (post-length section added). Maintained as the single canonical
|
||||||
algorithm statement; cite, do not restate.*
|
algorithm statement; cite, do not restate.*
|
||||||
|
|
||||||
*Sources (per-claim quality/confidence noted inline): LinkedIn Engineering — "Engineering
|
*Sources (per-claim quality/confidence noted inline): LinkedIn Engineering — "Engineering
|
||||||
|
|
@ -197,6 +214,7 @@ research paper, withdrawn 2025-08-23); LinkedIn Engineering — "Leveraging Dwel
|
||||||
AI (2025-08-11); Laura Lorenzetti, VP & Exec Editor (2026-05-19); Gyanda Sachdeva, VP
|
AI (2025-08-11); Laura Lorenzetti, VP & Exec Editor (2026-05-19); Gyanda Sachdeva, VP
|
||||||
Product (2026-02-16); Matt Navarra relaying LinkedIn Sr. Director Product (Aug 2025);
|
Product (2026-02-16); Matt Navarra relaying LinkedIn Sr. Director Product (Aug 2025);
|
||||||
Ordinal link-penalty study (900K, p<0.001); Socialinsider (1.3M); Buffer (2M+); Metricool
|
Ordinal link-penalty study (900K, p<0.001); Socialinsider (1.3M); Buffer (2M+); Metricool
|
||||||
(673K); AuthoredUp (621K, NLP-quality-scored); van der Blom Algorithm Insights 2025 (1.8M);
|
(673K); AuthoredUp (621K, NLP-quality-scored); AuthoredUp post-length study (372,126 posts,
|
||||||
|
Sep 2025–Feb 2026); van der Blom Algorithm Insights 2025 (1.8M);
|
||||||
Originality.ai (8,795 posts); Inc. (buzzword editorial). Full provenance: research brief
|
Originality.ai (8,795 posts); Inc. (buzzword editorial). Full provenance: research brief
|
||||||
`docs/remediation/research/01-linkedin-algorithm-signals.md`.*
|
`docs/remediation/research/01-linkedin-algorithm-signals.md`.*
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ After 10 experiments, you know YOUR audience better than any generic advice can
|
||||||
- Month 1: You're learning mechanics (baseline)
|
- Month 1: You're learning mechanics (baseline)
|
||||||
- Month 3: You understand YOUR patterns (above average)
|
- Month 3: You understand YOUR patterns (above average)
|
||||||
- Month 6: You're discovering insights from practice (exceptional)
|
- Month 6: You're discovering insights from practice (exceptional)
|
||||||
- Month 12: You're systematically generating unique perspectives (thought leader)
|
- Month 12: You're systematically generating unique perspectives (recognized expert)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ After establishing rapport:
|
||||||
|
|
||||||
**Time investment:** 3-5 hours (including creation and coordination)
|
**Time investment:** 3-5 hours (including creation and coordination)
|
||||||
**Reach:** Above baseline; deeper integration than a single co-post (no reliable multiplier)
|
**Reach:** Above baseline; deeper integration than a single co-post (no reliable multiplier)
|
||||||
**Benefit:** Creates reusable asset, positions both as thought leaders, deeper integration
|
**Benefit:** Creates reusable asset, positions both as authorities, deeper integration
|
||||||
|
|
||||||
**Execution:**
|
**Execution:**
|
||||||
- 1-2 calls to align on framework
|
- 1-2 calls to align on framework
|
||||||
|
|
|
||||||
|
|
@ -139,17 +139,17 @@ This structure creates positive response by opposing forces and compels the "see
|
||||||
**Hook (110-140 chars)**
|
**Hook (110-140 chars)**
|
||||||
→ Grab attention, create curiosity
|
→ Grab attention, create curiosity
|
||||||
|
|
||||||
**Context (200-300 chars)**
|
**Context (250-350 chars)**
|
||||||
→ Set up the situation/problem/observation
|
→ Set up the situation/problem/observation
|
||||||
→ Why should they care?
|
→ Why should they care?
|
||||||
→ What's at stake?
|
→ What's at stake?
|
||||||
|
|
||||||
**Insight/Argument (400-800 chars)**
|
**Insight/Argument (550-850 chars)**
|
||||||
→ Your main point
|
→ Your main point
|
||||||
→ Supporting evidence or logic
|
→ Supporting evidence or logic
|
||||||
→ This is the "meat" of the post
|
→ This is the "meat" of the post
|
||||||
|
|
||||||
**Implication (200-300 chars)**
|
**Implication (250-350 chars)**
|
||||||
→ What does this mean?
|
→ What does this mean?
|
||||||
→ Why does it matter?
|
→ Why does it matter?
|
||||||
→ Connect to bigger picture
|
→ Connect to bigger picture
|
||||||
|
|
@ -158,6 +158,10 @@ This structure creates positive response by opposing forces and compels the "see
|
||||||
→ What should the reader do/think?
|
→ What should the reader do/think?
|
||||||
→ Engagement prompt
|
→ Engagement prompt
|
||||||
|
|
||||||
|
*The five components sum to 1,210-1,790 characters — inside the 1,200-1,800 standard
|
||||||
|
band, so a draft that respects each component band passes the length gate by
|
||||||
|
construction.*
|
||||||
|
|
||||||
### The Narrative Arc (For Story-Based Posts)
|
### The Narrative Arc (For Story-Based Posts)
|
||||||
|
|
||||||
**Setup (200 chars)**
|
**Setup (200 chars)**
|
||||||
|
|
@ -183,48 +187,54 @@ This structure creates positive response by opposing forces and compels the "see
|
||||||
**CTA (50-100 chars)**
|
**CTA (50-100 chars)**
|
||||||
→ Engagement prompt
|
→ Engagement prompt
|
||||||
|
|
||||||
|
*Sum: 1,350-1,500 characters — inside the 1,200-1,800 standard band.*
|
||||||
|
|
||||||
### The Data-Driven Post (For Research/Statistics)
|
### The Data-Driven Post (For Research/Statistics)
|
||||||
|
|
||||||
**Stat Hook (100 chars)**
|
**Stat Hook (110-140 chars)**
|
||||||
→ Lead with the surprising number
|
→ Lead with the surprising number
|
||||||
|
|
||||||
**Context (200 chars)**
|
**Context (250-350 chars)**
|
||||||
→ Where this data comes from
|
→ Where this data comes from
|
||||||
→ Why it matters
|
→ Why it matters
|
||||||
|
|
||||||
**Breakdown (500-700 chars)**
|
**Breakdown (550-850 chars)**
|
||||||
→ What the data actually means
|
→ What the data actually means
|
||||||
→ Deeper analysis
|
→ Deeper analysis
|
||||||
→ Connect to reader's reality
|
→ Connect to reader's reality
|
||||||
|
|
||||||
**Action (200-300 chars)**
|
**Action (250-350 chars)**
|
||||||
→ What to do with this information
|
→ What to do with this information
|
||||||
→ Practical takeaways
|
→ Practical takeaways
|
||||||
|
|
||||||
**CTA (50-100 chars)**
|
**CTA (50-100 chars)**
|
||||||
→ Engagement prompt
|
→ Engagement prompt
|
||||||
|
|
||||||
|
*Sum: 1,210-1,790 characters — inside the 1,200-1,800 standard band.*
|
||||||
|
|
||||||
### The Contrarian Post (For Challenging Norms)
|
### The Contrarian Post (For Challenging Norms)
|
||||||
|
|
||||||
**Bold Claim Hook (110 chars)**
|
**Bold Claim Hook (110-140 chars)**
|
||||||
→ State the contrarian position clearly
|
→ State the contrarian position clearly
|
||||||
|
|
||||||
**Common Wisdom (200 chars)**
|
**Common Wisdom (250-350 chars)**
|
||||||
→ Acknowledge what "everyone" thinks
|
→ Acknowledge what "everyone" thinks
|
||||||
→ Show you understand the conventional view
|
→ Show you understand the conventional view
|
||||||
|
|
||||||
**The Challenge (400-600 chars)**
|
**The Challenge (550-850 chars)**
|
||||||
→ Why the common wisdom fails
|
→ Why the common wisdom fails
|
||||||
→ Evidence or logic for your position
|
→ Evidence or logic for your position
|
||||||
→ Personal experience or data
|
→ Personal experience or data
|
||||||
|
|
||||||
**Alternative View (300-400 chars)**
|
**Alternative View (250-350 chars)**
|
||||||
→ What we should do instead
|
→ What we should do instead
|
||||||
→ The better approach
|
→ The better approach
|
||||||
|
|
||||||
**CTA (50-100 chars)**
|
**CTA (50-100 chars)**
|
||||||
→ Invite discussion/disagreement
|
→ Invite discussion/disagreement
|
||||||
|
|
||||||
|
*Sum: 1,210-1,790 characters — inside the 1,200-1,800 standard band.*
|
||||||
|
|
||||||
## Call-to-Action Frameworks
|
## Call-to-Action Frameworks
|
||||||
|
|
||||||
CTAs should encourage engagement while feeling natural, not forced.
|
CTAs should encourage engagement while feeling natural, not forced.
|
||||||
|
|
@ -346,7 +356,7 @@ Not all engagement is equal. The defensible spine is the **order**, not a fixed
|
||||||
**Key insight:** One save or substantive comment is worth more than many reactions. Focus on content people want to save and share, and cultivate genuine substantive comments. See `references/algorithm-signals-reference.md` (cite, don't restate magnitudes).
|
**Key insight:** One save or substantive comment is worth more than many reactions. Focus on content people want to save and share, and cultivate genuine substantive comments. See `references/algorithm-signals-reference.md` (cite, don't restate magnitudes).
|
||||||
|
|
||||||
### First Hour Critical
|
### First Hour Critical
|
||||||
- Aim for 15+ engagements in first 60 minutes
|
- Keep the first 60 minutes active — strong early engagement unlocks broader distribution (directional; no published threshold, so measure against your own first hours rather than a fixed count)
|
||||||
- Respond quickly to early comments (a 30-minute response tends to earn more follow-up comments — figure unverified)
|
- Respond quickly to early comments (a 30-minute response tends to earn more follow-up comments — figure unverified)
|
||||||
- Seed engagement by notifying key connections
|
- Seed engagement by notifying key connections
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,26 @@ External links in the post body correlate with lower reach (correlational, ~38%
|
||||||
- Lets you add context that didn't fit the post's character limit
|
- Lets you add context that didn't fit the post's character limit
|
||||||
- Creates a natural CTA without cluttering the main post
|
- Creates a natural CTA without cluttering the main post
|
||||||
|
|
||||||
|
### What this file does *not* claim
|
||||||
|
|
||||||
|
The canonical reference rates the first-comment tactic **"neither a magic fix nor a
|
||||||
|
confirmed penalty — contested"**, at *low* confidence — practitioner heuristic, no large-N
|
||||||
|
primary source (`references/algorithm-signals-reference.md`). Nothing below upgrades that.
|
||||||
|
Specifically, this plugin does **not** assert:
|
||||||
|
|
||||||
|
- that a link in a comment carries a measured reach cost (no primary source either way)
|
||||||
|
- that author comments are auto-pinned or auto-promoted (they are not — pinning is manual)
|
||||||
|
- any percentage figure for what a first comment gains or costs you
|
||||||
|
|
||||||
|
Treat every timing and placement rule here as a hypothesis to test against your own
|
||||||
|
baseline, not a mechanic to optimize against. The one durable claim is the body-link
|
||||||
|
correlation itself, and even that is a correlation.
|
||||||
|
|
||||||
## Timing Strategy
|
## Timing Strategy
|
||||||
|
|
||||||
### Immediate (within 60 seconds)
|
### Immediate (within 60 seconds)
|
||||||
**Best for:** Link-sharing, resource lists, CTA
|
**Best for:** Link-sharing, resource lists, CTA
|
||||||
**Why:** Ensures the comment appears at the top before others comment. LinkedIn treats author comments as pinned by default when posted first.
|
**Why:** Being first gives the comment the best odds of sitting at the top before others arrive. **There is no automatic pinning** — LinkedIn documents pinning as an explicit action the post author takes from the comment's three-dot menu (LinkedIn Help, a524166), and default comment ordering is algorithmic ("Most relevant"), not chronological. If the comment must stay on top, pin it yourself.
|
||||||
|
|
||||||
### Delayed (15-30 minutes)
|
### Delayed (15-30 minutes)
|
||||||
**Best for:** Engagement boost, conversation starter, hot take
|
**Best for:** Engagement boost, conversation starter, hot take
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ The defensible **ordering** of engagement signals — **saves > shares > quality
|
||||||
**Used in:** `references/algorithm-signals-reference.md`, `references/engagement-frameworks.md`
|
**Used in:** `references/algorithm-signals-reference.md`, `references/engagement-frameworks.md`
|
||||||
|
|
||||||
### Engagement Velocity
|
### Engagement Velocity
|
||||||
Speed of engagement accumulation in the first hour after posting. 15+ engagements in the first hour unlocks Stage 3 distribution. Monitored at 5/15/30/60/90-minute intervals.
|
Speed of engagement accumulation in the first hour after posting. Strong early engagement unlocks broader distribution, but the effect is **directional — there is no published engagement count that flips it** (see `references/algorithm-signals-reference.md`, first-hour velocity). Monitored at 5/15/30/60/90-minute intervals.
|
||||||
|
|
||||||
**Used in:** `references/algorithm-signals-reference.md`, `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/audience-insights/engagement-patterns.md`
|
**Used in:** `references/algorithm-signals-reference.md`, `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/audience-insights/engagement-patterns.md`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ Before adding a visual, ask:
|
||||||
| Intermediate | Figma | Custom graphics, consistent templates | Free/$15/mo |
|
| Intermediate | Figma | Custom graphics, consistent templates | Free/$15/mo |
|
||||||
| Advanced | Adobe Illustrator | Complex infographics | $23/mo |
|
| Advanced | Adobe Illustrator | Complex infographics | $23/mo |
|
||||||
|
|
||||||
**Recommendation for thought leaders:** Canva or Figma with 2-3 reusable templates. Don't spend time on custom designs for every post.
|
**Recommendation for creators:** Canva or Figma with 2-3 reusable templates. Don't spend time on custom designs for every post.
|
||||||
|
|
||||||
## Brand Consistency Checklist
|
## Brand Consistency Checklist
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ to a finished draft and call it gated).
|
||||||
| Step 4 — Consistency + quality | Primary enforcement: apply rules 1–6, calibrate per rule 7, report a pass/flag per rule. Rule 8 is verified as historical fact (skeleton existed + was gated). |
|
| Step 4 — Consistency + quality | Primary enforcement: apply rules 1–6, calibrate per rule 7, report a pass/flag per rule. Rule 8 is verified as historical fact (skeleton existed + was gated). |
|
||||||
| Step 5 — Fact-check sweep | Fixes obey rule 6 (tighten, don't expand). |
|
| Step 5 — Fact-check sweep | Fixes obey rule 6 (tighten, don't expand). |
|
||||||
| Step 6 — Persona sweep (pre-lock) | Rework obeys rule 6; the leader-takeaway (rule 1) and arc (rule 2) are what the reader jury judges for resonance. A draft that passed rule 8 typically lands here with far fewer spine-level reworks. |
|
| Step 6 — Persona sweep (pre-lock) | Rework obeys rule 6; the leader-takeaway (rule 1) and arc (rule 2) are what the reader jury judges for resonance. A draft that passed rule 8 typically lands here with far fewer spine-level reworks. |
|
||||||
| Step 9 — Hook / conversion gate | Hook revisions obey rule 6 (sharpen the krok by tightening, body stays locked). |
|
| Step 9 — Hook / conversion gate | Hook revisions obey rule 6 (sharpen the krok by tightening, body stays locked). The hook-forge is not an exception: every variant is cut to the same 110–140 band, so choosing between kroks swaps rather than expands. |
|
||||||
|
|
||||||
## Self-certification boundary
|
## Self-certification boundary
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,26 @@ LinkedIn Newsletters are a powerful tool for building an owned audience within L
|
||||||
|
|
||||||
## When to Launch Your Newsletter
|
## When to Launch Your Newsletter
|
||||||
|
|
||||||
### Minimum Thresholds
|
### Not a platform gate — a readiness judgment
|
||||||
|
|
||||||
- **5,000+ followers** (ensures viable initial subscriber base)
|
**LinkedIn imposes no follower minimum on individual members:** "All LinkedIn members have
|
||||||
|
access to create a newsletter on LinkedIn" (LinkedIn Help, *Newsletters on LinkedIn FAQ*,
|
||||||
|
a517914; company pages have separate access criteria). Everything below is an editorial
|
||||||
|
readiness bar, not a requirement — you *can* launch at any size, the question is whether
|
||||||
|
you should.
|
||||||
|
|
||||||
|
### Readiness signals
|
||||||
|
|
||||||
|
- **An audience large enough that a launch issue lands somewhere** — a few thousand
|
||||||
|
followers is a common working floor, but it is a heuristic, not a threshold
|
||||||
- **3+ months of consistent posting** (proven content discipline)
|
- **3+ months of consistent posting** (proven content discipline)
|
||||||
- **Clear topical authority** (algorithm recognizes your expertise)
|
- **Clear topical authority** (algorithm recognizes your expertise)
|
||||||
- **Reliable content generation system** (can sustain weekly/biweekly publishing)
|
- **Reliable content generation system** (can sustain weekly/biweekly publishing)
|
||||||
|
|
||||||
### Why Wait Until 5,000+
|
### Why size matters at all
|
||||||
|
|
||||||
- Newsletter notifications go to all subscribers (empty newsletters damage credibility)
|
- Every issue notifies every subscriber — thin editions damage credibility at any scale
|
||||||
- Lower subscriber counts reduce perceived authority
|
- Lower subscriber counts reduce perceived authority
|
||||||
- Algorithm favors newsletters from established creators
|
|
||||||
- Need sufficient content library to repurpose effectively
|
- Need sufficient content library to repurpose effectively
|
||||||
|
|
||||||
### Red Flags You're Not Ready
|
### Red Flags You're Not Ready
|
||||||
|
|
@ -40,7 +48,11 @@ LinkedIn Newsletters are a powerful tool for building an owned audience within L
|
||||||
|
|
||||||
### LinkedIn Newsletters
|
### LinkedIn Newsletters
|
||||||
|
|
||||||
- Direct notification to ALL subscribers (inbox + email notification)
|
- Direct notification to ALL subscribers — but **not necessarily by email**: LinkedIn
|
||||||
|
de-duplicates across channels, and "if you receive an in-app or push notification, you
|
||||||
|
should not expect to also receive an email for the same notification" (LinkedIn Help,
|
||||||
|
a517914). Treat the notification as the reliable channel and email as a bonus; do not
|
||||||
|
plan an edition around inbox delivery
|
||||||
- Owned audience (subscribers chose to be notified)
|
- Owned audience (subscribers chose to be notified)
|
||||||
- **Ideal for:** Deeper dives, comprehensive frameworks, email list building, monetization
|
- **Ideal for:** Deeper dives, comprehensive frameworks, email list building, monetization
|
||||||
- Higher commitment from subscribers (they opted in)
|
- Higher commitment from subscribers (they opted in)
|
||||||
|
|
@ -278,7 +290,7 @@ When someone engages deeply on a topic:
|
||||||
|
|
||||||
| Mistake | Fix |
|
| Mistake | Fix |
|
||||||
|---------|-----|
|
|---------|-----|
|
||||||
| Launching too early | Wait until 5,000+ followers and consistent posting habit |
|
| Launching too early | Nothing on the platform stops you — wait for a consistent posting habit and an audience worth notifying |
|
||||||
| Inconsistent publishing | Choose sustainable frequency and never skip |
|
| Inconsistent publishing | Choose sustainable frequency and never skip |
|
||||||
| Newsletter as dumping ground for post leftovers | Provide genuine additional value, not reposts |
|
| Newsletter as dumping ground for post leftovers | Provide genuine additional value, not reposts |
|
||||||
| Over-promoting newsletter in every post | Subtle mentions, maximum 1 per 5-7 posts |
|
| Over-promoting newsletter in every post | Subtle mentions, maximum 1 per 5-7 posts |
|
||||||
|
|
@ -289,4 +301,4 @@ When someone engages deeply on a topic:
|
||||||
|
|
||||||
## Bottom Line
|
## Bottom Line
|
||||||
|
|
||||||
Newsletters are powerful for building owned audience and deepening relationships, but only after establishing consistent posting and reaching 5,000+ followers. Quality and consistency matter more than frequency.
|
Newsletters are powerful for building owned audience and deepening relationships. LinkedIn lets any member start one — the real gate is editorial: a consistent posting habit, a clear topic, and enough content capacity to sustain the cadence. Quality and consistency matter more than frequency.
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,46 @@ import { PROVENANCE_VALUES } from "./types.js";
|
||||||
import type { Provenance } from "./types.js";
|
import type { Provenance } from "./types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stable slug: lowercase, trim, every non-alphanumeric run → a single `-`, with
|
* Letters NFD cannot reach. Unicode decomposition turns `å`/`é`/`ñ` into a base
|
||||||
* leading/trailing dashes stripped. Stable across case + whitespace variation of
|
* letter plus a combining mark, but `ø`, `æ`, `ß` and friends are atomic code
|
||||||
* the same label, so it is a safe id key.
|
* points with no decomposition — they need an explicit mapping or they fall into
|
||||||
|
* the non-alphanumeric run and become a dash. Applied AFTER lowercasing, so only
|
||||||
|
* the lowercase forms are listed.
|
||||||
|
*/
|
||||||
|
const TRANSLITERATIONS: ReadonlyArray<readonly [RegExp, string]> = [
|
||||||
|
[/æ/g, "ae"],
|
||||||
|
[/ø/g, "o"],
|
||||||
|
[/œ/g, "oe"],
|
||||||
|
[/ß/g, "ss"],
|
||||||
|
[/[đð]/g, "d"],
|
||||||
|
[/þ/g, "th"],
|
||||||
|
[/ł/g, "l"],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable slug: lowercase, trim, transliterate, then every non-alphanumeric run →
|
||||||
|
* a single `-`, with leading/trailing dashes stripped. Stable across case +
|
||||||
|
* whitespace variation of the same label, so it is a safe id key.
|
||||||
|
*
|
||||||
|
* Transliteration (N24.6) exists because the plain `[^a-z0-9]` collapse was lossy
|
||||||
|
* for every non-English label: "Løkkene" slugged to `l-kkene`, and — worse — `ø`
|
||||||
|
* and `å` both became `-`, so "møte" and "måte" collapsed onto ONE id. It narrows
|
||||||
|
* that collision class; it does not close it (`møte` and `mote` still meet, as
|
||||||
|
* they must for the case/whitespace stability above to hold).
|
||||||
|
*
|
||||||
|
* ORDER IS LOAD-BEARING: transliteration runs BEFORE the `[^a-z0-9]+` collapse, so
|
||||||
|
* the collapse stays the final gate and the output whitelist is unchanged. Anything
|
||||||
|
* downstream that rests on "a slug cannot contain a %-escape, a path or a URL"
|
||||||
|
* (the ingestion-guard exposure figures do) rests on that ordering.
|
||||||
*/
|
*/
|
||||||
export function slugify(label: string): string {
|
export function slugify(label: string): string {
|
||||||
return label
|
let s = label
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/[^a-z0-9]+/g, "-")
|
.normalize("NFD")
|
||||||
.replace(/^-+|-+$/g, "");
|
.replace(/\p{M}+/gu, "");
|
||||||
|
for (const [pattern, replacement] of TRANSLITERATIONS) s = s.replace(pattern, replacement);
|
||||||
|
return s.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,24 @@ function today(): string {
|
||||||
return new Date().toISOString().slice(0, 10);
|
return new Date().toISOString().slice(0, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bundle-root index. `okf_version` sits in a FRONTMATTER block, not body text
|
||||||
|
* (OKF-form spec §6, v0.3): upstream carves exactly one exception to "index files
|
||||||
|
* contain no frontmatter", and it is enumerated to this single key. So nothing else
|
||||||
|
* rides along here — no `type:`/`title:` (those belong to concept files), and
|
||||||
|
* `okf_layout` stays in body text per §12. Non-root index files carry no block at all.
|
||||||
|
*
|
||||||
|
* The value stays 0.1 — this migration moves the marker, it does not bump it.
|
||||||
|
*/
|
||||||
function indexSeed(): string {
|
function indexSeed(): string {
|
||||||
return `# Brain — Index (MOC)
|
return `---
|
||||||
|
okf_version: 0.1
|
||||||
|
---
|
||||||
|
# Brain — Index (MOC)
|
||||||
|
|
||||||
> Map of Content — one screen pointing at every tributary, with a freshness flag.
|
> Map of Content — one screen pointing at every tributary, with a freshness flag.
|
||||||
> Generated by \`brain init\`; safe to hand-edit (a re-run never clobbers it).
|
> Generated by \`brain init\`; safe to hand-edit (a re-run never clobbers it).
|
||||||
|
|
||||||
okf_version: 0.1
|
|
||||||
|
|
||||||
| Tributary | What it holds | Freshness |
|
| Tributary | What it holds | Freshness |
|
||||||
|-----------|---------------|-----------|
|
|-----------|---------------|-----------|
|
||||||
| voice-samples | writing style | — |
|
| voice-samples | writing style | — |
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -15,8 +15,9 @@ import { parseProfile } from "../src/profile.js";
|
||||||
* `okr/scripts/okf-check.mjs`, the reference checker:
|
* `okr/scripts/okf-check.mjs`, the reference checker:
|
||||||
* - every CONCEPT file (`*.md` except `index.md`) carries a non-empty `type` in a
|
* - every CONCEPT file (`*.md` except `index.md`) carries a non-empty `type` in a
|
||||||
* leading YAML frontmatter block;
|
* leading YAML frontmatter block;
|
||||||
* - the bundle-root `index.md` carries an `okf_version` marker as markdown TEXT
|
* - the bundle-root `index.md` carries an `okf_version` marker in its FRONTMATTER
|
||||||
* (index files carry no frontmatter per the OKF spec);
|
* block — upstream's single carved exception to "index files carry no
|
||||||
|
* frontmatter" (spec §6, v0.3; this was body text until N24.6);
|
||||||
* - each directory level has its own `index.md` (progressive disclosure).
|
* - each directory level has its own `index.md` (progressive disclosure).
|
||||||
*
|
*
|
||||||
* EXCLUDED by design (brief §6): the `ingest/` tributary. `ingest/published/*.md`
|
* EXCLUDED by design (brief §6): the `ingest/` tributary. `ingest/published/*.md`
|
||||||
|
|
@ -68,10 +69,39 @@ describe("brain/ bundle is OKF-compatible form (Stage 1)", () => {
|
||||||
rmSync(root, { recursive: true, force: true });
|
rmSync(root, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("bundle-root index.md carries an okf_version marker (markdown text, no frontmatter)", () => {
|
// N24.6 (OKF 0.3 §6): the marker moved from body text INTO the root index.md's
|
||||||
|
// frontmatter block — upstream's one carved exception to "index files contain no
|
||||||
|
// frontmatter". The two assertions this replaced were vacuous: `/^okf_version:/m`
|
||||||
|
// matches a frontmatter line as happily as a body line, and `frontmatterType()`
|
||||||
|
// reads `type:`, so it returns null with or without a block. Both went green on
|
||||||
|
// either placement, which is exactly the debt class N24.5 swept.
|
||||||
|
test("bundle-root index.md carries okf_version INSIDE the leading frontmatter block", () => {
|
||||||
const index = readFileSync(join(root, "brain/index.md"), "utf8");
|
const index = readFileSync(join(root, "brain/index.md"), "utf8");
|
||||||
assert.match(index, /^okf_version:\s*\S+/m, "root index.md declares okf_version");
|
const block = index.match(/^---\n([\s\S]*?)\n---\n/);
|
||||||
assert.equal(frontmatterType(index), null, "index.md carries NO frontmatter (OKF reserved file)");
|
assert.ok(block, "root index.md opens with a frontmatter block");
|
||||||
|
assert.match(block![1], /^okf_version:\s*\S+/m, "the block declares okf_version");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("okf_version does NOT also sit in the body (one marker, one placement)", () => {
|
||||||
|
const index = readFileSync(join(root, "brain/index.md"), "utf8");
|
||||||
|
const body = index.replace(/^---\n[\s\S]*?\n---\n/, "");
|
||||||
|
assert.doesNotMatch(body, /^okf_version:/m, "no leftover body-text marker");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the root index frontmatter carries okf_version and nothing else", () => {
|
||||||
|
// Upstream's exception is enumerated to ONE key, so `okf_layout` (this
|
||||||
|
// convention's own extension marker, spec §12) stays in body text and no
|
||||||
|
// concept-style `type:`/`title:` may ride along in an index file.
|
||||||
|
const index = readFileSync(join(root, "brain/index.md"), "utf8");
|
||||||
|
const block = index.match(/^---\n([\s\S]*?)\n---\n/)![1];
|
||||||
|
const keys = block.split("\n").filter((l) => l.trim() !== "").map((l) => l.split(":")[0].trim());
|
||||||
|
assert.deepEqual(keys, ["okf_version"]);
|
||||||
|
assert.equal(frontmatterType(index), null, "an index file still carries no `type:`");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a non-root index.md carries no frontmatter at all", () => {
|
||||||
|
const journal = readFileSync(join(root, "brain/journal/index.md"), "utf8");
|
||||||
|
assert.doesNotMatch(journal, /^---\n/, "only the BUNDLE-ROOT index may carry a block");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("every concept file under brain/ carries a non-empty frontmatter type", () => {
|
test("every concept file under brain/ carries a non-empty frontmatter type", () => {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ export function renderKilder(input: RenderKilderInput): string {
|
||||||
out.push(`# Kilder — ${title} (${articleId})`, "");
|
out.push(`# Kilder — ${title} (${articleId})`, "");
|
||||||
out.push(
|
out.push(
|
||||||
"> Auto-generert fra edition-state `livedSpecifics` + specifics-bank. Per lastbærende påstand: hva backer den.",
|
"> Auto-generert fra edition-state `livedSpecifics` + specifics-bank. Per lastbærende påstand: hva backer den.",
|
||||||
"> Levd materiale er KTGs egne ord, aldri AI-oppdiktet (retning §3). Tall er uverifiserte til fakta-sjekket (regel 6/7).",
|
"> Levd materiale er operatørens egne ord, aldri AI-oppdiktet (retning §3). Tall er uverifiserte til fakta-sjekket (regel 6/7).",
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
out.push("## Lastbærende påstander", "");
|
out.push("## Lastbærende påstander", "");
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ describe("renderKilder — the NN-kilder.md artifact", () => {
|
||||||
const expected = `# Kilder — Maskinrommet (05)
|
const expected = `# Kilder — Maskinrommet (05)
|
||||||
|
|
||||||
> Auto-generert fra edition-state \`livedSpecifics\` + specifics-bank. Per lastbærende påstand: hva backer den.
|
> Auto-generert fra edition-state \`livedSpecifics\` + specifics-bank. Per lastbærende påstand: hva backer den.
|
||||||
> Levd materiale er KTGs egne ord, aldri AI-oppdiktet (retning §3). Tall er uverifiserte til fakta-sjekket (regel 6/7).
|
> Levd materiale er operatørens egne ord, aldri AI-oppdiktet (retning §3). Tall er uverifiserte til fakta-sjekket (regel 6/7).
|
||||||
|
|
||||||
## Lastbærende påstander
|
## Lastbærende påstander
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -780,7 +780,7 @@ if [ -x "$BR_DIR/node_modules/.bin/tsx" ]; then
|
||||||
BR_OUT=$( set +e; (cd "$BR_DIR" && npm test) 2>&1; echo "BR_EXIT:$?" )
|
BR_OUT=$( set +e; (cd "$BR_DIR" && npm test) 2>&1; echo "BR_EXIT:$?" )
|
||||||
BR_EXIT=$(echo "$BR_OUT" | grep -oE 'BR_EXIT:[0-9]+' | grep -oE '[0-9]+' | head -1)
|
BR_EXIT=$(echo "$BR_OUT" | grep -oE 'BR_EXIT:[0-9]+' | grep -oE '[0-9]+' | head -1)
|
||||||
BR_TESTS=$(echo "$BR_OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)
|
BR_TESTS=$(echo "$BR_OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)
|
||||||
BRAIN_TESTS_FLOOR=127 # SB-S0 34 [id(11)+profile(6)+fold(12)+scaffold(5)] + SB-S1 29 [ingest(14)+publish(9)+cli(6)] + SB-S2 19 [consolidate(12)+consolidate-cli(7)] + SB-S3b 12 [consolidate(10)+consolidate-cli(2)] + SB-S3c 19 [ingest(4)+publish(3)+assemble(8)+cli(4)] + SB-S3d 1 [scaffold dated-anchor seed] + SB-S3e 13 [reconcile: parse(5)+tiers(7)+io(1)]
|
BRAIN_TESTS_FLOOR=142 # 127 decomposed: SB-S0 34 [id(11)+profile(6)+fold(12)+scaffold(5)] + SB-S1 29 [ingest(14)+publish(9)+cli(6)] + SB-S2 19 [consolidate(12)+consolidate-cli(7)] + SB-S3b 12 [consolidate(10)+consolidate-cli(2)] + SB-S3c 19 [ingest(4)+publish(3)+assemble(8)+cli(4)] + SB-S3d 1 [scaffold dated-anchor seed] + SB-S3e 13 [reconcile: parse(5)+tiers(7)+io(1)]; + 7 added between S3e and N24.6 without a floor bump (measured, not decomposed) + N24.6 8 [id: transliteration(3)+whitelist(1)+ascii-goldens(1), okf-conform: frontmatter placement(3)]
|
||||||
if [ "$BR_EXIT" = "0" ] && [ -n "$BR_TESTS" ] && [ "$BR_TESTS" -ge "$BRAIN_TESTS_FLOOR" ]; then
|
if [ "$BR_EXIT" = "0" ] && [ -n "$BR_TESTS" ] && [ "$BR_TESTS" -ge "$BRAIN_TESTS_FLOOR" ]; then
|
||||||
pass "brain suite green: $BR_TESTS tests pass (floor $BRAIN_TESTS_FLOOR)"
|
pass "brain suite green: $BR_TESTS tests pass (floor $BRAIN_TESTS_FLOOR)"
|
||||||
else
|
else
|
||||||
|
|
@ -2930,6 +2930,379 @@ fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# --- Section 16y: Positioning Brief - generation, consumption, operator gate (N23 / E#4) ---
|
||||||
|
echo "--- Positioning Brief: distillation + create-surface consumption + regeneration gate (N23) ---"
|
||||||
|
|
||||||
|
# A draft is only as anchored as the positioning it is written against. The brief is a
|
||||||
|
# DISTILLATE of the second brain, not a second copy of the declared profile, and three
|
||||||
|
# disciplines carry the whole feature - each of which fails silently if eroded:
|
||||||
|
# (E#4 separation) user-profile.md stays the operator's DECLARED identity; the brief is
|
||||||
|
# the evidence-weighted distillate. Two files restating the same claim
|
||||||
|
# is the N22 defect class (conflicting numbers across surfaces), so the
|
||||||
|
# brief must DEFER to the profile and to the operations anchor.
|
||||||
|
# (E#4 consumption) a brief nothing reads is a file, not an engine. The create surfaces
|
||||||
|
# and newsletter Step 1 must read it the way they read voice-samples.
|
||||||
|
# (E#4 gate) positioning is the operator's own. Regeneration is operator-gated:
|
||||||
|
# an explicit go, a visible diff, and never a silent overwrite. This is
|
||||||
|
# the check most likely to be "simplified" away, because a confirmation
|
||||||
|
# prompt reads as friction rather than the guarantee it is.
|
||||||
|
# (anti-drift) the brain-consumption rules stay in ONE file (strategy-advisor). The
|
||||||
|
# hosting command DELEGATES; it does not copy the block. Six copies of
|
||||||
|
# the standard skeleton (N21) is exactly what that copy would become.
|
||||||
|
BRIEF_PATH_N23="profile/positioning-brief.md"
|
||||||
|
SETUP_N23="commands/setup.md"
|
||||||
|
ADVISOR_N23="agents/strategy-advisor.md"
|
||||||
|
TMPL_N23="config/positioning-brief.template.md"
|
||||||
|
DIFFCHK_N23="agents/differentiation-checker.md"
|
||||||
|
NEWS_N23="commands/newsletter.md"
|
||||||
|
|
||||||
|
gate_honest() { # $1 = text; honest iff it REQUIRES a go, SHOWS the change, and FORBIDS a silent overwrite
|
||||||
|
# -qiF, not -qF: these are concepts in running prose, where sentence position decides the
|
||||||
|
# casing. Case-pinning would force the prose to fit the lint instead of the other way round.
|
||||||
|
echo "$1" | grep -qiF "explicit go" \
|
||||||
|
&& echo "$1" | grep -qiF "never overwrite" \
|
||||||
|
&& echo "$1" | grep -qiF "diff"
|
||||||
|
}
|
||||||
|
|
||||||
|
GATE_SELFTEST_N23=1
|
||||||
|
if ! gate_honest "regeneration requires an explicit go: show the diff first and never overwrite the existing brief without it"; then
|
||||||
|
GATE_SELFTEST_N23=0; echo " non-vacuity FAIL: a fully-gated regeneration probe was not detected"
|
||||||
|
fi
|
||||||
|
while IFS= read -r probe; do
|
||||||
|
[ -z "$probe" ] && continue
|
||||||
|
if gate_honest "$probe"; then
|
||||||
|
GATE_SELFTEST_N23=0; echo " false-positive FAIL: under-specified gate probe accepted -> $probe"
|
||||||
|
fi
|
||||||
|
done <<'NEGATIVE16Y'
|
||||||
|
regenerate the brief from the brain and overwrite it with the new distillation
|
||||||
|
ask the operator before regenerating, then write the new brief over the old one
|
||||||
|
show the diff and regenerate on confirmation, replacing the previous brief
|
||||||
|
an explicit go is required before regenerating the positioning brief
|
||||||
|
NEGATIVE16Y
|
||||||
|
if [ "$GATE_SELFTEST_N23" -eq 1 ]; then
|
||||||
|
pass "regeneration-gate self-test: predicate needs explicit-go + diff + no-silent-overwrite (1 accepted, 4 under-specified rejected)"
|
||||||
|
else
|
||||||
|
fail "regeneration-gate self-test failed - the N23 gate lint is vacuous or over-eager"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#4 generation) the template exists and carries the four positioning questions
|
||||||
|
if [ -f "$TMPL_N23" ] \
|
||||||
|
&& grep -qF "## Who I am" "$TMPL_N23" 2>/dev/null \
|
||||||
|
&& grep -qF "## Who this is for" "$TMPL_N23" 2>/dev/null \
|
||||||
|
&& grep -qF "## What I hold" "$TMPL_N23" 2>/dev/null \
|
||||||
|
&& grep -qF "## How I differ" "$TMPL_N23" 2>/dev/null; then
|
||||||
|
pass "positioning-brief template carries all four positioning sections (E#4)"
|
||||||
|
else
|
||||||
|
fail "$TMPL_N23 missing or incomplete - the brief has no declared shape (E#4)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#4 separation) the template states it is a distillate that defers, not a second profile
|
||||||
|
if grep -qF "does not restate" "$TMPL_N23" 2>/dev/null \
|
||||||
|
&& grep -qF "user-profile.md" "$TMPL_N23" 2>/dev/null; then
|
||||||
|
pass "template records that the brief defers to the declared profile instead of restating it"
|
||||||
|
else
|
||||||
|
fail "$TMPL_N23 does not separate distillate from declared profile - two sources of positioning truth (N22 class)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#4 generation) setup hosts the generator as its own step and names the target path
|
||||||
|
if grep -qF "## Step 3g: Positioning Brief" "$SETUP_N23" 2>/dev/null \
|
||||||
|
&& grep -qF "$BRIEF_PATH_N23" "$SETUP_N23" 2>/dev/null; then
|
||||||
|
pass "setup Step 3g generates the brief to the per-user data dir (E#4)"
|
||||||
|
else
|
||||||
|
fail "$SETUP_N23 has no Step 3g writing $BRIEF_PATH_N23 - the generator has no surface (E#4)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#4 generation) the step is reachable: the Step 2 menu offers it
|
||||||
|
if grep -qF "Positioning brief" "$SETUP_N23" 2>/dev/null \
|
||||||
|
&& grep -qF "always show all 8" "$SETUP_N23" 2>/dev/null; then
|
||||||
|
pass "setup Step 2 menu offers the positioning brief (8 options) - the step is reachable"
|
||||||
|
else
|
||||||
|
fail "$SETUP_N23 Step 2 menu does not offer the brief - Step 3g is unreachable (E#4)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (anti-drift) setup DELEGATES the distillation instead of copying the brain-consumption block
|
||||||
|
if grep -qF "linkedin-studio:strategy-advisor" "$SETUP_N23" 2>/dev/null; then
|
||||||
|
pass "setup delegates the distillation to the namespaced strategy-advisor (no copied brain block)"
|
||||||
|
else
|
||||||
|
fail "$SETUP_N23 does not delegate to linkedin-studio:strategy-advisor - the brain rules will be copied (N21 class)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (anti-drift) the brain-consumption rules stay in exactly one file
|
||||||
|
BRAINDOC_N23=$(grep -lF "Consuming the evolving profile" agents/*.md commands/*.md 2>/dev/null | wc -l | tr -d ' ')
|
||||||
|
if [ "$BRAINDOC_N23" = "1" ]; then
|
||||||
|
pass "brain-consumption rules live in exactly 1 file - no second copy to drift (SB-S3a)"
|
||||||
|
else
|
||||||
|
fail "brain-consumption rules found in $BRAINDOC_N23 files - a copy will drift from the original (N21 class)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#4 generation) the advisor owns the distillation and writes the four sections
|
||||||
|
if grep -qF "Distilling the positioning brief" "$ADVISOR_N23" 2>/dev/null \
|
||||||
|
&& grep -qF "$BRIEF_PATH_N23" "$ADVISOR_N23" 2>/dev/null; then
|
||||||
|
pass "strategy-advisor owns the positioning distillation (reuses its brain readers)"
|
||||||
|
else
|
||||||
|
fail "$ADVISOR_N23 has no positioning-brief distillation - setup would have to reimplement it (E#4)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#4 separation) the distillation defers to the operator's own anchor over an older inference
|
||||||
|
if grep -qF "Who I am now" "$ADVISOR_N23" 2>/dev/null \
|
||||||
|
&& grep -qiF "operator has declared is never overridden by a distilled inference" "$ADVISOR_N23" 2>/dev/null; then
|
||||||
|
pass "distillation subordinates itself to the operator's dated anchor (frozen-past-self guard extends to positioning)"
|
||||||
|
else
|
||||||
|
fail "$ADVISOR_N23 may let a machine distillate outrank the operator's own declaration (E#4)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#4 gate) regeneration is operator-gated on BOTH surfaces that can trigger it
|
||||||
|
for f_n23 in "$SETUP_N23" "$ADVISOR_N23"; do
|
||||||
|
if gate_honest "$(cat "$f_n23" 2>/dev/null)"; then
|
||||||
|
pass "$(basename "$f_n23"): regeneration is gated (explicit go + diff + no silent overwrite)"
|
||||||
|
else
|
||||||
|
fail "$f_n23 may regenerate the brief without an explicit go, a diff, or without preserving the old one (E#4)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# (E#4 consumption) the create surfaces and newsletter Step 1 read the brief, traceably
|
||||||
|
for f_n23 in commands/post.md commands/quick.md commands/batch.md "$NEWS_N23" "$DIFFCHK_N23"; do
|
||||||
|
if grep -qF "$BRIEF_PATH_N23" "$f_n23" 2>/dev/null; then
|
||||||
|
pass "$(basename "$f_n23") reads the positioning brief (E#4 consumption)"
|
||||||
|
else
|
||||||
|
fail "$f_n23 does not read $BRIEF_PATH_N23 - the brief steers nothing there (E#4)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# (E#4 consumption) absence is graceful: a fresh install has no brain and no brief
|
||||||
|
if grep -qF "no brief yet" "$ADVISOR_N23" 2>/dev/null \
|
||||||
|
|| grep -qF "no brief yet" "$SETUP_N23" 2>/dev/null; then
|
||||||
|
pass "an absent brief degrades silently (fresh installs have no brain to distil)"
|
||||||
|
else
|
||||||
|
fail "absent-brief handling is undocumented - a fresh install would error or nag (E#4)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Section 16z: Promo-Post Engine - hook-forge + shareability gate (N24 / E#7, E#8) ---
|
||||||
|
echo "--- Promo-Post Engine: hook-forge + shareability gate (N24) ---"
|
||||||
|
|
||||||
|
# The edition's promo post is the acknowledgement engine: a peer forwarding it to a peer is the
|
||||||
|
# signal the edition was written to earn. Two things were missing at Step 9 and both fail QUIETLY:
|
||||||
|
# (E#7 forge) the shipped krok was the one the draft happened to end on - it never competed
|
||||||
|
# against an alternative. The forge produces five to eight variants, enforces the
|
||||||
|
# mobile cutoff PER VARIANT (measured, not estimated), spans at least three hook
|
||||||
|
# types so the choice is real, and traces every variant to the locked body so a
|
||||||
|
# variant cannot promise what the edition does not deliver.
|
||||||
|
# (E#7 honesty) the forge carries ONLY checkable marks - measured length, hook type, body-trace.
|
||||||
|
# A predicted-engagement score would be an invented composite, which is the exact
|
||||||
|
# defect class N22 removed (fabricated percentiles / Velocity Score). This check is
|
||||||
|
# the one most likely to erode, because a ranked list LOOKS more helpful.
|
||||||
|
# (E#8 deling) a click is one reader; a forward is the acknowledgement. The gate asks who
|
||||||
|
# forwards this and why, demands a named role and a sender-motive, and CANNOT be
|
||||||
|
# resolved by re-reading the same copy more generously (never a silent pass).
|
||||||
|
# (invariant) the forged variant goes THROUGH the N9 randsone gate, never around it - a forge
|
||||||
|
# that bypassed the fact/language pass would reopen the hole N9 closed.
|
||||||
|
# Both predicates are compound, so both carry a non-vacuity self-test (mirrors 16p/16y). All
|
||||||
|
# checks are UNCONDITIONAL .md greps -> they raise the deps-absent floor.
|
||||||
|
NL_N24="commands/newsletter.md"
|
||||||
|
PR_N24="agents/persona-reviewer.md"
|
||||||
|
STEP9_N24=$(awk '/^## Step 9:/{f=1} /^## Step 10:/{f=0} f' "$NL_N24")
|
||||||
|
|
||||||
|
forge_honest() { # $1 = text; honest iff it forges a SET, cuts on the band, and refuses an invented score
|
||||||
|
echo "$1" | grep -qiF "five to eight hook variants" \
|
||||||
|
&& echo "$1" | grep -qiF "disqualified, not ranked lower" \
|
||||||
|
&& echo "$1" | grep -qiF "no predicted-engagement score"
|
||||||
|
}
|
||||||
|
|
||||||
|
deling_gated() { # $1 = text; gated iff it asks the named-forwarder question, names the actions, and cannot self-resolve
|
||||||
|
# -qiF throughout: these are concepts in running prose, where sentence position decides the
|
||||||
|
# casing (N23 lesson - do not bend the prose to fit the lint).
|
||||||
|
echo "$1" | grep -qiF "who forwards this to a colleague, and why" \
|
||||||
|
&& echo "$1" | grep -qiF "RESHARE / SAVE / SEND" \
|
||||||
|
&& echo "$1" | grep -qiF "never a silent pass"
|
||||||
|
}
|
||||||
|
|
||||||
|
FORGE_SELFTEST_N24=1
|
||||||
|
if ! forge_honest "forge five to eight hook variants; a variant outside the band is disqualified, not ranked lower, and carries no predicted-engagement score"; then
|
||||||
|
FORGE_SELFTEST_N24=0; echo " non-vacuity FAIL: a fully-wired hook-forge probe was not detected"
|
||||||
|
fi
|
||||||
|
while IFS= read -r probe; do
|
||||||
|
[ -z "$probe" ] && continue
|
||||||
|
if forge_honest "$probe"; then
|
||||||
|
FORGE_SELFTEST_N24=0; echo " false-positive FAIL: under-wired forge probe accepted -> $probe"
|
||||||
|
fi
|
||||||
|
done <<'NEGATIVE16Z1'
|
||||||
|
forge five to eight hook variants and rank them by predicted engagement, best first
|
||||||
|
five to eight hook variants, each disqualified, not ranked lower when out of band, scored 1-10 for reach
|
||||||
|
generate a few hook options and let the operator pick the strongest one
|
||||||
|
variants outside the band are disqualified, not ranked lower, and carry no predicted-engagement score
|
||||||
|
NEGATIVE16Z1
|
||||||
|
if [ "$FORGE_SELFTEST_N24" -eq 1 ]; then
|
||||||
|
pass "hook-forge self-test: predicate needs the variant SET + band-cutoff + no invented score (1 accepted, 4 under-wired rejected)"
|
||||||
|
else
|
||||||
|
fail "hook-forge self-test failed - the N24 forge lint is vacuous or over-eager"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DELING_SELFTEST_N24=1
|
||||||
|
if ! deling_gated "the gate asks who forwards this to a colleague, and why, records RESHARE / SAVE / SEND, and allows never a silent pass"; then
|
||||||
|
DELING_SELFTEST_N24=0; echo " non-vacuity FAIL: a fully-gated shareability probe was not detected"
|
||||||
|
fi
|
||||||
|
while IFS= read -r probe; do
|
||||||
|
[ -z "$probe" ] && continue
|
||||||
|
if deling_gated "$probe"; then
|
||||||
|
DELING_SELFTEST_N24=0; echo " false-positive FAIL: under-gated deling probe accepted -> $probe"
|
||||||
|
fi
|
||||||
|
done <<'NEGATIVE16Z2'
|
||||||
|
ask who forwards this to a colleague, and why, then record RESHARE / SAVE / SEND and move on
|
||||||
|
judge whether the post is shareable and saveable; never a silent pass
|
||||||
|
who forwards this to a colleague, and why - answer it, and never a silent pass
|
||||||
|
NEGATIVE16Z2
|
||||||
|
if [ "$DELING_SELFTEST_N24" -eq 1 ]; then
|
||||||
|
pass "shareability-gate self-test: predicate needs the named-forwarder question + the three actions + no silent pass (1 accepted, 3 under-gated rejected)"
|
||||||
|
else
|
||||||
|
fail "shareability-gate self-test failed - the N24 deling lint is vacuous or over-eager"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#7 forge) Step 9 forges a variant SET and cuts on the band instead of inventing a score
|
||||||
|
if forge_honest "$STEP9_N24"; then
|
||||||
|
pass "newsletter.md Step 9 forges five to eight variants, cuts out-of-band, and invents no score (E#7)"
|
||||||
|
else
|
||||||
|
fail "newsletter.md Step 9 has no honest hook-forge - the krok still ships unchallenged, or is ranked by an invented number (E#7)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#7 measurement) the band is MEASURED per variant, with the multibyte-safe counter
|
||||||
|
if echo "$STEP9_N24" | grep -qF "perl -CSD" \
|
||||||
|
&& echo "$STEP9_N24" | grep -qiF "enforced per variant, never on the set"; then
|
||||||
|
pass "Step 9 measures the hook band per variant with the multibyte-safe counter (awk length lies on ae/oe/aa)"
|
||||||
|
else
|
||||||
|
fail "Step 9 does not measure the band per variant - byte-counting over-reports Norwegian hooks (E#7)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#7 real choice) the surviving set must span hook types, or the forge produced rewordings
|
||||||
|
if echo "$STEP9_N24" | grep -qiF "at least three distinct hook types"; then
|
||||||
|
pass "Step 9 requires the surviving variants to span at least three hook types (a real choice, not rewordings)"
|
||||||
|
else
|
||||||
|
fail "Step 9 does not require type-spanning variants - eight rewordings of one idea is not a choice (E#7)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#7 no bait) every variant traces to a claim the LOCKED body makes
|
||||||
|
if echo "$STEP9_N24" | grep -qiF "a claim the locked body actually makes"; then
|
||||||
|
pass "Step 9 disqualifies a variant that promises what the locked body does not deliver (no bait krok)"
|
||||||
|
else
|
||||||
|
fail "Step 9 lets a hook variant promise what the edition never delivers - bait reaches the feed (E#7)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (invariant) the chosen variant is gated, not exempted - it re-enters the N9 randsone pass
|
||||||
|
if echo "$STEP9_N24" | grep -qiF "through the randsone gate, never around it"; then
|
||||||
|
pass "Step 9 routes the chosen forge variant through the randsone gate (N9 hole stays closed)"
|
||||||
|
else
|
||||||
|
fail "Step 9 does not route forged variants through the randsone gate - a forged hook could reach the feed ungated (N9)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#8 deling) the shareability gate is present at Step 9, with the whole predicate
|
||||||
|
if deling_gated "$STEP9_N24"; then
|
||||||
|
pass "newsletter.md Step 9 runs the shareability gate: named forwarder + RESHARE/SAVE/SEND + no silent pass (E#8)"
|
||||||
|
else
|
||||||
|
fail "newsletter.md Step 9 missing the shareability gate - nobody asks who would forward the edition (E#8)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#8 escape hatch) a twice-failed reframe becomes an OPERATOR decision, never a lowered bar
|
||||||
|
if echo "$STEP9_N24" | grep -qiF "body-level finding" \
|
||||||
|
&& echo "$STEP9_N24" | grep -qF "/linkedin:pivot"; then
|
||||||
|
pass "Step 9 routes an unshareable edition to an operator decision (pivot or recorded fold-in), not to a softer read"
|
||||||
|
else
|
||||||
|
fail "Step 9 has no honest exit for an unshareable edition - the gate would resolve itself by lowering the bar (E#8)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#8 persistence) both new results are persisted where the resumption protocol can see them
|
||||||
|
if echo "$STEP9_N24" | grep -qF "articles.NN.hookForge" \
|
||||||
|
&& echo "$STEP9_N24" | grep -qF "articles.NN.personaSweep.deling"; then
|
||||||
|
pass "Step 9 persists the forge and the share verdict to edition-state (resumable, auditable)"
|
||||||
|
else
|
||||||
|
fail "Step 9 does not persist hookForge / personaSweep.deling - the gate leaves no trace (E#7/E#8)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#8 agent contract) konverter-modus carries the SECOND binary, with role + motive + action
|
||||||
|
if grep -qiF "who forwards this to a colleague, and why" "$PR_N24" 2>/dev/null \
|
||||||
|
&& grep -qiF "named forwarder role" "$PR_N24" 2>/dev/null \
|
||||||
|
&& grep -qiF "RESHARE / SAVE / SEND" "$PR_N24" 2>/dev/null; then
|
||||||
|
pass "persona-reviewer konverter-modus returns the deling binary with a named role and an action (E#8)"
|
||||||
|
else
|
||||||
|
fail "$PR_N24 konverter-modus has no share verdict - Step 9's gate has no judge (E#8)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#8 anti-flattery) a manufactured forwarder is the false green this gate exists to prevent
|
||||||
|
if grep -qiF "A generic answer is not an answer" "$PR_N24" 2>/dev/null; then
|
||||||
|
pass "persona-reviewer refuses a generic forwarder ('anyone in tech' names nobody)"
|
||||||
|
else
|
||||||
|
fail "$PR_N24 accepts a generic answer to who-forwards-this - the gate would pass on flattery (E#8)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (E#7 role separation) the jury judges the chosen krok; the forge stays the editor's pen
|
||||||
|
if grep -qiF "the forge is the editor's pen" "$PR_N24" 2>/dev/null; then
|
||||||
|
pass "persona-reviewer does not write or rank the forge variants (the jury judges, the editor writes)"
|
||||||
|
else
|
||||||
|
fail "$PR_N24 may rank or rewrite forge variants - that breaks the jury/editor separation (E#7)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Section 16aa: Debt-Sweep Anti-Erosion (N24.5) ---
|
||||||
|
echo "--- Debt-Sweep Anti-Erosion (N24.5) ---"
|
||||||
|
|
||||||
|
# Two defects that a one-time fix does NOT make terminal, because nothing watches them:
|
||||||
|
# (a) CHANGELOG duplicate version heading. A duplicated "## [x.y.z]" makes the changelog
|
||||||
|
# ambiguous about which entry a version actually is, and it was rediscovered SIX times
|
||||||
|
# before this lint existed - each time fixed by hand, each time silently reintroduced by
|
||||||
|
# the next release edit. Structural, so a grep settles it: no version number may head two
|
||||||
|
# sections. A deliberately retained historical entry keeps a distinct heading (the
|
||||||
|
# "-legacy" suffix), which is exactly what makes it visible instead of ambiguous.
|
||||||
|
# (b) Step 9 persisting state keys the schema template never declares. The template IS the
|
||||||
|
# schema-of-record an adopter copies; a key that only exists because a command writes it
|
||||||
|
# is undeclared state, and the drift is invisible until someone reads both files side by
|
||||||
|
# side. N9 set the precedent (randsoneGate), N24 repeated it twice (hookForge,
|
||||||
|
# personaSweep.deling) - the pattern recurs precisely because the additive keys are
|
||||||
|
# backward-compatible and therefore cost nothing to forget.
|
||||||
|
# The (a) predicate carries a non-vacuity self-test (mirrors 16p/16y/16z): a duplicate-detector
|
||||||
|
# that never fires would pass a duplicated changelog forever. All checks are UNCONDITIONAL.
|
||||||
|
|
||||||
|
dup_version_headings() { # reads stdin; echoes each version number that heads more than one section
|
||||||
|
grep -oE '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' | sort | uniq -d
|
||||||
|
}
|
||||||
|
|
||||||
|
# Self-test (in-memory, no temp files): the detector must fire on a known duplicate and stay
|
||||||
|
# silent on distinct headings - including the "-legacy" form used to disambiguate a kept entry.
|
||||||
|
SELFTEST_DUP=$(printf '## [1.2.0] - 2026-01-01\n## [1.1.0] - 2025-12-01\n## [1.2.0] - 2025-06-01\n' | dup_version_headings)
|
||||||
|
SELFTEST_CLEAN=$(printf '## [1.2.0] - 2026-01-01\n## [1.2.0-legacy] - 2025-06-01\n## [1.1.0] - 2025-12-01\n' | dup_version_headings)
|
||||||
|
if [ -n "$SELFTEST_DUP" ] && [ -z "$SELFTEST_CLEAN" ]; then
|
||||||
|
pass "duplicate-heading self-test: repeated version caught, distinct -legacy heading ignored"
|
||||||
|
else
|
||||||
|
fail "duplicate-heading self-test failed - the detector no longer distinguishes a duplicate from a distinct heading"
|
||||||
|
fi
|
||||||
|
|
||||||
|
CHANGELOG_DUPS=$(dup_version_headings < CHANGELOG.md)
|
||||||
|
if [ -z "$CHANGELOG_DUPS" ]; then
|
||||||
|
pass "CHANGELOG has no duplicated version heading (each '## [x.y.z]' heads exactly one section)"
|
||||||
|
else
|
||||||
|
fail "CHANGELOG version heading(s) duplicated - rename the historical entry (e.g. '-legacy') so the version is unambiguous:"
|
||||||
|
echo "$CHANGELOG_DUPS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (b) every key Step 9 persists must be declared in the schema template - shape AND _doc.
|
||||||
|
if python3 - <<'PYEOF' 2>/dev/null
|
||||||
|
import json, sys
|
||||||
|
d = json.load(open("config/edition-state.template.json"))
|
||||||
|
art = d["articles"]["01"]
|
||||||
|
doc = d["_doc"]
|
||||||
|
missing = [k for k in ("hookForge", "randsoneGate") if k not in art]
|
||||||
|
missing += ["personaSweep.deling"] if "deling" not in art.get("personaSweep", {}) else []
|
||||||
|
missing += [f"_doc.{k}" for k in ("hookForge", "randsoneGate", "personaSweep") if k not in doc]
|
||||||
|
sys.exit(1 if missing else 0)
|
||||||
|
PYEOF
|
||||||
|
then
|
||||||
|
pass "edition-state template declares every Step 9 key (hookForge, randsoneGate, personaSweep.deling) in shape + _doc"
|
||||||
|
else
|
||||||
|
fail "config/edition-state.template.json is missing a key Step 9 persists - undeclared state drifts from the schema-of-record"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
# --- Section 18: Assertion-Count Anti-Erosion (SC6) ---
|
# --- Section 18: Assertion-Count Anti-Erosion (SC6) ---
|
||||||
# The lint self-modifies its own checks, so a green run could mask a silently dropped
|
# The lint self-modifies its own checks, so a green run could mask a silently dropped
|
||||||
# assertion. Pin the total pass()+fail() invocations as a monotonic floor; the count
|
# assertion. Pin the total pass()+fail() invocations as a monotonic floor; the count
|
||||||
|
|
@ -3010,12 +3383,21 @@ echo ""
|
||||||
# wiring grep + always-present/refusal-included grep + report leads-with-baseline/no-eyeball
|
# wiring grep + always-present/refusal-included grep + report leads-with-baseline/no-eyeball
|
||||||
# compound grep + normal-variation-not-trend grep + report refusal-honest compound grep +
|
# compound grep + normal-variation-not-trend grep + report refusal-honest compound grep +
|
||||||
# WoW-loses-to-baseline grep + analyze verify-before-diagnose grep + analyze never-upgrade-
|
# WoW-loses-to-baseline grep + analyze verify-before-diagnose grep + analyze never-upgrade-
|
||||||
# refusal grep) = 251.
|
# refusal grep) = 251; +17 for N23's seventeen UNCONDITIONAL Section-16y checks (regeneration-gate
|
||||||
|
# self-test + template four-section grep + template distillate/defers grep + setup Step-3g grep +
|
||||||
|
# setup menu-reachability grep + setup delegation grep + brain-block one-file count + advisor
|
||||||
|
# distillation grep + advisor declared-over-inferred grep + two per-surface gate greps + five
|
||||||
|
# consumption greps + absent-brief graceful grep) = 268; +13 for N24's thirteen UNCONDITIONAL
|
||||||
|
# Section-16z checks (hook-forge self-test + shareability-gate self-test + Step-9 forge_honest
|
||||||
|
# compound grep + Step-9 per-variant measurement grep + Step-9 type-span grep + Step-9 body-trace
|
||||||
|
# grep + Step-9 randsone-routing grep + Step-9 deling_gated compound grep + Step-9 operator-exit
|
||||||
|
# grep + Step-9 persistence grep + persona-reviewer deling-binary compound grep + persona-reviewer
|
||||||
|
# generic-answer refusal grep + persona-reviewer forge/editor separation grep) = 281.
|
||||||
# NB: the floor tracks the deps-absent MINIMUM (conditional TS suites warn-skip and drop
|
# NB: the floor tracks the deps-absent MINIMUM (conditional TS suites warn-skip and drop
|
||||||
# the count), so it is bumped only by UNCONDITIONAL new checks — NOT pinned to the
|
# the count), so it is bumped only by UNCONDITIONAL new checks — NOT pinned to the
|
||||||
# deps-present TOTAL_CHECKS (that would zero the warn-skip margin and false-fail a fresh
|
# deps-present TOTAL_CHECKS (that would zero the warn-skip margin and false-fail a fresh
|
||||||
# clone). Runs last so TOTAL_CHECKS sees every prior check.
|
# clone). Runs last so TOTAL_CHECKS sees every prior check.
|
||||||
ASSERT_BASELINE_FLOOR=251
|
ASSERT_BASELINE_FLOOR=284
|
||||||
TOTAL_CHECKS=$((PASS + FAIL))
|
TOTAL_CHECKS=$((PASS + FAIL))
|
||||||
if [ "$TOTAL_CHECKS" -ge "$ASSERT_BASELINE_FLOOR" ]; then
|
if [ "$TOTAL_CHECKS" -ge "$ASSERT_BASELINE_FLOOR" ]; then
|
||||||
pass "assertion-count anti-erosion: $TOTAL_CHECKS checks >= baseline floor $ASSERT_BASELINE_FLOOR"
|
pass "assertion-count anti-erosion: $TOTAL_CHECKS checks >= baseline floor $ASSERT_BASELINE_FLOOR"
|
||||||
|
|
|
||||||
|
|
@ -148,9 +148,56 @@ line `<ISO-ts> exit=<code> <json>` to `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/lin
|
||||||
The nightly run is **deterministic-brief-only (C1)**: it re-renders the brief from the current store
|
The nightly run is **deterministic-brief-only (C1)**: it re-renders the brief from the current store
|
||||||
— freshness-aging drops stale trends, `surfacedCount` accumulates day-over-day — but does **not** poll
|
— freshness-aging drops stale trends, `surfacedCount` accumulates day-over-day — but does **not** poll
|
||||||
new sources. A double-fire on the same day is a safe no-op (RE-R3b per-day idempotency: byte-identical
|
new sources. A double-fire on the same day is a safe no-op (RE-R3b per-day idempotency: byte-identical
|
||||||
`.md`, `surfacedCount` not double-counted). The autonomous AI capture step (poll → score → capture
|
`.md`, `surfacedCount` not double-counted). This wrapper is deliberately unaware of the AI capture
|
||||||
before the brief) plugs into the documented seam in `run-daily.sh` as a later slice (e); a
|
step — that is a **different mechanism**, below (RE-R3f).
|
||||||
brief-history diff is also a later slice.
|
|
||||||
|
## Unattended AI discovery — `--headless` (RE-R3f, slice e)
|
||||||
|
|
||||||
|
The gap `run-daily.sh` leaves open on purpose: without new captures, the nightly brief is a
|
||||||
|
near-no-op. Slice (e) closes it by making the **actual discovery pass** — `/linkedin:trends`'s
|
||||||
|
poll → score → capture → brief loop, including the trend-spotter agent's web research — safe to
|
||||||
|
fire with no operator present, so Sunday discovery can happen unattended.
|
||||||
|
|
||||||
|
**This is not a bash/cron wrapper.** `/linkedin:trends --headless` needs a full Claude Code agent
|
||||||
|
turn (it spawns the `trend-spotter` subagent), which only the `claude` binary itself can run — a
|
||||||
|
shell script cannot replicate that. Two mechanisms exist for firing it unattended:
|
||||||
|
|
||||||
|
- **Claude Code's Desktop Scheduled Tasks (recommended).** A first-party, local, persistent
|
||||||
|
scheduler purpose-built for exactly this — unlike a hand-rolled cron job, it survives restarts,
|
||||||
|
keeps session history, and handles auth without a standing `ANTHROPIC_API_KEY`. Set it up to run
|
||||||
|
`/linkedin:trends --headless [--mode long-form]` on your Sunday cadence.
|
||||||
|
- **A hand-rolled `claude -p` invocation** (e.g. from cron/launchd directly), for a machine where
|
||||||
|
Desktop Scheduled Tasks isn't available. The exact recipe, verified against Claude Code's own
|
||||||
|
headless docs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude -p "/linkedin:trends --headless" \
|
||||||
|
--bare --plugin-dir "<path to the installed linkedin-studio plugin>" \
|
||||||
|
--permission-mode dontAsk \
|
||||||
|
--allowedTools "Read,Bash,Task,WebSearch,WebFetch" \
|
||||||
|
--output-format json
|
||||||
|
```
|
||||||
|
|
||||||
|
`--bare` skips plugin auto-discovery for a faster/cleaner scripted start, so `--plugin-dir` is
|
||||||
|
required alongside it. `--bare` also means no OAuth/keychain session — set `ANTHROPIC_API_KEY` in
|
||||||
|
the environment. `--permission-mode dontAsk` auto-denies anything not in `--allowedTools`
|
||||||
|
(including `AskUserQuestion`, unconditionally) instead of blocking on a prompt — the command's own
|
||||||
|
**Step 0.5 contract** (`commands/trends.md`) is what makes that safe rather than a silent abort.
|
||||||
|
If the research subagent can run long, raise `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` (default
|
||||||
|
10-minute wait ceiling on a background subagent) or set it to `0`.
|
||||||
|
|
||||||
|
**Do not trust the exit code or the model's prose as the success signal** — Claude Code's own docs
|
||||||
|
are explicit that a `-p` run's process exit reflects the CLI process, not the pass's semantic
|
||||||
|
outcome. Verify the pass actually did something the same way Step 3 of the command already does:
|
||||||
|
`CLI status --json`'s capture delta and whether today's `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/
|
||||||
|
linkedin-studio}/trends/morning-brief/<date>.md` exists. `--output-format json` also reports
|
||||||
|
`total_cost_usd` per run — log it if you care about the standing cost of a weekly unattended
|
||||||
|
research pass.
|
||||||
|
|
||||||
|
**Scope note:** RE-R3f adds no new source/test files here — the entire mechanism is the
|
||||||
|
`commands/trends.md` `--headless` contract (a prompt-file change) plus this documentation. No
|
||||||
|
`schedule.ts`/`run-daily.sh` change; that wrapper stays what RE-R3c built (deterministic brief-only,
|
||||||
|
above), and remains a separate, valid path for a plain daily re-render with no new discovery.
|
||||||
|
|
||||||
## Temporal overlay (RE-R3d)
|
## Temporal overlay (RE-R3d)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,9 @@ Read `references/engagement-frameworks.md` for hook types, story structures, and
|
||||||
|
|
||||||
**Standard Post (1,200-1,800 chars):**
|
**Standard Post (1,200-1,800 chars):**
|
||||||
1. **Hook (110-140 chars):** Grab attention
|
1. **Hook (110-140 chars):** Grab attention
|
||||||
2. **Context (200-300 chars):** Set up why this matters
|
2. **Context (250-350 chars):** Set up why this matters
|
||||||
3. **Insight/Argument (400-800 chars):** Main point with evidence
|
3. **Insight/Argument (550-850 chars):** Main point with evidence
|
||||||
4. **Implication (200-300 chars):** What this means for readers
|
4. **Implication (250-350 chars):** What this means for readers
|
||||||
5. **CTA (50-100 chars):** Engagement prompt
|
5. **CTA (50-100 chars):** Engagement prompt
|
||||||
|
|
||||||
**Critical formatting rules:**
|
**Critical formatting rules:**
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ description: |
|
||||||
first-hour engagement tactics, collaboration formats, and speaking pipeline management.
|
first-hour engagement tactics, collaboration formats, and speaking pipeline management.
|
||||||
|
|
||||||
This skill should be used when the user wants to build their network, find speaking opportunities, collaborate
|
This skill should be used when the user wants to build their network, find speaking opportunities, collaborate
|
||||||
with other thought leaders, improve their commenting strategy, or optimize their
|
with other creators in their niche, improve their commenting strategy, or optimize their
|
||||||
engagement routine.
|
engagement routine.
|
||||||
|
|
||||||
Triggers on: "speaking opportunities", "conference speaking", "collaboration", "co-author",
|
Triggers on: "speaking opportunities", "conference speaking", "collaboration", "co-author",
|
||||||
|
|
@ -67,7 +67,7 @@ Engagement isn't what you do after posting -- it's what enables successful posti
|
||||||
- Continue responding to all comments
|
- Continue responding to all comments
|
||||||
- Ask follow-up questions to deepen conversation
|
- Ask follow-up questions to deepen conversation
|
||||||
|
|
||||||
**What 15+ engagements in first hour looks like:**
|
**What an active first hour can look like (illustrative mix, not a threshold):**
|
||||||
- 8-10 thoughtful comments
|
- 8-10 thoughtful comments
|
||||||
- 3-5 shares
|
- 3-5 shares
|
||||||
- 2-3 profile visits with connection requests
|
- 2-3 profile visits with connection requests
|
||||||
|
|
@ -182,7 +182,7 @@ Before pursuing speaking, ensure:
|
||||||
4. Generate talk abstracts and bio
|
4. Generate talk abstracts and bio
|
||||||
5. Build outreach templates
|
5. Build outreach templates
|
||||||
|
|
||||||
**User: "I want to collaborate with other thought leaders"**
|
**User: "I want to collaborate with other creators in my niche"**
|
||||||
1. Identify potential partners with scoring
|
1. Identify potential partners with scoring
|
||||||
2. Suggest appropriate format for relationship stage
|
2. Suggest appropriate format for relationship stage
|
||||||
3. Generate outreach messages
|
3. Generate outreach messages
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue