publish-post — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited publish-post (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
Orchestrates the full publishing pipeline for a single article. One invocation, end to end.
the-ax-shift)shane-personal-v2Run all steps in order. Do not skip steps. Do not pause for review.
| Step name | What it does |
|---|---|
locate | Find the article and extract metadata |
humanize | Voice pass on the article body |
style | MS Writing Style Guide pass |
hero | Generate blog hero artifact |
hero-shot | Screenshot blog hero (OG + in-page) |
frontmatter | Wire image filenames into article YAML |
linkedin | Generate LinkedIn post artifact |
linkedin-shot | Screenshot LinkedIn image |
companion | Save companion text to Obsidian |
build | Build the site |
sw-bump | Bump service worker cache version |
commit | Stage files, commit, push |
locate — Locate the articlefind ./pages/articles -name "<slug>.md" | head -1Read the file. Extract and hold in context:
title — full article title from frontmatterdate — formatted as YYYY.MM.DD for artifact datelinesdescription — from resources/data/articles-list.json (key = slug), used as dekcategory — derived from the file's directory path (e.g. pages/articles/strategic-insights/ → strategic insights)humanize — Text pass: humanizeInvoke 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.
style — Text pass: ms-style-passInvoke 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.
hero — Generate blog heroInvoke the design-blog-hero skill with brand slug shane-personal-v2 and the following brief:
locate)locate)YYYY.MM.DDThe skill writes: ./design/shane-personal-v2/artifacts/blog-hero-YYYY-MM-DD-<slug>.html
hero-shot — Screenshot the blog hero (1x)Find the artifact:
ls -t ./design/shane-personal-v2/artifacts/blog-hero-*-<slug>.html | head -1Run 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.
frontmatter — Update article frontmatterEdit the article's YAML frontmatter to set:
image: <slug>-og.png
heroImage: <slug>-hero.pngIf image already exists, replace it. If it doesn't, add both fields after slug:.
linkedin — Generate LinkedIn postInvoke the design-linkedin-post skill with brand slug shane-personal-v2 and the following brief:
The skill writes: ./design/shane-personal-v2/artifacts/linkedin-YYYY-MM-DD-<slug>.html
linkedin-shot — Screenshot the LinkedIn image (1x)Find the artifact:
ls -t ./design/shane-personal-v2/artifacts/linkedin-*-<slug>.html | head -1Run:
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');
})();companion — Extract companion text and save to ObsidianExtract 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>.htmlInvoke 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 -A && git -C "$VAULT" commit -m "docs: linkedin companion for <slug>"build — Build the sitecomposer buildVerify the build succeeds (exit code 0) before continuing.
sw-bump — Bump the service worker cache versionRead 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).
commit — Commit and pushStage 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.htmlDo not stage dist/.
Commit message:
feat: publish <slug>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>Push to origin.
Default: run all twelve steps in order, end-to-end, no checkpoints.
--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, commit
…and stop.
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 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
deviceScaleFactor: 1 — never 2. Keeps declared og:image dimensions matching the actual PNG.--resume-from build after the user fixes the underlying issue.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.