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 |
pre-commit | Gate: gaps, claim verification, provenance |
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>.htmlThe 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:
humanize on the companion text and apply the result.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>"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).
pre-commit — the gate before publishingThree 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: nonecommit — 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. 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 <[email protected]>Push to origin.
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.
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
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.deviceScaleFactor: 1 — never 2. Keeps declared og:image dimensions matching the actual PNG.humanize and writing-review.--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.