feat(linkedin): newsletter Step 7-10 lock, delivery, hook-gate, schedule (S10)
This commit is contained in:
parent
fba70870ce
commit
f24c6e30f7
1 changed files with 204 additions and 13 deletions
|
|
@ -70,12 +70,12 @@ single most important correction from the Seres process (plan §0.4, principle 5
|
||||||
| 9 | **Hook / conversion gate** | persona gate on the distribution text post-lock: "would YOU click?" | **`persona-reviewer`** (conversion mode) |
|
| 9 | **Hook / conversion gate** | persona gate on the distribution text post-lock: "would YOU click?" | **`persona-reviewer`** (conversion mode) |
|
||||||
| 10 | **Scheduling** | register the edition in the plugin queue/state for native scheduling | `hooks/scripts/queue-manager.mjs` |
|
| 10 | **Scheduling** | register the edition in the plugin queue/state for native scheduling | `hooks/scripts/queue-manager.mjs` |
|
||||||
|
|
||||||
> **Build status:** Steps 0–6 are implemented below. Steps 7–10 are added in a
|
> **Build status:** all 11 phases (Steps 0–10) are implemented below. This
|
||||||
> subsequent build session (plan step S10). Until then, this command takes an
|
> command takes an edition end-to-end: load → calibration → verified research →
|
||||||
> edition from load → calibration → verified research → draft →
|
> draft → consistency/quality → fact-check sweep → pre-lock persona sweep →
|
||||||
> consistency/quality → fact-check sweep → pre-lock persona sweep, persisting
|
> optional annotation → LOCK/delivery → post-lock hook gate → scheduling,
|
||||||
> each phase to `edition-state.json` and the HANDOVER and stopping cleanly
|
> persisting each phase to `edition-state.json` and the HANDOVER and stopping
|
||||||
> between sessions.
|
> cleanly between sessions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -477,14 +477,205 @@ Next: Step 7 — Annotation (optional), then Step 8 — LOCK → delivery.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Steps 7–10 (added in a subsequent build session)
|
## Step 7: Annotation — optional annotatable review HTML
|
||||||
|
|
||||||
Optional annotation (render annotatable review HTML), lock/delivery (POST.html
|
Before locking, you may render the draft as a self-contained, annotatable HTML
|
||||||
"all in one place"), the post-lock hook/conversion gate (`persona-reviewer` in
|
page for one last manual read in the browser (the same pencil-toggle annotation
|
||||||
konverter-modus), and scheduling are implemented in plan step S10. Each will
|
surface the render scripts ship). This step is **optional** — skip it if the
|
||||||
append its phase here, reading the phase contract from
|
editor is satisfied with the in-session draft. It does not gate lock.
|
||||||
`config/edition-state.template.json` and (once extracted in S12) the long-form
|
|
||||||
quality rules from `references/longform-quality-rules.md`.
|
**Procedure:**
|
||||||
|
|
||||||
|
1. **Confirm the draft path.** The consistency- and persona-passed draft from
|
||||||
|
Steps 4–6 is the `NN-utkast.md` (NN = zero-padded edition number) in the
|
||||||
|
series folder, with YAML front matter (`title`, etc.).
|
||||||
|
|
||||||
|
2. **Render the review HTML.** `render/build-html.mjs` writes to `./review/`
|
||||||
|
relative to the *current working directory*, so run it **with cwd = the
|
||||||
|
series folder** (not the plugin). Pass the draft file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <serie-mappe> && node "${CLAUDE_PLUGIN_ROOT}/render/build-html.mjs" NN-utkast.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check the exit code (N3 — do not assume success).** A non-zero exit (e.g.
|
||||||
|
missing file → the script prints `Fant ikke:` and continues, or no-args →
|
||||||
|
exit 1) means no review HTML was produced. Report the failure and the
|
||||||
|
`build-html.mjs` stderr; do NOT advance the phase on a silent failure.
|
||||||
|
|
||||||
|
3. **Hand off the link.** On success the script prints `Skrev <path> (<KB>)`.
|
||||||
|
Surface `<serie-mappe>/review/NN-utkast.html` as a `file://` link for the
|
||||||
|
editor's manual annotation pass. Fold any resulting edits back **by
|
||||||
|
tightening** (the Step 4 rule still holds) — then, if the edit was
|
||||||
|
substantive, re-run the affected sweep (Step 5 or 6) before proceeding.
|
||||||
|
|
||||||
|
4. **Persist.** Set `currentPhase: "annotation"` in `edition-state.json` and
|
||||||
|
append an "annotation rendered (optional) → next: lock/delivery" pointer to
|
||||||
|
the HANDOVER §6. If skipped, note "annotation skipped" and move on.
|
||||||
|
|
||||||
|
```
|
||||||
|
Annotation (optional).
|
||||||
|
- Rendered: <serie-mappe>/review/NN-utkast.html (or: skipped)
|
||||||
|
- build-html exit: 0 (else: non-zero — review HTML NOT produced, see stderr)
|
||||||
|
Next: Step 8 — LOCK → delivery.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 8: LOCK → delivery — POST.html "all in one place"
|
||||||
|
|
||||||
|
This is the **lock**. Only enter it once the Step 6 persona sweep returned a
|
||||||
|
clean primær JA and the Step 5 fact-check has no unresolved 🔴. Locking
|
||||||
|
produces the editor's single delivery artifact — `POST.html`, the
|
||||||
|
"all-in-one-place" page that carries the edition text plus its distribution
|
||||||
|
(delingstekst) copy, ready to paste into LinkedIn.
|
||||||
|
|
||||||
|
> **Order assertion (enforced).** Lock (Step 8) runs AFTER the pre-lock persona
|
||||||
|
> sweep (Step 6) and BEFORE the hook/conversion gate (Step 9). Reversing lock
|
||||||
|
> and the pre-lock sweep reproduces the exact Seres failure (reopening locked
|
||||||
|
> texts) — see Step 6. The post-lock hook-gate (Step 9) judges only the
|
||||||
|
> distribution hook and never reopens the locked body. `[GATE]`
|
||||||
|
|
||||||
|
**Procedure:**
|
||||||
|
|
||||||
|
1. **Confirm lock preconditions.** In `edition-state.json`: the article's
|
||||||
|
`factcheckLog` has no open 🔴 and `personaSweep.resonance` recorded a primær
|
||||||
|
JA. If either is missing, STOP — return to Step 5/6. Do not lock past an
|
||||||
|
open gate.
|
||||||
|
|
||||||
|
2. **Confirm the delivery inputs exist in the series folder.**
|
||||||
|
`render/build-linkedin.mjs` reads, relative to cwd:
|
||||||
|
- `linkedin/edition-config.json` — calendar, freshness, cover credit/caption
|
||||||
|
- `linkedin/edition-delingstekst.md` — the per-edition distribution text
|
||||||
|
(and the `samle` post, built unconditionally)
|
||||||
|
|
||||||
|
If either file is absent the script throws on read — verify both are present
|
||||||
|
before invoking.
|
||||||
|
|
||||||
|
3. **Render POST.html.** Run with **cwd = the series folder** (the script
|
||||||
|
resolves `linkedin/` from cwd and writes `linkedin/NN/POST.html`). The draft
|
||||||
|
filename MUST keep its two-digit `NN` prefix or the script skips it
|
||||||
|
(`↷ hopper over … (ikke NN-prefiks)`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <serie-mappe> && node "${CLAUDE_PLUGIN_ROOT}/render/build-linkedin.mjs" NN-utkast.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check the exit code (N3 — do not assume success).** Confirm exit 0 AND
|
||||||
|
that `linkedin/NN/POST.html` now exists (the `✓ linkedin/NN/POST.html`
|
||||||
|
line). A skip-warning with exit 0 but no NN file means the prefix was wrong —
|
||||||
|
treat as failure, rename, re-run. Surface any throw (missing config /
|
||||||
|
delingstekst) with its stderr.
|
||||||
|
|
||||||
|
4. **Lock the article.** Set `locked: true`, `status: "locked"`, and
|
||||||
|
`currentPhase: "lock-delivery"` in `edition-state.json`. Surface
|
||||||
|
`<serie-mappe>/linkedin/NN/POST.html` as a `file://` link. From here the
|
||||||
|
body is frozen — Step 9 may only adjust the distribution hook, never the
|
||||||
|
locked edition text.
|
||||||
|
|
||||||
|
```
|
||||||
|
LOCK → delivery.
|
||||||
|
- Preconditions: factcheck 🔴 = none, persona resonans primær = JA (else: STOP)
|
||||||
|
- Delivered: <serie-mappe>/linkedin/NN/POST.html
|
||||||
|
- build-linkedin exit: 0 + POST.html present (else: non-zero / skip — NOT locked)
|
||||||
|
- Article status: locked
|
||||||
|
Next: Step 9 — Hook / conversion gate (post-lock).
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 9: Hook / conversion gate — "would YOU click?" (AFTER lock)
|
||||||
|
|
||||||
|
The locked edition still has to earn the click. The **distribution text** — the
|
||||||
|
delingstekst hook that fronts the post in the feed — now faces a final binary
|
||||||
|
gate: would the primær persona, scrolling, actually stop and open this? This is
|
||||||
|
the post-lock conversion sweep, distinct from the pre-lock resonance sweep
|
||||||
|
(Step 6): it judges the **hook only**, binary, never the body.
|
||||||
|
|
||||||
|
> **Order assertion (enforced).** This hook-gate (Step 9) runs AFTER lock
|
||||||
|
> (Step 8) — it operates on the distribution text of an already-locked edition
|
||||||
|
> and must never reopen the locked body. If the hook fails, you revise the
|
||||||
|
> *delingstekst* (and re-render via Step 8's `build-linkedin.mjs`), not the
|
||||||
|
> edition text. This ordering is the inverse of the resonance sweep, which runs
|
||||||
|
> BEFORE lock — keeping the two apart is what fixed the Seres process. `[GATE]`
|
||||||
|
|
||||||
|
**Procedure:**
|
||||||
|
|
||||||
|
1. **Take the distribution hook** — the first two lines of the edition's entry
|
||||||
|
in `linkedin/edition-delingstekst.md` (and the `samle` hook, if shipping the
|
||||||
|
collected post). This is what the reader sees before "…see more".
|
||||||
|
|
||||||
|
2. **Run `persona-reviewer` in conversion mode** for the **primær** persona
|
||||||
|
only, from THIS command layer in the foreground. Pass
|
||||||
|
**`mode: konverter`** (the after-lock, hook-only mode — NOT resonans). The
|
||||||
|
agent returns a single binary verdict, **JA / NEI**, on «would YOU click?» —
|
||||||
|
no axis scoring, no flags, no rewritten copy (principle: the jury judges,
|
||||||
|
the editor writes).
|
||||||
|
|
||||||
|
3. **Gate on the binary.**
|
||||||
|
- **JA** → the hook converts. Proceed to Step 10.
|
||||||
|
- **NEI** → the hook does not earn the click. Revise the **delingstekst hook
|
||||||
|
only** (sharpen the krok by tightening — close the gap, hold the body
|
||||||
|
frozen), **re-render POST.html** via Step 8's `build-linkedin.mjs` (the
|
||||||
|
body stays locked; only the distribution copy changed), and re-run the
|
||||||
|
conversion check. Loop until JA. `[GATE]`
|
||||||
|
|
||||||
|
4. **Persist.** Record the conversion verdict in
|
||||||
|
`edition-state.json` → `articles.NN.personaSweep.conversion` (and HANDOVER §5),
|
||||||
|
and set `currentPhase: "hook-conversion-gate"`.
|
||||||
|
|
||||||
|
```
|
||||||
|
Hook / conversion gate (post-lock).
|
||||||
|
- Primær persona: <name> mode: konverter (hook only)
|
||||||
|
- Verdict: JA — hook converts (else: NEI — revise delingstekst hook, re-render, re-check)
|
||||||
|
- Body: untouched (locked in Step 8)
|
||||||
|
Gate: [PASS — JA] (else loop on the distribution hook)
|
||||||
|
Next: Step 10 — Scheduling.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 10: Scheduling — register the edition in the plugin queue
|
||||||
|
|
||||||
|
The locked, conversion-passed edition is ready to ship. Register it in the
|
||||||
|
plugin's native scheduling queue so it shows up in `/linkedin:calendar`,
|
||||||
|
`/linkedin:publish`, and the posting-time reminders — the same queue the
|
||||||
|
short-form pipeline uses. The edition is now a first-class scheduled post.
|
||||||
|
|
||||||
|
**Procedure:**
|
||||||
|
|
||||||
|
1. **Pick the slot.** Confirm the target `scheduled_date` (YYYY-MM-DD) and
|
||||||
|
`scheduled_time` from the edition-config calendar / the series brief. If the
|
||||||
|
slot is unset, ask once (Step 1's calibration may already have settled it).
|
||||||
|
|
||||||
|
2. **Register via `queue-manager.mjs`.** Add one queue entry for the edition,
|
||||||
|
reusing the short-form queue contract — `queueAdd(id, draftPath, schedDate,
|
||||||
|
schedTime, pillar, format, hookPreview, charCount)`:
|
||||||
|
- `id` — stable edition id (e.g. `<series-slug>-NN`)
|
||||||
|
- `draftPath` — the locked `linkedin/NN/POST.html`
|
||||||
|
- `format` — `newsletter` (so calendar/reporting can distinguish long-form)
|
||||||
|
- `hookPreview` — the conversion-passed distribution hook (first ~80 chars)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node -e 'import("'"${CLAUDE_PLUGIN_ROOT}"'/hooks/scripts/queue-manager.mjs").then(q => q.queueAdd("<series-slug>-NN","<serie-mappe>/linkedin/NN/POST.html","YYYY-MM-DD","HH:MM","<pillar>","newsletter","<hook ~80c>",<charCount>))'
|
||||||
|
```
|
||||||
|
|
||||||
|
The function appends to `assets/drafts/queue.json` with `status:
|
||||||
|
"scheduled"` and returns the new entry.
|
||||||
|
|
||||||
|
3. **Persist + close the edition.** Set the article's `status: "scheduled"`,
|
||||||
|
`scheduled: "<YYYY-MM-DD HH:MM>"`, and `currentPhase: "scheduling"` in
|
||||||
|
`edition-state.json`. Append a closing "edition scheduled → ready for
|
||||||
|
`/linkedin:publish` on <date>" pointer to the HANDOVER §6. The pipeline is
|
||||||
|
complete.
|
||||||
|
|
||||||
|
```
|
||||||
|
Scheduling.
|
||||||
|
- Queue entry: <series-slug>-NN → assets/drafts/queue.json (status: scheduled)
|
||||||
|
- Slot: YYYY-MM-DD HH:MM format: newsletter
|
||||||
|
- Article status: scheduled
|
||||||
|
Edition complete. Visible in /linkedin:calendar; mark live with /linkedin:publish.
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue