# Linkedin Post

> Interviews a person to extract one idea, then writes a LinkedIn post in their voice, checks it, archives it and publishes it. Triggers: write a LinkedIn post, I have an idea for a post, turn this into a post. Not for setting up pillars or a voice profile (use linkedin-setup).

- Skill: `alexis-morain/linkedin-post` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add alexis-morain/linkedin-post`
- Raw SKILL.md: https://api.skillmd.com/api/skills/alexis-morain/linkedin-post/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: alexis-morain (https://skillmd.com/u/alexis-morain)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/alexis-morain/linkedin-post

---


# Write a post

> **Paths in this file are relative to the bundle root**, the directory holding
> the router `SKILL.md`, not to this skill's own directory. The bundle installs
> as one unit so that `references/`, `locales/` and `lib/` resolve the same way
> from every skill in it.

The bottleneck is not writing, it is extraction. This skill interviews first
and writes second. **It never invents material.**

## Before anything

1. Read the material's `## Status` block. If `filled: no`, say so in one line
   and offer `linkedin-setup`. Do not proceed by guessing who this person is.
2. Read its Profile, Voice, Pillars and Ideas sections.
3. Read `locales/<interview_language>/style.md` and `interview.md`. If the pack
   does not exist, fall back to `locales/en`, and say so.
4. Count the pillars of the last eight ledger rows with `state: published`,
   drafts do not count. Announce the balance in one line and name the pillar
   that is behind.
5. Find ledger rows published more than seven days ago whose three numbers are
   still empty. Ask for them. It takes thirty seconds and it feeds the
   loop that everything else depends on.

Then offer three ways in: a free subject, a voice note, or an idea from the
Ideas section, prioritising the pillar that is behind.

For a voice note, transcribe locally. Nothing about this leaves the machine.

## The interview

Intents and ladder: `references/interview-intents.md`, set B.
Wording: `locales/<interview_language>/interview.md`.

**One question at a time. Never a numbered block.** Ask, wait, dig. The
ceiling below until the format is settled, and the format's own after that.
There is no floor: what ends an interview is the material, and a number
underneath would be a quota to reach on a person who had already said enough.

The rules that matter most, restated because they are the ones that get
dropped:

- **If an answer is abstract, do not advance.** Ask for the instance again.
  A form changes subject when an answer is hollow. This does not. The one
  exception is a person who never had a scene and came in with a thesis:
  that is the `witnessed-instance` door, not evasion.
- **Quote the previous answer inside the next question.**
- **Every turn, list what has been acquired, fact by fact, then what is
  missing.** Not a count. A number is a claim about the material and nobody
  can check it; the list is the material, and the person reads their own words
  back. Code used to count this and the floor runs no code, so the counting
  moved into the open:

  ```
  Acquired
    31 percent error on net burn        "we were off by thirty one percent"
    the meeting ran 40 minutes          "the next meeting ran forty minutes"
  Missing
    when this happened
  ```

  Distinct facts only, figures and proper nouns, never turns taken. Two
  answers repeating one fact are one fact. Undercount on purpose: the cost of
  one question too many is a question, and the cost of one too few is an
  invented detail.
- Six is the ceiling until the format sets its own, and neither is a target.

## The break: format and angle

Happens **after the first or second answer, not at the end.** The format
decides which rungs matter. The angle gives every later question a thesis to
serve.

Both are covered by `references/formats.md`, including the exact shape of an
angle proposal. **Once the format is settled, the ladder is the one written
for it**, under the break in `references/interview-intents.md`. A story and a
stance do not climb in the same order, and only the formats whose climb has
been observed have a row there. The rest climb the default order, stop at the
default ceiling, and say nothing more than that.

**The row settles how far as well as in what order.** Its `Ceiling` is where
that format stops, and it is not the count of rungs written on the row: a
story keeps climbing past the ones it names, a stance does not. `Presumed`
says which way a turn breaks when the material is arguably enough and
arguably not: `deep` asks the next rung, `short` goes to the sheet. Neither
overrules the sufficiency test, and a stance still missing its occurrence is
unfinished rather than short. Before the break there is no format yet, so
those turns run under the default ceiling.

The one rule to carry in your head: **two angles, never one, and each one
carries a verbatim quote of something the person actually said.** If it cannot
be quoted, the angle was invented. Throw it away and ask another question.

## The validation sheet

**Nothing is written until this sheet is approved.** This is the guard that is
missed most often, and it is exactly the mechanism that catches a borrowed
experience before it reaches a draft.

Produce the sheet, then ask whether to go with it. Do not draft until there is
an answer.

**When a tool is offered for the sheet, the sheet goes through it and nothing
else is said in that turn.** One field per line of the sheet, the elements as
a list, one or two first lines. No further question, no commentary around the
call: the person reads the sheet on their screen and decides there. If the
material is thin, say so inside the sheet, under CONCRETE ELEMENTS, rather
than asking again. A small model asked for the sheet tends to answer with one
more interview question, and the person then gets no sheet at all.

```
ANGLE               one line, restated with the material collected
CONCRETE ELEMENTS   one bullet per fact, each with the words it came from
THE STRONG MOMENT   the anecdote or reported sentence that carries the post
CENTRAL CONVICTION  in quotes, what they conclude
FIRST LINE          two proposals, or theirs
```

**One label per line in that block, and never a continuation line.** It is
read as lines by `app/tests/test_prose.py`, which splits each one on its
first double space, so a wrapped description becomes a sixth label that is
the empty string. Say less rather than wrapping.

**Every bullet carries its quote underneath, with the source named.** Like
this, and the shape matters more than the wording:

```
CONCRETE ELEMENTS
  - Forecast error went from 31 percent to 6 percent on the same model
    SAID: "we were off by thirty one percent on net burn, same model"
  - The rebuild took eleven hours
    CORPUS: "Eleven hours. That is the median time I spend building a
    first board pack"
```

A bullet with no quote under it was invented. It comes out. This is the whole guard: a model that knows
how something usually works will write the usual thing as a fact, and the
quote is what makes that visible to the person rather than to nobody.

**Where the host can publish a web page, offer the sheet as one**, per
`references/sheet-artifact.md`. It puts each quote beside its bullet and
counts the characters in the browser rather than estimating them. It is a
convenience: the person still approves in the conversation, and a host that
cannot do it says so in one line and carries on. It never carries the
material.

Three hard rules on this sheet:

- **A bullet traces to an interview answer or to the corpus, never to the
  profile.** The profile is input to a question, never evidence. No
  "plausible", no inference.
- The first line is the only thing most readers will see. If a proposal is
  chosen, **the post is written for it**, to the character. The person is
  asked which one, and taking neither is one of the answers: a sheet whose
  `first_line` is `null` was read and both proposals were refused, so the
  post opens on a line written for this angle and never on either of them.
  A sheet with no `first_line` at all is one nobody was asked about.
- Once the sheet is approved, the interview is closed. No more questions.

## Writing

**Open the generation with an explicit output-language directive.** One line,
before anything else: the post is written in `<language>`. This is one of the
two guards against the engine's English leaking into the post.

Then produce, in this order:

1. **Three hooks, three different angles.** Not three phrasings of one idea.
   Each has to stand alone inside the fold.
2. **The body**, structured by the chosen format.
3. **The close.** One idea, not a summary. An open question only if it is
   sincere.
4. **The character count.** Do not trim by reflex: a dense long post holds, and
   `references/platform.md` explains why length is an outcome and not a
   setting. `lib/publish.py` refuses anything past the platform limit.
5. **Two photo ideas.** A **portrait** staging the action of the post, not a
   generic pose: what they do, where, with what in their hands. And a
   **visual** showing the number or the object the hook talks about.
6. **Three tips**: the **strong message**, quoting the sentence *and saying why
   it works*; the **weak spot**, naming the passage; the **lesson** for the
   next post.
7. **The raw transcript** of the interview, verbatim. It is material for later.
   The angles that were not taken are sleeping in it, and they go back into
   the Ideas section at the end of the session.
8. **The anchors block**, format in `references/anchoring.md`: each claim of
   the body paired with what backs it, and the label says where that lives.
   `SAID:` is the interview sentence, quoted word for word in the language of
   the interview; `SHEET:` is a line of the approved sheet, copied exactly. A
   line of the sheet is never offered as `SAID:`, the person approved it and
   did not say it. A claim with nothing to back it stays bare; bare is honest,
   and a plausible quote invented to dress it is the failure the block exists
   to catch. The profile backs nothing: it is input to a question, never
   evidence.

   **An anchor shorter than ten characters, typography folded, counts as
   absent.** One letter is found in any text, so an anchor that cannot miss
   is an alarm that cannot ring. Code used to refuse those and the floor runs
   no code, so the rule is written out here: quote enough of the sentence to
   be findable in it, and a reader who cannot find the anchor in the source
   it names is looking at a fabricated one.

**When a tool is offered for the post, `body` is the post alone**: the chosen
first line, the body and the close, in the output language, and nothing else.
The other hooks, the character count and the transcript stay out of it. The
transcript is already on disk, and a hook left inside the body is a sentence
the traceability check will mark as unanchored. The photo ideas and the tips
go in their own fields; the anchors block becomes the `anchors` field, one
pair per entry, the backing under `said` or `sheet` exactly as the block would
label it, under the same rules.

**The signature block is not generated, it is concatenated.** It is appended
after a blank line, without passing through the model. A generated signature
drifts a little on every post until it belongs to somebody else. It lives in
the Profile section of the material.

## The deterministic pass

Run it on the post body, not on the archive file: the front matter and the
title will produce findings that have nothing to do with the post.

```bash
python3 lib/lint.py --lang <lang> - < body.txt
```

It reports; the human decides. Only the rules a pack marks hard block, and
those are typically the em dash and emoji. A finding on a word the person
actually said, in a quote, stays in.

If the pack is not native reviewed, the tool says so, and its findings are
read as suggestions.

## Revisions

Free loop, no quota. Always restart from the interview material, never rewrite
blind.

**The sheet rule applies here too, and this is where it is usually forgotten.**
A revision can reintroduce an invented detail behind the guard, because the
guard only covered the first generation. Observed in the wild: a plain "change
the angle" request turned a home made verification script into a piece of
server infrastructure that never existed, stated as fact.

So: after every revision, reread the produced text and check that each new fact
comes from the interview. If one does not, say so and take it out.

Five ways in. When a revision is asked for without saying what, offer this
vocabulary. It unblocks faster than an open question.

| Revision | Offer |
|---|---|
| Redo the hook | too commercial, open on a number, sharper, make it a question |
| Change the angle | more about the failure, from the client's point of view, more contrarian, business rather than personal |
| Add an element | a numbered anecdote, an objection, a date, a consequence |
| Change the tone | blunter, less corporate, more personal, calmer, less preachy |
| One specific passage | they paste the extract, nothing else is touched |

### When the revision asks for something that was never said

A revision can ask for a fact the interview never gave: a source for a
figure, a date, a name, a comparison with a market. The rule against
inventing does not soften because a draft exists. It gets louder, because the
person is looking at a text now, and what is missing reads as a gap to fill
rather than as something nobody ever said.

Three moves, in this order, and none of them optional.

1. **Refuse in one sentence, out loud.** "I cannot invent a source for you."
   Not a hedge, not a quiet omission, and never a plausible placeholder that
   somebody has to catch later.
2. **Offer typed options, not an open question.** Name the shapes the answer
   could have: a comparison site, a barometer from a firm, rate cards they
   have seen, a specific article. This is the two opposed options procedure
   widened. An open "what is your source?" gets a shrug; four named shapes
   get an answer, or an honest no.
3. **Offer the exit.** "Otherwise I take the passage out." Nobody is ever
   left choosing between inventing something and abandoning the post.

**Nothing is rewritten in that turn.** The refusal is the whole answer.

A turn that produced nothing costs the person nothing either: the request it
refused is still the request. When they come back with a source, the writing
turn is handed both, what they asked for and what they answered, in that
order. Somebody typing `Malt barometer, 2025` is naming a source, not asking
for a shorter post.

Wording for all three: `locales/<interview_language>/interview.md`,
under the connective phrases.

## Rewriting one passage

When the request is about one block rather than about the post, the material
carries that block under `## Passage`, word for word, and the turn calls
`rewrite_passage` instead of `propose_draft`.

**Send back that block and nothing else.** Not the post with the block
changed, not the block with a line of the one above it for context. What you
send replaces exactly the characters the passage occupies, so a sentence of
preamble lands inside the post, in that spot, between two paragraphs
somebody wrote.

The rest of the post is out of reach, and that is the point rather than a
restriction to work around. Somebody asking for a sharper second paragraph
is not asking you to improve the first one, and under `propose_draft` they
had no way to ask for one without risking the other.

What still applies, unchanged:

- **Every fact in the new block comes from the interview.** A rewrite is
  where an invented detail gets in behind the guard, and a shorter scope
  does not make that less true.
- **Refuse rather than invent.** The refusal contract above is the same
  here, and the exit is the same: the passage comes out.
- **The block may become two.** Splitting a paragraph is a legitimate
  rewrite; the span does not care how many blocks land in it.
- **Anchors are for the new block only.** What backs the rest of the post is
  kept for you, and repeating those pairs would claim them twice.

## Graded review, on request

On a post written elsewhere, or before publishing. Return a **score out of
10**, a **status** (ready to publish, needs work, material is missing), one
sentence of verdict, then the passages to revisit **quoted**, each with a
proposed replacement.

The score alone says nothing actionable; the status is what decides. And do not
be generous: a decent post is a 7, not a 9.

## Closing: the lines that changed

**A post that stays a draft is not finished.** A batch of drafts that never
shipped is the most common way this whole system dies.

**Everything that changes the material leaves this skill the same way**: one
block, at the end, holding only the lines that changed, which the person
pastes back into their file. One mechanism, not three. The floor writes
nothing, so what changed has to be visible and copyable, and a session that
ends without this block has quietly lost everything it produced.

```
MATERIAL UPDATE

Ledger, new row:
  2026-08-29 | 1 | counter-intuitive-number | VISIBILITY | draft | | | |
  hook: "Thirty-one percent to six percent, on the same model."
  chars: 1263   ref:    note:

Ideas, move to Used:
  2026-08-29 | P1 | Your model is not wrong, your commentary is

Ideas, add:
  [P2] TRUST The diligence question that costs five weeks. Material: the
  revenue recognition answer, already public.

Next session: 2026-09-12, the diligence question (P2)
```

Rules on the block, and all three exist because a person is going to paste
this by hand:

- **Only what changed.** Reprinting the whole material invites a paste that
  overwrites an edit they made themselves.
- **The nine ledger columns in order**, empty ones left empty rather than
  dropped, so a row stays readable as a row. The four descriptive fields
  follow underneath, where nothing parses them.
- **Nothing is written here.** A tier that holds files may apply the block
  instead of printing it. That is convenience, and the block is printed
  anyway.

Then, in this order:

1. The block above, with the ledger row and the idea bank. **Never close a
   session leaving the bank poorer than it was at the start.**
2. Publish or schedule. At the floor the person posts it themselves, which is
   what `copy` always meant. Where the host can run commands,
   `lib/publish.py` handles the three tiers.

   **Never hand raw text to a scheduling tool.** Run it through
   `publish.to_scheduler_html` first. A feed renders consecutive paragraphs
   with no gap, so a post sent without empty separators arrives as a wall of
   text, and a decomposed accent that survived every layer intact shows up as a
   letter with something floating beside it. Both have happened here, on the
   same post. The converter also settles what crosses over: the post is text,
   only `**bold**` becomes markup, and a short numbered heading is the one
   place it earns its keep.

   **Check the target channel before scheduling anything.** A personal profile
   and a company page are two lines in a config file and two very different
   things in a feed. This has already gone wrong in this project's history:
   three test posts went to the wrong page and five drafts sat scheduled on it.
   `publish.py --plan` prints the target before anything is sent, and it warns
   when the channel has an id but no name.

   Deleting a post inside a scheduling tool does not unpublish it from the
   platform. Removing something already live means going to the platform.

3. At J+7, **the same block again**, carrying the three numbers and the
   `measured` date for that row: inbound connections from target profiles,
   inbound messages that mention a project, mentions of the post in a meeting.
   Not likes. It is the identical mechanism, which is the point: a person who
   pasted one block already knows how to paste this one. Confidence thresholds
   are in `references/measure.md`, and `state` moves to `published` on what
   the person says happened, never because a send returned zero.

4. **Set the next session before closing this one.** This is the step that
   decides whether there is a second post at all. The bottleneck is not
   writing, it is coming back.

   Propose a date, and **attach an idea already chosen** from the Ideas section,
   prioritising the pillar that is behind. An appointment without a subject
   gets postponed; an appointment with one gets kept. Both go in the block,
   on the `Next session` line.

   If they want a reminder, set one. Do not set one unasked.

## Hard rules

- **No invented number.** Every number comes from the material, from the
  posts the person pasted, or from the interview. If one is missing, ask, or
  drop the claim.
- **No fabricated client experience.** Never attach a post to a lived
  experience that does not exist. The profile's abandoned segments exist
  precisely so a post is not written as if they were clients.
- **Names that are not public stay out.** The material lists them. Ask before
  citing.
- **No post aimed at a target that is out of scope for this channel.** The
  profile says which ones those are.
- **Paid promotion is disclosed.** Affiliate links included. See
  `locales/<lang>/market.md`, and note that this project has already lost a
  disclosure between a draft and its published version. It is not a style
  detail and it does not get cut for length.
- **Style**: `locales/<lang>/style.md` applies in full.
- **No comment gate** unless the profile has deliberately turned it on.

