fix(linkedin): close dogfood friction (S14)
Close all 9 friction points from the S13 newsletter dogfood (operator elected to fix F6-F9 rather than defer): - F1: namespace all subagent_type calls in newsletter.md to linkedin-thought-leadership:<name> (4 sites + canonical note) - F2: document agent invocation form + reload requirement in CLAUDE.md + README.md (reload itself is an operator action) - F3: add edition-config / edition-delingstekst / edition-HANDOVER templates under config/ + wire into Steps 0 and 8 + footer - F4: reconcile draft path to <serie>/NN-utkast.md (series root) - F5: de-hardcode series root (explicit arg / LTL_SERIES_ROOT / default) - F6: config-derive carousel editions (remove Seres CAROUSEL set); correct samle comment - F7: build-html.mjs exits non-zero when zero HTML produced - F8: guard parseDelingstekst (graceful ENOENT) + correct Step 8 wording - F9: relocate agents/README.md -> docs/agents-capability-matrix.md Re-tested: 87/87 plugin tests pass; build-html/build-linkedin behavior re-verified live. Per-item outcomes logged in dogfood-S13-friction.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
adfa2085fc
commit
92e0a0b4f5
11 changed files with 339 additions and 54 deletions
|
|
@ -31,8 +31,10 @@ This command is **fundamentally different** from the short-form commands:
|
|||
phases* (fact-check sweep + persona sweep + hook-gate), NOT by the short-form
|
||||
`PreToolUse` content-gatekeeper/voice-guardian hooks (those stay short-form-only).
|
||||
- **State lives in the series folder, not the plugin.** Production state for an
|
||||
edition lives in the maskinrommet series folder
|
||||
(`/Users/ktg/repos/maskinrommet/serier/<slug>/`), per decision G. The plugin
|
||||
edition lives in the resolved **series root** (Step 0) — by default the
|
||||
maskinrommet series folder
|
||||
(`${LTL_SERIES_ROOT:-/Users/ktg/repos/maskinrommet/serier}/<slug>/`), per
|
||||
decision G, but any explicit path works (Step 0 resolution order). The plugin
|
||||
ships the *schema* (`config/edition-state.template.json`) and this command;
|
||||
the edition's actual state + drafts live with the series.
|
||||
- **Multi-session by design.** A single edition spans several sessions. Every
|
||||
|
|
@ -45,6 +47,12 @@ This command is **fundamentally different** from the short-form commands:
|
|||
(Step 6) — is launched directly from THIS command, in the foreground.** Never
|
||||
delegate the fan-out to a nested background agent.
|
||||
|
||||
> **Agent invocation form (required).** Plugin agents resolve only under their
|
||||
> namespaced type — `subagent_type: linkedin-thought-leadership:<name>` (e.g.
|
||||
> `linkedin-thought-leadership:fact-checker`), never the bare `<name>`. A bare
|
||||
> `subagent_type` does not resolve and the `Task` call fails. Every
|
||||
> `subagent_type` below is written in the namespaced form for this reason.
|
||||
|
||||
> **Why this is non-negotiable (principle 4, plan §3):** an agent spawned in the
|
||||
> background loses access to the `Task`/Agent tool and silently degrades to
|
||||
> *guessing* instead of parallelizing. The command layer (this session) is the
|
||||
|
|
@ -84,9 +92,19 @@ single most important correction from the Seres process (plan §0.4, principle 5
|
|||
Resume state first — this command is multi-session, so always reconstruct where
|
||||
the edition left off before doing anything.
|
||||
|
||||
1. **Locate the series folder.** If the user named a series/edition, use it.
|
||||
Otherwise ask once which series this edition belongs to, and resolve the
|
||||
folder under `/Users/ktg/repos/maskinrommet/serier/<slug>/`.
|
||||
1. **Locate the series folder.** Resolve a **series root** — the folder that
|
||||
holds this edition. Resolution order:
|
||||
- If the operator passed an explicit path (e.g. `/linkedin:newsletter
|
||||
<path-to-serie>`), use it verbatim. This is how the edition is produced for
|
||||
any repo, a throwaway fixture, or a non-default location.
|
||||
- Otherwise derive it from the series slug under the **default series base**,
|
||||
`${LTL_SERIES_ROOT:-/Users/ktg/repos/maskinrommet/serier}/<slug>/`. The
|
||||
`LTL_SERIES_ROOT` env-var overrides the base without editing this command;
|
||||
the maskinrommet path is the default, not the only path.
|
||||
- If neither a path nor a resolvable slug is available, ask once which series
|
||||
(or series-root path) this edition belongs to.
|
||||
All later steps treat `<serie>` as this resolved series root; nothing below
|
||||
re-hardcodes the maskinrommet path.
|
||||
2. **Read edition-state** (`<serie>/linkedin/edition-state.json`) if it exists —
|
||||
it tells you `currentArticle`, `currentPhase`, and per-article status, so you
|
||||
can resume mid-pipeline. The schema is documented in
|
||||
|
|
@ -95,10 +113,11 @@ the edition left off before doing anything.
|
|||
you will create one at the end of Step 2.
|
||||
3. **Read the edition-HANDOVER** (`<serie>/HANDOVER.md` or
|
||||
`<serie>/linkedin/edition-HANDOVER.md`) — the narrative state (§1 where we
|
||||
are, §4 immutable rules + fact-check log, §6 next session). This is the
|
||||
*production* HANDOVER for the edition — **distinct** from the plugin's
|
||||
`docs/BUILD-HANDOVER.local.md`, which governs building the plugin itself.
|
||||
Do not confuse or merge them.
|
||||
are, §4 immutable rules + fact-check log, §6 next session). The structure is
|
||||
defined by `${CLAUDE_PLUGIN_ROOT}/config/edition-HANDOVER.template.md` (copy +
|
||||
fill it when starting a new edition). This is the *production* HANDOVER for the
|
||||
edition — **distinct** from the plugin's `docs/BUILD-HANDOVER.local.md`, which
|
||||
governs building the plugin itself. Do not confuse or merge them.
|
||||
4. **Read the voice profile** — `assets/voice-samples/authentic-voice-samples.md`
|
||||
and anything else under `assets/voice-samples/`. Long-form must match the
|
||||
author's voice; this is the reference for every drafting and review phase.
|
||||
|
|
@ -275,7 +294,8 @@ Turn the verified research notes (Step 2) into a full draft. This is a
|
|||
|
||||
> **This phase may span multiple sessions.** A long edition can exceed a single
|
||||
> session's context budget. If you approach the budget mid-draft, stop cleanly,
|
||||
> write the partial draft to the edition folder, record `currentPhase: "draft"`
|
||||
> write the partial draft to the series root as `<serie>/NN-utkast.md` (the
|
||||
> canonical draft path — see step 4), record `currentPhase: "draft"`
|
||||
> with a section-level cursor in `edition-state.json`, and append a precise
|
||||
> "draft resumes at section <X>" pointer to the edition-HANDOVER §6. The next
|
||||
> session re-reads Step 0, picks up the cursor, and continues. Never start the
|
||||
|
|
@ -298,22 +318,28 @@ Turn the verified research notes (Step 2) into a full draft. This is a
|
|||
|
||||
3. **Draft with the `content-repurposer` muscle.** Reuse `agents/content-repurposer.md`
|
||||
(its article→long-form conversion discipline) for the section-to-prose work —
|
||||
invoke it via `Task` for individual sections when useful, *from this command
|
||||
layer* (foreground, principle 4). The command owns assembly and voice; the
|
||||
invoke it via `Task` (`subagent_type: linkedin-thought-leadership:content-repurposer`)
|
||||
for individual sections when useful, *from this command layer* (foreground,
|
||||
principle 4). The command owns assembly and voice; the
|
||||
agent assists with conversion. The draft is voice-matched by THIS session, not
|
||||
self-certified for voice — voice-match remains an `[OPERATØR]` / `[GATE:
|
||||
voice-trainer]` judgment, never auto-passed (plan §10.0).
|
||||
|
||||
4. **Write the draft** to the edition folder (`<serie>/linkedin/<article>.draft.md`),
|
||||
set `currentPhase: "draft"` in `edition-state.json`, and append a
|
||||
"draft complete → next: consistency/quality" pointer to the HANDOVER §6.
|
||||
4. **Write the draft** to the **series root** as `<serie>/NN-utkast.md` (NN =
|
||||
zero-padded edition number — the SAME filename Steps 7 and 8 render from).
|
||||
This is the single canonical draft path: `render/build-html.mjs` (Step 7) and
|
||||
`render/build-linkedin.mjs` (Step 8) both consume `NN-utkast.md` from cwd, and
|
||||
the renderer **silently skips** any draft without an `NN` prefix. Do NOT write
|
||||
to `linkedin/<article>.draft.md` — that path is skipped at render. Set
|
||||
`currentPhase: "draft"` in `edition-state.json`, and append a "draft complete →
|
||||
next: consistency/quality" pointer to the HANDOVER §6.
|
||||
|
||||
```
|
||||
Draft complete (or: partial — resumes at section <X>).
|
||||
- Premise established: <one line>
|
||||
- Key points drafted: <N>/<N>
|
||||
- Voice-match: [OPERATØR]/[GATE: voice-trainer] — NOT self-certified
|
||||
Draft written: <serie>/linkedin/<article>.draft.md
|
||||
Draft written: <serie>/NN-utkast.md (series root, NN-prefixed — Steps 7/8 render this exact file)
|
||||
Next: Step 4 — Consistency + quality.
|
||||
```
|
||||
|
||||
|
|
@ -396,7 +422,7 @@ because it "feels" right or because it sits in your own research notes.
|
|||
block can be verified independently without overlap.
|
||||
|
||||
3. **Fan out in parallel — issue all N `fact-checker` calls in a SINGLE message**
|
||||
(multiple `Task` tool-uses in one turn, `subagent_type: fact-checker`) so they
|
||||
(multiple `Task` tool-uses in one turn, `subagent_type: linkedin-thought-leadership:fact-checker`) so they
|
||||
run concurrently, from THIS command layer in the foreground (principle 4, plan
|
||||
§3). Each call gets one claim-block and returns the agent's standard
|
||||
verification log + risk-sort (🔴/🟡/🟢) + gate decision (PASS/REWORK/BLOCK).
|
||||
|
|
@ -466,7 +492,8 @@ reopening locked texts — the biggest single process error of the series (plan
|
|||
|
||||
2. **Fan out one `persona-reviewer` call per persona, in parallel** — issue them
|
||||
in a SINGLE message (multiple `Task` tool-uses, `subagent_type:
|
||||
persona-reviewer`), from THIS command layer in the foreground (principle 4).
|
||||
linkedin-thought-leadership:persona-reviewer`), from THIS command layer in the
|
||||
foreground (principle 4).
|
||||
Pass each call its persona name and **`mode: resonans`** (the before-lock mode
|
||||
— all six axes, ≤5 flags as direction). This is NOT conversion mode, which is
|
||||
the post-lock hook-gate in Step 9. One persona per run — never mix two.
|
||||
|
|
@ -528,10 +555,13 @@ editor is satisfied with the in-session draft. It does not gate lock.
|
|||
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.
|
||||
**Check the exit code (N3 — do not assume success).** As of S14/F7 the exit
|
||||
code is authoritative: `build-html.mjs` exits **non-zero when zero HTML files
|
||||
were produced** (e.g. a typo'd/missing filename — it prints `Fant ikke:` then
|
||||
`Ingen HTML produsert …`), and exits 0 only when at least one file was written.
|
||||
Still confirm the expected output file exists — verify
|
||||
`<serie-mappe>/review/NN-utkast.html` is present, not just exit 0. Report the
|
||||
stderr and do NOT advance the phase if the file is missing.
|
||||
|
||||
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
|
||||
|
|
@ -573,14 +603,20 @@ produces the editor's single delivery artifact — `POST.html`, the
|
|||
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)
|
||||
2. **Confirm the delivery inputs in the series folder.**
|
||||
`render/build-linkedin.mjs` reads, relative to cwd (`<serie>/linkedin/`):
|
||||
- `linkedin/edition-config.json` — calendar, freshness, cover credit/caption.
|
||||
Template + schema: `${CLAUDE_PLUGIN_ROOT}/config/edition-config.template.json`.
|
||||
- `linkedin/edition-delingstekst.md` — the per-edition distribution text (and
|
||||
the `samle` post). Template: `${CLAUDE_PLUGIN_ROOT}/config/edition-delingstekst.template.md`,
|
||||
whose header documents the exact `## Del N —` / `## Samle` section grammar the
|
||||
renderer parses.
|
||||
|
||||
If either file is absent the script throws on read — verify both are present
|
||||
before invoking.
|
||||
Both inputs are **optional and graceful** (renderer degrades, does not throw):
|
||||
a missing or malformed `edition-config.json` falls back to empty defaults, and a
|
||||
missing `edition-delingstekst.md` yields no distribution copy while the article
|
||||
`POST.html` still builds. Provide both for a complete delivery — the
|
||||
distribution hook is what Step 9 gates.
|
||||
|
||||
3. **Render POST.html.** Run with **cwd = the series folder** (the script
|
||||
resolves `linkedin/` from cwd and writes `linkedin/NN/POST.html`). The draft
|
||||
|
|
@ -635,7 +671,8 @@ the post-lock conversion sweep, distinct from the pre-lock resonance sweep
|
|||
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
|
||||
2. **Run `persona-reviewer` in conversion mode** (`subagent_type:
|
||||
linkedin-thought-leadership:persona-reviewer`) 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?» —
|
||||
|
|
@ -712,6 +749,9 @@ Edition complete. Visible in /linkedin:calendar; mark live with /linkedin:publis
|
|||
## Reference Files
|
||||
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-state.template.json` — edition-state schema (11 phases)
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-config.template.json` — static delivery metadata schema (calendar, freshness, credit, captions) — Step 8
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-delingstekst.template.md` — distribution-copy grammar (`## Del N —` / `## Samle`) — Steps 8/9
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-HANDOVER.template.md` — narrative production-state structure (§1–§6) — Step 0
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/personas.template.md` — reusable reader personas + "primær trumfer" rule
|
||||
- `${CLAUDE_PLUGIN_ROOT}/agents/fact-checker.md` — Step 5 fact-check agent (risk-sorted, guilty-until-disproven)
|
||||
- `${CLAUDE_PLUGIN_ROOT}/agents/persona-reviewer.md` — Step 6/9 reader jury (resonance + conversion modes)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue