fix(linkedin-studio): S7 harden batch — format-aware length band on per-post quality check

S7 close-out (1 edit / 1 command-file · lint 81/0 · counts 29/19/26/6):
- batch.md:95 Step-3b Quick Quality Check: replace vague "Character count in range" with a format-aware band (standard 1,200-1,800 · quick 150-500 · carousel/video caption per that format) — the old line named no band, and the 3a structure (:88-92) sums to 960-1640, under the 1,200 standard floor, so a ~1,000-char standard post passed "in range" unflagged

Axis-c (length band) closed; axes a/b/d PASS. Class: hybrid guided/stateful + post-emitting per post (each of 3-5 drafts is a feed post). Buzzword (:98) + no-body-link (:97) already present — the gap S2-S6 found is already closed in batch. Wiring verified: trend-spotter + content-planner resolve; all 5 node helpers (queueUpcoming/queueAdd 8-arg/queueFormatSummary/generateIcalFromQueue/writeIcalFile) export with matching signatures. 3 deferrals (3a single-structure vs mixed-format promise · no per-post differentiation gate by design · weekly_goal-unset micro-fallback). Local-only per push policy.
This commit is contained in:
Kjell Tore Guttormsen 2026-06-18 15:29:31 +02:00
commit 8a3e109b35
2 changed files with 78 additions and 1 deletions

View file

@ -92,7 +92,7 @@ Follow the standard structure:
- CTA: 50-100 characters
### 3b. Quick Quality Check
- Character count in range
- Character count in the band for this post's format (standard 1,2001,800 · quick 150500 · carousel/video caption per that format) — not just "in range"
- Hook works standalone
- No external links in body
- No corporate buzzwords

View file

@ -632,3 +632,80 @@ graceful degradation when voice samples are absent.
:69 honesty) · axes a/b PASS.
---
## Session 7 — Create II (`batch`)
> S7 status: re-grounded the proposed predicates against the actual `batch.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S7 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** hybrid **guided/stateful + post-emitting per post** — each of
> the 35 drafts IS a feed post, so the post predicates apply *per post*; the orchestration around
> them (state-load, agent-delegation, scheduling, queue, `.ics`) is the guided/stateful layer.
### /linkedin:batch — one theme/pillar → 35 mixed-format feed posts, scheduled + queued + .ics
**INTENT.** `batch` is the Create journey's **volume path**: from one theme or pillar produce a full
week (35 posts) with varied angles + formats, each scheduled, queued (so it shows in
`/linkedin:calendar` + session-start reminders), and exported to a `.ics`. It is an **orchestrator**
delegates angle discovery to `trend-spotter`, the balanced batch plan to `content-planner`, and owns
scheduling/queue/ical itself. Post-emitting per post, so each draft must honor the content bar
mechanically (hook **110140**, format-appropriate length band, no body link, no banned buzzword,
topic→5 pillars); guided/stateful for the orchestration (subagent targets + node helpers resolve;
graceful degradation).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
"Sunday prep": one theme → a week of **mixed-format** posts (standard → carousel → quick → video rotation).
- **Invocation:** `/linkedin:batch` → "One main theme" → 35 angles.
- **Walk:** Step 0 (`:23`) load state/SKILL/plan/templates → Step 1 (`:33`) theme/pillar (+ `trend-spotter`
for timely angles) → Step 2 (`:45`) `content-planner` plan + schedule (slot template by `weekly_goal`,
queue-conflict check `:58`) → **Step 3 per post: 3a draft (`:86`) → 3b Quick Quality Check (`:94`, the
gap) → 3c save (`:101`) → 3d queueAdd (`:124`)** → Step 4 review (`:130`) → Step 5 finalize + `.ics` (`:172`).
- **CONCRETE friction (spec-level, anchored — not a generated batch):** at **Step 3b** the per-post check
`:95` read **"Character count in range"** — **no band named** (range of *what?*), so it is unenforceable;
and the Step 3a "standard structure" (`:8892`: hook 110140 + context 200300 + insight 400800 +
implication 200300 + CTA 50100) **sums to 9601640**, whose floor sits **below** the canonical standard
band **1,2001,800** (CLAUDE.md rule #2). A "standard" post drafted to the structure can land ~1,000 chars
and pass `:95` "in range" unflagged. Complication: batch is **mixed-format**, so the band is
format-dependent — imposing 1,2001,800 on everything would wrongly fail a quick post (the S4 category
error in miniature). **Buzzword + no-body-link were NOT gaps here** (`:98` + `:97` already present — the
hole S2S6 kept finding is already closed in `batch`).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Theme→plan→schedule→per-post draft/check/save/queue→review→finalize+
`.ics` delivers the week-of-content promise. *Tension (deferred):* Step 3a imposes one "standard structure"
though Step 2 (`:76`) promises format variety — `batch` is an orchestrator, so carousel/video inherit their
own formats' rules; recorded, not edited.
- **(b) algorithm bar — PASS.** No body link (`:97`); buzzword-clean (`:98`); pillar-aligned scheduling
(`scheduling-strategy.md`); 5x5x5 reminder (`:169`). `batch` uses a deliberately lighter "Quick Quality
Check" (no per-post `differentiation-checker`) — breadth-over-depth by design for a week-at-once tool; *note,
not a fix.*
- **(c) MECHANICAL predicate (post-emitting, per post) — GAP → fixed.** hook 110140 ✓ (`:88`) · no-body-link
✓ (`:97`) · buzzword ✓ (`:98`) · topic→pillars ✓ (`:39/:77/:112`) · subagent resolves ✓ (`:43/:47`) ·
**length band ✗** (`:95` "in range" named no band; 3a structure sums 9601640, under the 1,200 standard floor).
- **(d) agent-wiring + graceful degradation — wiring PASS; PASS.** `trend-spotter` (`:43`) + `content-planner`
(`:47`) both resolve to real `agents/*.md`, both `subagent_type: linkedin-studio:`-namespaced. All 5 node
helpers resolve with matching signatures: `queueUpcoming(days=7)` / `queueAdd(…8 params)` (batch passes 8 args)
/ `queueFormatSummary(entries)` (queue-manager.mjs `:52/:63/:112`), `generateIcalFromQueue` / `writeIcalFile`
(ical-generator.mjs `:183/:201`). Degradation: no-plan→`:31` "create one first"; empty queue→`:181` guard.
*Minor (deferred):* no fallback if `weekly_goal` is unset (`:52` slot-template lookup) — micro, below axis-pass.
**HARDEN (surgical — 1 edit, 1 file, grep-confirmed landed).**
- `commands/batch.md:95``- Character count in range` → `- Character count in the band for this post's
format (standard 1,2001,800 · quick 150500 · carousel/video caption per that format) — not just "in range"`.
- **Deferrals (3, recorded):** (1) Step 3a single-structure vs mixed-format promise (orchestrator inherits
per-format rules); (2) no per-post differentiation gate (deliberate breadth-over-depth design); (3)
`weekly_goal`-unset fallback (micro-degradation). All below the stopping-rule's axis-pass bar.
- **CONCRETE after:** a ~1,000-char "standard" draft now trips the format band (under the 1,200 floor) instead
of passing the vague "in range"; a quick post is judged against 150500, not wrongly against 1,2001,800.
**VERIFY.**
- `bash scripts/test-runner.sh``Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19** confirmed
(`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` = `batch.md` only,
1 insertion / 1 deletion). Only `.md` touched → hooks/analytics `node --test` not needed.
- Anti-check grep: bare vague `^- Character count in range$` → NONE (gone); format-band line present (`:95`).
- Before/after delta: axis (c) length-band sub-check now enforced + format-aware at the load-bearing Step-3b
gate. The 1 edit is the cause.
- Disposition: **HARDENED** (1 edit, axis-c) · 3 recorded deferrals · axes a/b/d PASS.
---