agentleFS
slogsdonslogsdon/skills-workflows/skills/publish-post/SKILL.md

publish-post

Skill0 starsChanged 3 months agoMITView on GitHub

End-to-end publishing pipeline for a new article on shane.logsdon.io. Runs text quality passes (humanize → ms-style-pass), generates brand-system design artifacts (blog hero OG + in-page hero, LinkedIn post image), screenshots them at 1x, wires images into the article frontmatter and site, routes LinkedIn companion text through humanize and writing-review, saves it to Obsidian, builds the site, bumps the service worker cache version, and commits + pushes. One pre-commit gate can halt the run: unresolved [TK] gaps, claims that fail independent verification, or a missing provenance block. Otherwise unattended — no human checkpoints. Working directory must be the site repo root. Triggers: "publish <slug>", "publish the post", "run the publish pipeline", "/publish-post".

5 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use review-posts and draft-post.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • content-millSkill

    review-posts

    Show the user's Bluesky posts in Content Mill, meaning what is scheduled, published, failed or still a draft, and cancel a scheduled one. Use when the user asks what is scheduled, what went out, or whether something failed.

    Also in 5 projects that use this

  • spirosbaxSkill

    draft-post

    Draft a LinkedIn post for the user's own company page, in that company's own voice, plus an optional visual brief. Reads COMPANY_NAME and COMPANY_LINKEDIN from insaight:get_config, loads the company's real past posts through the Insaight MCP, extracts their hook, structure, length, emoji, hashtag and language patterns, then writes two variants against that reference. If the user pastes a link, fetches the article and treats it as the brief. Use this whenever the user wants something published on LinkedIn, even when they never name a skill or their company: 'write a LinkedIn post', 'draft a post about [topic]', 'post idea for [theme]', 'create LinkedIn content', 'help me write a post', 'make a post from this article', 'turn this into a post', or a pasted URL with 'post this'. Prefer it over drafting freehand — a post written without the style reference reads generic and off-voice. For one-to-one cold DMs or emails to a prospect, use draft-outreach instead.

    Also in 5 projects that use this

The file

We split this skill into the rules it gives an agent

12 rules. About 0 are common, 7 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Run all steps in order. Do not skip steps.

    line 28
  2. Distinctive:

    Read the file. Extract and hold in context:

    line 54
  3. Distinctive:

    Run this Node script, substituting the actual artifact path:

    line 95
  4. Very specific:

    If image already exists, replace it.

    line 132
  5. Distinctive:

    After writing, commit the vault:

    line 209
  6. Distinctive:

    Verify the build succeeds (exit code 0) before continuing.

    line 223
  7. Very specific:

    Read public/sw.js. Find the current cache name (pattern: shane-logsdon-io-static-vN).

    line 229
  8. Distinctive:

    If any are found, stop. Do not build, do not commit.

    line 245
  9. Distinctive:

    Do not stage dist/.

    line 277
  10. Very specific:

    Commit message. Carry the provenance block from pre-commit into the body so each publish records what came from Shane and what the model …

    line 279
  11. Very specific:

    Default: run all thirteen steps in order, end-to-end, no checkpoints.

    line 296
  12. Very specific:

    If <step> is not in {locate, humanize, style, hero, hero-shot, frontmatter, linkedin, linkedin-shot, companion, build, sw-bump, commit}, …

    line 300

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

For drafting upstream of this skill, use /workflow-stage-draft — it covers the idea → draft → humanize → ms-style-pass chain that gets a piece ready for publish-post. This skill assumes the draft is already voice-corrected and style-checked.

Skill: publish-post

Orchestrates the full publishing pipeline for a single article. One invocation, end to end.

Input

  • Required: article slug (kebab-case, e.g. the-ax-shift)
  • Brand slug is always shane-personal-v2

Steps

Run all steps in order. Do not skip steps. Do not pause for review.

Step nameWhat it does
locateFind the article and extract metadata
humanizeVoice pass on the article body
styleMS Writing Style Guide pass
heroGenerate blog hero artifact
hero-shotScreenshot blog hero (OG + in-page)
frontmatterWire image filenames into article YAML
linkedinGenerate LinkedIn post artifact
linkedin-shotScreenshot LinkedIn image
companionSave companion text to Obsidian
buildBuild the site
sw-bumpBump service worker cache version
pre-commitGate: gaps, claim verification, provenance
commitStage files, commit, push

Step locate — Locate the article

find ./pages/articles -name "<slug>.md" | head -1

Read the file. Extract and hold in context:

  • title — full article title from frontmatter
  • date — formatted as YYYY.MM.DD for artifact datelines
  • description — from resources/data/articles-list.json (key = slug), used as dek
  • category — derived from the file's directory path (e.g. pages/articles/strategic-insights/ → strategic insights)

Step humanize — Text pass: humanize

Invoke the humanize skill on the full article body (everything below the YAML frontmatter). The skill edits prose in place. Apply all its rules. Write the result back to the .md file using the Edit tool.


Step style — Text pass: ms-style-pass

Invoke the ms-style-pass skill on the same file, after humanize has run. It operates on what humanize produced — do not re-run humanize after this step. Write the result back to the .md file.


Step hero — Generate blog hero

Invoke the design-blog-hero skill with brand slug shane-personal-v2 and the following brief:

  • Headline: article title, sentence-case, ending in a period (max ~12 words)
  • Dek: article description (from locate)
  • Category: article category (from locate)
  • Dateline: article date formatted YYYY.MM.DD
  • Accent word: the single most specific noun or subject word in the title (the word that names what the article is actually about)

The skill writes: ./design/shane-personal-v2/artifacts/blog-hero-YYYY-MM-DD-<slug>.html


Step hero-shot — Screenshot the blog hero (1x)

Find the artifact:

ls -t ./design/shane-personal-v2/artifacts/blog-hero-*-<slug>.html | head -1

Run this Node script, substituting the actual artifact path:

const puppeteer = require('puppeteer');
const path = require('path');
const ARTIFACT = '<artifact-path-from-above>';
const OUT = path.resolve('./public/images');
(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-web-security']
  });
  const page = await browser.newPage();
  await page.goto('file://' + ARTIFACT, { waitUntil: 'networkidle0' });
  await page.setViewport({ width: 1200, height: 630, deviceScaleFactor: 1 });
  const og = await page.$('.canvas-og');
  await og.screenshot({ path: OUT + '/<slug>-og.png', type: 'png' });
  await page.setViewport({ width: 1440, height: 600, deviceScaleFactor: 1 });
  const hero = await page.$('.canvas-hero');
  await hero.screenshot({ path: OUT + '/<slug>-hero.png', type: 'png' });
  await browser.close();
  console.log('done');
})();

Critical: deviceScaleFactor: 1 always. 2x PNGs cause LinkedIn CDN blur because the declared og:image dimensions won't match the actual file dimensions.


Step frontmatter — Update article frontmatter

Edit the article's YAML frontmatter to set:

image: <slug>-og.png
heroImage: <slug>-hero.png

If image already exists, replace it. If it doesn't, add both fields after slug:.


Step linkedin — Generate LinkedIn post

Invoke the design-linkedin-post skill with brand slug shane-personal-v2 and the following brief:

  • Headline: max 8 words from the title (legibility at feed scale)
  • Supporting line: article description
  • CTA: omit
  • Attribution: omit
  • Companion text style: insight

The skill writes: ./design/shane-personal-v2/artifacts/linkedin-YYYY-MM-DD-<slug>.html


Step linkedin-shot — Screenshot the LinkedIn image (1x)

Find the artifact:

ls -t ./design/shane-personal-v2/artifacts/linkedin-*-<slug>.html | head -1

Run:

const puppeteer = require('puppeteer');
const path = require('path');
const ARTIFACT = '<artifact-path-from-above>';
const OUT = path.resolve('./design/shane-personal-v2/screenshots');
(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-web-security']
  });
  const page = await browser.newPage();
  await page.goto('file://' + ARTIFACT, { waitUntil: 'networkidle0' });
  await page.setViewport({ width: 1200, height: 627, deviceScaleFactor: 1 });
  const canvas = await page.$('.canvas');
  await canvas.screenshot({ path: OUT + '/linkedin-<slug>.png', type: 'png' });
  await browser.close();
  console.log('done');
})();

Step companion — Extract companion text and save to Obsidian

Extract the companion text from the HTML comment block at the end of the LinkedIn artifact:

awk '/COMPANION TEXT:/{found=1; next} /-->/{if(found) exit} found{print}' \
  ./design/shane-personal-v2/artifacts/linkedin-*-<slug>.html

The companion text is published prose, and Publishing/LinkedIn/ is the only surface with a live audience. The design skill that produced it does not voice-check it, so run two passes before it goes near the vault:

  1. Invoke humanize on the companion text and apply the result.
  2. Invoke writing-review and resolve every ask-author finding. The post is short enough that one invented number is the whole risk.

Then invoke the obsidian skill to create a note at Publishing/LinkedIn/<slug>.md with this content:

# <title>

**Published:** <YYYY-MM-DD>
**Article:** https://shane.logsdon.io/<type>/<category>/<slug>/
**Image:** `design/shane-personal-v2/screenshots/linkedin-<slug>.png`

---

<companion text verbatim>

After writing, commit the vault:

VAULT="$HOME/Library/Mobile Documents/iCloud~md~obsidian/Documents/Personal"
git -C "$VAULT" add '<the note paths you wrote>' && git -C "$VAULT" commit -m "docs: linkedin companion for <slug>"

Step build — Build the site

composer build

Verify the build succeeds (exit code 0) before continuing.


Step sw-bump — Bump the service worker cache version

Read public/sw.js. Find the current cache name (pattern: shane-logsdon-io-static-vN). Increment N by 1. Use Edit with replace_all: true to update both occurrences (in install and activate handlers).


Step pre-commit — the gate before publishing

Three checks. A failure in any of them stops the run: do not build, do not commit.

1. No unresolved gaps. Scan the article body for [TK markers, using the exact article path that locate resolved:

grep -n '\[TK' "<article-path-from-locate>"

Exit code 1 means no hits, which is the pass case. Do not rely on a ** glob here: globstar is off by default in the bash macOS ships, so ** silently behaves like a single *.

If any are found, stop. Do not build, do not commit. Report each hit with its line number and ask Shane to either supply the material or explicitly authorise cutting the passage.

An unresolved [TK] at publish time means the article contains either a reader-facing placeholder or, worse, a specific someone invented to fill the gap. Neither ships. This is the one gate in this pipeline that overrides the no-checkpoints rule.

2. Claims survive an adversary. Collect every checkable factual claim in the article — numbers, dates, percentages, quotations, causal claims, "first / largest / most" assertions, and customer or vendor statements — together with the path or note that backs each one. Then invoke the verify skill once per claim. One verifier per claim; never batch two claims into one verifier, and never hand the verifier this session's context or your own summary of it.

A claim with no artifact behind it cannot pass verify, and that is by design — it returns drop with unsettled:, which means the claim needs Shane's sourcing or should be cut. Do not paper over a drop by rewording the claim into something weaker but still unsourced.

3. Provenance for the commit body.

Author material:    <which sections or language came from Shane, the vault, or his own input>
Model contribution: <organization, structure, connective prose, artifact generation>
Open items:         none

Step commit — Commit and push

Stage these files:

pages/articles/**/<slug>.md
public/images/<slug>-og.png
public/images/<slug>-hero.png
public/sw.js
design/shane-personal-v2/artifacts/blog-hero-*-<slug>.html
design/shane-personal-v2/artifacts/linkedin-*-<slug>.html
design/shane-personal-v2/screenshots/linkedin-<slug>.png
design/shane-personal-v2/components/components.html

Do not stage dist/.

Commit message. Carry the provenance block from pre-commit into the body so each publish records what came from Shane and what the model wrote:

feat: publish <slug>

Author material: <sections or language from Shane, the vault, or his own input>
Model contribution: <organization, structure, connective prose, artifact generation>
Open items: none

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Push to origin.


Resume protocol

Default: run all thirteen steps in order, end-to-end, no checkpoints. pre-commit is the only step that can halt a run.

--resume-from <step>: skip every step before <step> and begin from <step>. The named step itself runs. The rest of the pipeline runs end-to-end from there.

If <step> is not in {locate, humanize, style, hero, hero-shot, frontmatter, linkedin, linkedin-shot, companion, build, sw-bump, commit}, print:

Invalid resume-from step: <step>
Valid steps: locate, humanize, style, hero, hero-shot, frontmatter, linkedin, linkedin-shot, companion, build, sw-bump, pre-commit, commit

…and stop.

Required state when resuming

locate always runs first inside any resume — even when --resume-from <later-step> is passed — because every later step depends on the slug-derived metadata (title, date, description, category). Treat locate as implicit setup, not a step that can itself be skipped from the user's perspective.

State required to start at each step:

  • --resume-from locate — same as a fresh run; needs the slug.
  • --resume-from humanize — needs the article file (located via locate).
  • --resume-from style — needs an article that has been through humanize (or that the user explicitly says is voice-correct). Confirm before running.
  • --resume-from hero — needs the cleaned article body (text passes done). Pulls headline / dek / category / dateline from the located article.
  • --resume-from hero-shot — needs ./design/shane-personal-v2/artifacts/blog-hero-*-<slug>.html. If absent, prompt the user or run from hero.
  • --resume-from frontmatter — needs ./public/images/<slug>-og.png and ./public/images/<slug>-hero.png. If absent, prompt or run from hero-shot.
  • --resume-from linkedin — needs the article metadata; independent of hero artifacts.
  • --resume-from linkedin-shot — needs ./design/shane-personal-v2/artifacts/linkedin-*-<slug>.html. If absent, prompt or run from linkedin.
  • --resume-from companion — needs the LinkedIn artifact HTML (companion text is embedded in the comment block) and the LinkedIn screenshot path. If either is absent, prompt or run from linkedin-shot.
  • --resume-from build — independent of prior artifacts; just runs composer build against the working tree as it stands.
  • --resume-from sw-bump — needs a successful build. If composer build has not run since the last edit to public/, ask whether to run build first.
  • --resume-from pre-commit — needs the final article body. Runs the gap scan, the claim verification, and the provenance block. commit will not run while any [TK] remains unresolved, or while any claim came back unsettled.
  • --resume-from commit — needs all final files in place (article, images, sw.js bump, design artifacts, screenshots, components.html). Show git status before committing so the user can verify the staged set; abort and ask for direction if the diff looks unexpected.

Examples:

/publish-post the-ax-shift --resume-from hero          # text passes already done by hand
/publish-post the-ax-shift --resume-from build         # everything generated, just rebuild + ship
/publish-post the-ax-shift --resume-from commit        # final retry after a transient build hiccup

Rules

  • No open [TK] ships, and no unsourced claim ships. pre-commit is a hard gate, not a warning. If it fires, stop and ask. This is the only step in the pipeline allowed to override the no-checkpoints rule.
  • Order is fixed. humanize before ms-style-pass. Hero before LinkedIn. Screenshots before frontmatter update. Build before commit.
  • 1x screenshots always. deviceScaleFactor: 1 — never 2. Keeps declared og:image dimensions matching the actual PNG.
  • Companion text to Obsidian, not to the repo. The companion text note goes to Obsidian only, and only after it has been through humanize and writing-review.
  • Stage all design artifacts and screenshots. Design artifacts, screenshots, and components.html are committed for documentation. Only dist/ is excluded.
  • If design-blog-hero or design-linkedin-post need an accent word and none is obvious, use the most specific noun in the title — the word that would answer "what is this article actually about?"
  • If the build fails, stop. Do not commit. Report what failed and suggest --resume-from build after the user fixes the underlying issue.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.