feat(linkedin-studio): N13 — publiserings-slots + vacancy-varsel + WIP-roll-up + slot-default [skip-docs]
A1-10 + D-5. The queue answers "what is scheduled"; nothing answered "is the week covered, and what is still being written". N13 makes both readable at session start. - Publishing slots are operator config: profile/publishing-slots.json in the per-user data dir, schema + OPT-IN template in config/publishing-slots.template.json. The plugin ships no publishing times and writes the grid for nobody; absent config means every slot surface stays silent (same never-nag discipline as the trend/brain nudges). Data dir rather than the state file, which is machine-written and whose frontmatter reader does not parse lists. - hooks/scripts/slots.mjs (new): one implementation behind four surfaces. Zero-dep (a SessionStart hook must not spawn tsx), pure core (dates in as strings, no clock), imported by the commands exactly as queue-manager.mjs already is — so session-start's vacancy warning and Step 10's slot default cannot drift apart. - Coverage counts the short-form queue (scheduled/published; cancelled frees the slot) AND editions-register rows claiming that date, counted per date, so long-form and short-form cannot be double-booked and a two-slot day needs two posts. - Session start gains "## Editions in Flight" (series, phase, next action, claimed slot, days in flight) and "## Publishing Slots" (next open slot, open count in 14 days, and how to fill it when the gap is <=3 days). The existing discovery nudge is untouched. - /linkedin router + /linkedin:calendar surface the same roll-up; calendar documents the opt-in copy. /linkedin:newsletter Step 10 defaults to the next open slot (one confirmation, not an interview) and writes --slot onto the register row. - D-5: references/scheduling-strategy.md marked "confidence: low / directional" per the algorithm reference's own standard, deferring to the operator's grid and their own analytics. Neutral example time replaces the operator-specific one in the N12 docs. TDD: red proven first (module absent; 5 session-start behaviours failing). hooks 140 -> 174 · test-runner 184 -> 197 (Section 16t: 13 unconditional greps incl. a de-niche check that no publishing time is hardcoded in the slot path and no operator grid is committed, + non-vacuity self-test; anti-erosion floor 166 -> 179). Suites green: trends 300/0 · brain 134/0 · editions 72/0 · specifics-bank 45/0 · tests 35/0 · render 60/0. 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
657539ef09
commit
677eab9294
14 changed files with 1069 additions and 19 deletions
|
|
@ -2080,9 +2080,24 @@ 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).
|
||||
1. **Pick the slot — default to the next open one (N13).** Read the operator's
|
||||
publishing grid and take the first slot nothing covers yet, so scheduling is
|
||||
one confirmation rather than an interview:
|
||||
|
||||
```bash
|
||||
node --input-type=module -e "
|
||||
import { nextFreeSlot, formatSlot, readSlots } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/slots.mjs';
|
||||
const s = readSlots().slots.length ? nextFreeSlot({ days: 28 }) : null;
|
||||
console.log(s ? s.date + ' ' + s.time + ' (' + formatSlot(s) + ')' : 'NO SLOT');
|
||||
"
|
||||
```
|
||||
|
||||
Propose that date/time and ask **once** for a yes/no. `NO SLOT` means the grid
|
||||
is unconfigured or every slot in the next 28 days is taken — then fall back to
|
||||
the edition-config calendar / series brief, and ask for the slot outright
|
||||
(Step 1's calibration may already have settled it). A slot the operator
|
||||
overrides is simply used as given; the default is a starting point, never a
|
||||
constraint.
|
||||
|
||||
2. **Register via `queue-manager.mjs`.** Add one queue entry for the edition,
|
||||
reusing the short-form queue contract — `queueAdd(id, draftPath, schedDate,
|
||||
|
|
@ -2119,9 +2134,12 @@ now a first-class scheduled post.
|
|||
`edition-state.json`. Write a closing "edition scheduled → mark live via
|
||||
`/linkedin:calendar` on <date>" line to `<serie>/STATE.md` (overwrite). The
|
||||
pipeline is complete.
|
||||
**↳ Phase transition** — run the protocol (Step 0) for `scheduling`,
|
||||
`--next "published — mark live via /linkedin:calendar"`, and then **close the
|
||||
register row** (this step alone does):
|
||||
**↳ Phase transition** — run the protocol (Step 0) for `scheduling`, this time
|
||||
**with the slot** (`--slot "<YYYY-MM-DD HH:MM>"`, the one confirmed in step 1)
|
||||
and `--next "published — mark live via /linkedin:calendar"`. The slot on the row
|
||||
is what makes the edition visible as *coverage* of that publishing slot, so the
|
||||
next session stops offering it as open. Then **close the register row** (this
|
||||
step alone does):
|
||||
|
||||
```bash
|
||||
node --import tsx "${CLAUDE_PLUGIN_ROOT}/scripts/editions/src/cli.ts" register-complete \
|
||||
|
|
@ -2214,6 +2232,7 @@ the honest decision surface; it sells nothing.
|
|||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-state.template.json` — edition-state schema (18 phases including Fix #2 lived-specifics extraction (Step 1.5) + `articles.NN.livedSpecifics`, the deterministic §B/§C1 contract-gate (Step 4.5), v2.1 skeleton + spine-prose gates, v2.3 visual-assets, v2.4 editorial-review, and v3.1 headless-review + per-article `personas` + `pivots`)
|
||||
- `${CLAUDE_PLUGIN_ROOT}/scripts/specifics-bank/` — lived-specifics store + per-edition binding (Fix #2, kilde-så-draft): `query`/`add` (Step 1.5), `validate-binding` (Step 2.5 gate), `render-kilder` (`NN-kilder.md` ledger), `record-usage` (Step 8 lock — «used in edition NN»). Design record: `docs/fix2/slice2-binding.md`
|
||||
- `${CLAUDE_PLUGIN_ROOT}/scripts/editions/` — series-level memory (N11): `distil-append` writes what a locked edition spent to `<serie>/linkedin/series-distillate.json` (Step 8), `distil-check` gates the next skeleton against it (Step 2.5). Deterministic character-trigram similarity; advisory, never blocking. **Editions register (N12):** `register-upsert` at every phase transition (appends `articles.NN.phaseLog` + mirrors the row into `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/editions/register.json`), `register-list` for the work-in-progress view, `register-complete` at Step 10 (measured lead time). The register mirrors state — resumption never reads it
|
||||
- `${CLAUDE_PLUGIN_ROOT}/hooks/scripts/slots.mjs` — the publishing grid (N13): `nextFreeSlot` gives Step 10 its default slot (queue + register coverage), and session start reports open slots from the same functions. Zero-dep, so hook and command can never disagree. The grid itself is operator config (`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/publishing-slots.json`); unconfigured means Step 10 simply asks
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-config.template.json` — static delivery metadata schema (calendar, freshness, credit, captions) — Step 8
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/image-credit-caption.template.md` — cover motif + credit + caption table (honest-about-AI credit) — Step 7.5
|
||||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-delingstekst.template.md` — distribution-copy grammar (`## Del N —` / `## Samle`) — Steps 8/9
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue