fix(linkedin-studio): N24.5 — mekanisk gjeldssveip (plugin.json, CHANGELOG-dublett, de-niche, KTG, Step 9-nøkler) [skip-docs]

Fem grep-lokaliserte gjeldsposter fra STATE FUNN, pluss to anti-erosjons-lints
som gjør to av dem terminale i stedet for gjenoppdaget en gang til.

1. .claude-plugin/plugin.json description — v4.0.0-arvet release-notat med
   historiske tellinger («→ 19 agents», «→ 27 commands», «→ 29 commands»)
   erstattet av en beskrivelse av hva pluginen ER. Dette er teksten /plugin
   viser. Ingen tellinger i teksten: de drifter, og CHANGELOG.md eier historikken.

2. CHANGELOG hadde TO «## [0.6.0]»-overskrifter (2026-07-17 = ekte, 2026-02-07 i
   halen). Halens datert om til «## [0.6.0-legacy]». Sjette gangs gjenoppdagelse
   — derfor lint, se punkt 6.

3. De-niche: 7 «thought leader»-treff utenfor commands/ rettet — README (mot
   kommandoens egen frontmatter-ordlyd), skills/linkedin-networking (2:
   FUNN-listen navnga bare :9, men :185 var samme defekt), analytics-interpreter,
   analytics-tools-guide, collaborations-guide, linkedin-visual-style.
   BEVISST BEHOLDT: de 5 blocklist-linjene, de to negative eksemplene
   (opportunity-generation, det ironiske sitatet i video-strategy-guide), og
   network-builder:562 — et WebSearch-uttrykk som treffer hvordan ANDRE
   merker seg selv, allerede paret med «Creator».

4. Hardkodet «KTG» i generisk operatør-tiltale fjernet. FUNN navnga tre linjer i
   language-reviewer; klassen var 21 linjer over 4 agenter, newsletter.md,
   edition-state-templaten og specifics-bank sitt renderte NN-kilder.md-artefakt.
   BEVISST BEHOLDT som proveniens: CHANGELOG, tests/fixtures/* (de ER KTGs
   faktiske redaktørrunde, kalibreringsfasiten), adopter-notatene som ærlig
   flagger KTG-interne verktøy (contract-gate, specifics-bank), «KTG
   cover-directive» og «answers KTG's question» — attribusjon, ikke tiltale.

5. Step 9 persisterte tre nøkler templaten aldri deklarerte: randsoneGate (N9),
   hookForge + personaSweep.deling (N24). Nå deklarert i BÅDE artikkel-shapen og
   _doc. Additivt, schemaVersion forblir 1. _doc.hookForge bærer N22-invarianten
   videre: en variant har målt lengde / krok-type / kroppsspor og ALDRI en
   predikert engagement-score.

6. To nye ubetingede lints (Section 16aa), begge mutasjonstestet mot de FAKTISKE
   filene, ikke bare mot self-testen:
   - CHANGELOG-dublettvakt (+ in-memory non-vakuitets-self-test som skiller en
     ekte dublett fra en bevisst «-legacy»-overskrift)
   - Step 9-nøkkeldeklarasjon (shape + _doc)
   Mutasjon 1 (gjeninnfør dubletten) → rødt. Mutasjon 2 (fjern hookForge fra
   shapen) → rødt. Mutasjon 3 (fjern _doc.randsoneGate, fila fortsatt gyldig
   JSON) → rødt. Restaurert → grønt.

Ti suiter grønne: test-runner 300 → 303 (floor 281 → 284) · trends 300 ·
analytics 202 · hooks 191 · brain 134 · editions 72 · render 63 ·
specifics-bank 45 · tests 35 · contract-gate 33.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vMLTF7J13jJmgqwwJyivt
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 21:46:35 +02:00
commit e38f71f418
17 changed files with 97 additions and 31 deletions

View file

@ -1,7 +1,7 @@
{
"name": "linkedin-studio",
"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 03): 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": {
"name": "Kjell Tore Guttormsen"
},

View file

@ -551,7 +551,7 @@ Public release for open-source marketplace. All runtime bugs fixed, documentatio
- **Orphaned files** — deleted outdated docs/commands-reference.md
- **BACKLOG.md and DEVELOPMENT-LOG.md** — gitignored (internal development files)
## [0.6.0] - 2026-02-07
## [0.6.0-legacy] - 2026-02-07
### Note
First formal version. Previously unversioned.

View file

@ -156,7 +156,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: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: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

View file

@ -424,7 +424,7 @@ Remind them:
- Month 1: Learning mechanics (baseline)
- Month 3: Understanding YOUR patterns (above average)
- Month 6: Discovering insights from practice (exceptional)
- Month 12: Systematically generating unique perspectives (thought leader)
- Month 12: Systematically generating unique perspectives (recognized expert)
## References

View file

@ -181,7 +181,7 @@ flags; if you suppressed any, say how many and of what severity.
### Step 4 — Emit the report (the operator gates)
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
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
@ -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.
7. **Cap at eight; never truncate silently.** If you suppressed findings, say how
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
gate is `[OPERATØR]`.

View file

@ -28,7 +28,7 @@ tools: ["Read", "Grep"]
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
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.
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)
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
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.
@ -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.
6. **Cap at ten; never truncate silently.** If you suppressed findings, say how
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
gate is `[OPERATØR]`.
8. **The in-tree checklist is the operative source of truth.** It ships and is

View file

@ -188,7 +188,7 @@ corrected sentence is not. If you ever hand back edited prose, you have failed t
role.
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
unaccounted.
@ -313,7 +313,7 @@ already hedged in the draft.]
that fails verification is the headline catch.
8. **A contradicted claim is 🔴, not 🟡.** Never soften disproving evidence.
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

View file

@ -185,7 +185,7 @@ carries a **quote or line reference.** If you ever hand back edited prose, you
have failed the role.
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
(`[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)
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
holds the gate (`[OPERATØR]`).
@ -284,7 +284,7 @@ before lock. Operator decides fold-in; this is [OPERATØR].
polish. Sort BLOCK→REWORK→NICE.
7. **Cap at ten; never truncate silently.** If you suppressed findings, say how
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,
not a hard halt — the gate is `[OPERATØR]`.

View file

@ -1426,19 +1426,19 @@ and `persona-reviewer` never flags em-dash density (that is this step).
grep-able, the architecture checks need a read.
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
annotation:
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
`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**.
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
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
`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
@ -1458,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
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
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
re-run confirms clean):
@ -1646,10 +1646,10 @@ command runs — see `${CLAUDE_PLUGIN_ROOT}/commands/headless-review.md` for the
with a cross-archetype signal line. **Mark ⚑ converged** any passage two
independent cold reviewers flag — independent agreement with no shared session
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]`
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).
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).

View file

@ -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 ![alt](linkedin/NN/figN.png) 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.",
"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: 58 variants are forged for the distribution copy (the body stays locked, as everywhere in this step). The 110140-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.",
"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 56.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.",
"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`).",
@ -65,7 +68,8 @@
"personaSweep": {
"skeleton": null,
"resonance": null,
"conversion": null
"conversion": null,
"deling": null
},
"headlessReview": {
"frozenDraft": null,
@ -93,6 +97,8 @@
"figures": [],
"carousel": null
},
"hookForge": null,
"randsoneGate": null,
"pivots": [],
"foldIns": [],
"retro": null,

View file

@ -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 3: You understand YOUR patterns (above average)
- 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)
---

View file

@ -208,7 +208,7 @@ After establishing rapport:
**Time investment:** 3-5 hours (including creation and coordination)
**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:**
- 1-2 calls to align on framework

View file

@ -131,7 +131,7 @@ Before adding a visual, ask:
| Intermediate | Figma | Custom graphics, consistent templates | Free/$15/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

View file

@ -64,7 +64,7 @@ export function renderKilder(input: RenderKilderInput): string {
out.push(`# Kilder — ${title} (${articleId})`, "");
out.push(
"> 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", "");

View file

@ -62,7 +62,7 @@ describe("renderKilder — the NN-kilder.md artifact", () => {
const expected = `# Kilder — Maskinrommet (05)
> 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

View file

@ -3243,6 +3243,66 @@ 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) ---
# 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
@ -3337,7 +3397,7 @@ echo ""
# 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
# clone). Runs last so TOTAL_CHECKS sees every prior check.
ASSERT_BASELINE_FLOOR=281
ASSERT_BASELINE_FLOOR=284
TOTAL_CHECKS=$((PASS + FAIL))
if [ "$TOTAL_CHECKS" -ge "$ASSERT_BASELINE_FLOOR" ]; then
pass "assertion-count anti-erosion: $TOTAL_CHECKS checks >= baseline floor $ASSERT_BASELINE_FLOOR"

View file

@ -6,7 +6,7 @@ description: |
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
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.
Triggers on: "speaking opportunities", "conference speaking", "collaboration", "co-author",
@ -182,7 +182,7 @@ Before pursuing speaking, ensure:
4. Generate talk abstracts and bio
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
2. Suggest appropriate format for relationship stage
3. Generate outreach messages