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:
parent
77426135a5
commit
e38f71f418
17 changed files with 97 additions and 31 deletions
|
|
@ -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].",
|
||||
"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: 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.",
|
||||
"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.",
|
||||
"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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue