feat(linkedin-studio): N14 — foldIns-fangst + Step 11 retro + background-headless + språkregel-akkumulering [skip-docs]
Lukker sløyfen pluginen manglet: en rettelse operatøren gjør i utgave N håndheves i N+1 i stedet for å bli gjenoppdaget. Maskineriet fantes (foldIns-skjema + promote→ratify siden fix #1); det som manglet var wiring — ingenting skrev til køen og ingen fase tømte den. - Fangst (A2-F7): Steps 2.5/3a/5.5/6.5 appender rettelsen ordrett med trigger, decision "pending". Ubetinget og bevisst dum — å avgjøre ved fangst om noe "fortjener" en regel er nettopp slik køen holder seg tom og sløyfen dør. - Step 11 retro (A2-F8), ≤5 min ETTER scheduling: promoter køen med eksplisitt JA/NEI (mekanisk → atomisk contract-gate-promotering, teller kun med grønn --ratify; dømmekraft → operatørens språkregelfil; NEI → rejected, beholdes), effort-oppsummering fra MÅLT phaseLog (aldri re-estimert), og ÉN friksjons-spørring som besvares tilbake til operatøren. Faser 18 → 19; resumption-tabellen ruter scheduling → Step 11, retro → complete. - articles.NN.retro: additiv-valgfri (default null), schemaVersion forblir 1. - Background-headless (A2-F6): --background kjører pakken i en bakgrunnsagent som skriver rapporten til disk; drafting-sesjonen leser fila. Samme isolasjon som fersk sesjon, uten copy-paste-sømmen. Inline fan-out = eksplisitt fallback. - Språkregler (C-10): ${DATA}/language-rules/<lang>.md (opt-in, template). Leses TO ganger — av language-reviewer (fanger) og av Step 4 (forebygger). Shippet banliste forblir baseline; brukerfila utvider. - references/fold-in-loop.md (A2-F9): loopen dokumentert domene-generelt in-tree, så en adopter uten ekstern skrivekontrakt har hele sløyfen. Refs 28 → 29. Suiter (alle grønne): test-runner 197 → 217 (Section 16u: 18 ubetingede greps + non-vacuity self-test; fase-sveip 16 → 17 faser; floor 179 → 198) · hooks 174 · trends 300 · brain 134 · editions 72 · specifics-bank 45 · contract-gate 33 · tests 35 · render 60. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxvWAjte7vPcF79QeSRvRJ
This commit is contained in:
parent
677eab9294
commit
f0532dce3f
9 changed files with 711 additions and 30 deletions
|
|
@ -22,7 +22,8 @@
|
|||
"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)",
|
||||
"hook-conversion-gate — persona gate on distribution text post-lock: would YOU click? (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)"
|
||||
],
|
||||
"articleStatusValues": ["pending", "in-progress", "locked", "scheduled"],
|
||||
"phaseLog": "Per-article phase-transition log (N12 / A1-12) — the raw material for measuring lead time. Additive-optional (default [], NO schemaVersion bump — same pattern as sourceTrendId/targetLevel/language; an edition produced before N12 simply has none and the register treats its absence as \"not measured\"). Shape: [ { phase, completedAt } ] in transition order, where phase is one of the canonical _doc.phases identifiers and completedAt is an ISO-8601 timestamp. Written DETERMINISTICALLY, never by hand: every /linkedin:newsletter phase transition runs `scripts/editions` (`node --import tsx src/cli.ts register-upsert --edition-state <path>`), which appends the entry for the just-written currentPhase AND mirrors the edition into the editions register (${LINKEDIN_STUDIO_DATA}/editions/register.json) in one call — the log and the register cannot drift apart because nothing writes one without the other. Idempotent against an immediately repeated transition (a re-run of the same step logs once), but a phase that recurs AFTER another one is logged again: /linkedin:pivot legitimately sends an edition back through cleared gates, and that second pass is real production time. Per-article rather than top-level because lead time is a property of an edition, mirroring articles.NN.phase. The register is a MIRROR of this file, never a source of truth — losing it costs telemetry, not an edition.",
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
"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.",
|
||||
"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`).",
|
||||
"targetLevel": "Per-article target-level for the edition (N10 / C-8). Additive-optional (default null, NO schemaVersion bump — same pattern as sourceTrendId/language; an edition that never resolves one leaves it null and the reader agents fall back to each persona's own `ekspertise` field). Fixes WHERE this edition sits on the configured target-level span (references/longform-quality-rules.md + config/personas.template.md): it tells persona-reviewer which end is the reader-facing (primær) end and which is the mandatory-secondary end, so «practically usable at all levels» is judged from both ends rather than one baked-in reader role. Set at Step 1 of /linkedin:newsletter — the N7 trend bridge / N6 proposal layer pre-fills it from the trend record's `targetLevel` (scripts/trends), the operator confirms or edits it during calibration, and it is persisted here at the Step 1.5 checkpoint (alongside sourceTrendId). Free-text level descriptor (e.g. \"line leader → solution engineer\", \"beginner → practitioner\"), NOT an enum — the span is domain-general and defined by the operator's persona set, never hardcoded."
|
||||
|
|
@ -93,6 +95,7 @@
|
|||
},
|
||||
"pivots": [],
|
||||
"foldIns": [],
|
||||
"retro": null,
|
||||
"locked": false,
|
||||
"scheduled": null,
|
||||
"sourceTrendId": null,
|
||||
|
|
|
|||
70
config/language-rules.template.md
Normal file
70
config/language-rules.template.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
language: en
|
||||
schemaVersion: 1
|
||||
---
|
||||
|
||||
# Accumulated language rules — `<language>`
|
||||
|
||||
> **OPT-IN — the plugin never writes this file for you.** Copy it into your data
|
||||
> dir, one file per review language, and let it grow:
|
||||
>
|
||||
> ```bash
|
||||
> mkdir -p "${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/language-rules"
|
||||
> cp "${CLAUDE_PLUGIN_ROOT}/config/language-rules.template.md" \
|
||||
> "${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/language-rules/en.md"
|
||||
> ```
|
||||
>
|
||||
> Name the file after the edition's `language` value (`en.md`, `no.md`, …).
|
||||
> While it is absent, the shipped baseline applies alone and nothing warns.
|
||||
|
||||
This file is **yours**. It holds the language corrections you have made that you
|
||||
do not want to make again — the judgment half of the fold-in loop
|
||||
(`references/fold-in-loop.md`). Two things read it:
|
||||
|
||||
- **`agents/language-reviewer.md`** (Step 6.5, cold review) — grades against
|
||||
these rules in addition to its five standard checks, so a correction you
|
||||
confirmed once is caught every edition after.
|
||||
- **`/linkedin:newsletter` Step 4** (consistency + quality) — reads it while the
|
||||
draft is still being cleaned, so the defect is *prevented*, not only caught.
|
||||
|
||||
**`/linkedin:newsletter` Step 11** (retro) appends to it: when you promote a
|
||||
judgment-class fold-in, the rule lands here with the edition that surfaced it.
|
||||
Mechanical corrections do **not** come here — those become deterministic
|
||||
`scripts/contract-gate` rules instead.
|
||||
|
||||
## Relationship to the shipped baseline
|
||||
|
||||
`references/longform-quality-rules.md` rule 3 (the AI-slop ban-list) is the
|
||||
**baseline** and is never edited by the loop. This file **extends** it. Where the
|
||||
two disagree, the stricter one wins; if you actually want to overturn a baseline
|
||||
rule, say so explicitly under *Deliberate exceptions* below — an unexplained
|
||||
contradiction reads as drift to the next reviewer.
|
||||
|
||||
## Format
|
||||
|
||||
One rule per bullet. Keep each rule **checkable** — a reviewer must be able to
|
||||
tell from the text alone whether it was broken. Add the date and the edition that
|
||||
surfaced it, so a rule that turns out to be wrong can be traced back and dropped.
|
||||
|
||||
```markdown
|
||||
- **<what to avoid>** → <what to do instead>. _(<YYYY-MM-DD>, edition <NN>)_
|
||||
```
|
||||
|
||||
Good: *«"leverage" as a verb → use the plain verb ("use", "build on").»*
|
||||
Bad: *«write better sentences»* — nothing can check that.
|
||||
|
||||
---
|
||||
|
||||
## Rules
|
||||
|
||||
<!-- Step 11 appends promoted judgment-class fold-ins below this line. -->
|
||||
|
||||
_(empty — no rules accumulated yet)_
|
||||
|
||||
## Deliberate exceptions
|
||||
|
||||
Baseline rules you have consciously decided not to follow, **with the reason**.
|
||||
Keep this short; a long list here means the baseline is wrong for your practice
|
||||
and should be discussed, not quietly bypassed.
|
||||
|
||||
_(none)_
|
||||
Loading…
Add table
Add a link
Reference in a new issue