name: authoring-innerzero-blog-post description: Use when writing, drafting, editing, or rewriting blog posts for the InnerZero website at C:\Users\sumlu\Documents\innerzero_website. Triggers on tasks involving .mdx files in src/content/blog/, .md files in blog-queue/, or any request to author, draft, plan, or rewrite InnerZero blog content. Encodes InnerZero-specific copy rules (em dash ban, 12+ banned AI phrases, brand naming, voice), structure conventions (question-form h2s with answer-first chunks, FAQ section, summary box, comparison tables one-row-per-line), tag taxonomy (13 approved entries), internal linking discipline, AEO patterns for ChatGPT/Perplexity/Google AI Overviews citations, encryption/privacy claim discipline, and gotchas from 18+ shipped batch posts. Does NOT apply to general website pages, components, routes, the desktop app at C:\Users\sumlu\Documents\InnerZero, or the Summers Solutions site.
Quick reference
- Published posts live at
src/content/blog/<slug>.mdx. Queue drafts live atblog-queue/<slug>.md(note.md, not.mdx). - Queue frontmatter
date:carries a real ISO date (YYYY-MM-DD). The publish script picks the earliest queue file withdate <= today UTC. There is NOPUBLISH_DATE_PLACEHOLDERin queue frontmatter; that token only survives in body copy for legacy "Last verified:" lines. - Word count target: 1200-1800 (empirical ceiling on shipped corpus is 1791 from Batch 2).
- 12 validation gates run before commit. See "Validation gates before commit" section.
- Read "Gotchas" section every time. It captures bugs from 18+ shipped batch posts.
When this skill applies
Triggers (use this skill):
- Writing a new blog post (queue or direct-publish)
- Editing or rewriting an existing post under
src/content/blog/orblog-queue/ - Planning a batch of related posts (comparison, pillar, AEO category)
- Reviewing a draft for copy hygiene, structure, or AEO discipline
- Auditing a post against the banned-phrase or em-dash rules
Non-triggers (this skill does not apply):
- General website pages (homepage, pricing, /download, /privacy, /licence)
- React components, routes, design system, or styling work
- The desktop app at
C:\Users\sumlu\Documents\InnerZero(different repo entirely) - The Summers Solutions parent site
- next.config, package.json, deploy config, Vercel settings
Repo layout for blog content
src/content/blog/*.mdx: published posts. Live URL ishttps://innerzero.com/blog/<slug>.blog-queue/*.md: queued drafts. Extension is.md, NOT.mdx. The publish script renames during the move.- Auto-publish cron:
0 9 * * 2,5(Tuesday + Friday 09:00 UTC) via.github/workflows/blog-auto-publish.yml, executingscripts/publish-next-blog-post.py. - On publish: queue file's leading QUICK-EDIT CHECKLIST HTML comment is stripped, any body-level
PUBLISH_DATE_PLACEHOLDERtokens are substituted with the frontmatter date, the file is written tosrc/content/blog/<original_stem>.mdx, the queue.mdis deleted, andgit add+git commit -m "Auto-publish: <slug>"runs. - PROJECT_MAP.md regeneration is NOT a website-publish concern. The publish script contains zero PROJECT_MAP references. PROJECT_MAP lives in the desktop repo and is regenerated there.
- Sitemap (next-sitemap) auto-includes published posts.
blog-queue/is excluded from sitemap and from Next route discovery because BLOG_DIR is hardcoded tosrc/content/bloginsrc/lib/blog.ts:5.
Frontmatter shape
The slug is derived from a frontmatter slug: field if present, otherwise from the filename minus .mdx (src/lib/blog.ts:31). Every Batch 1+2 post sets slug: explicitly. Match the corpus convention: include slug: in every new post.
Reading time is read from frontmatter, NOT auto-computed. src/lib/blog.ts:39 reads data.readingTime || "" and there is no word-count code anywhere in blog.ts. Authors MUST set readingTime: "X min read". Estimate at 200 words/minute and round up.
OG image is optional. The system falls back to /banner.png when ogImage is absent (src/app/(marketing)/blog/[slug]/page.tsx:42). Only set ogImage: when the post needs a custom image. Most posts omit it.
Literal example (copy and adapt for new posts):
---
title: "Does InnerZero Work Offline?"
description: "InnerZero runs locally on your machine and works fully offline once models are installed. What works without internet, what needs a one-time download, and what cloud features look like."
date: "2026-05-21"
author: "Louie"
authorRole: "Founder"
slug: "does-innerzero-work-offline"
tags: ["local ai", "privacy", "features"]
readingTime: "5 min read"
featured: false
---
Field reference:
| Field | Type | Required | Notes |
|---|---|---|---|
| title | string | yes | The h1. Avoid title case where it harms readability. Wrap in double quotes. |
| description | string | yes | 1-2 sentences. Used for og:description, twitter:description, RSS, JSON feed, and llms.txt. Wrap in double quotes. |
| date | string | yes | ISO YYYY-MM-DD. In queue files this is the scheduled publish date. Wrap in double quotes. |
| updated | string | no | ISO date. Set when materially refreshing a published post. |
| author | string | no | Defaults to "Louie" if absent. |
| authorRole | string | no | Defaults absent. Corpus uses "Founder". |
| slug | string | no | Falls back to filename. Set explicitly to match corpus convention. |
| tags | string array | yes | 2-4 entries from the approved taxonomy. Use SPACES not hyphens. |
| readingTime | string | yes | Format "N min read". Manual. |
| featured | boolean | no | Defaults to false. Reserve true for hero-card posts only. |
| ogImage | string | no | Falls back to /banner.png. Omit unless a custom image exists. |
| draft | boolean | no | Only honoured by RSS/JSON feed (excludes from feed). Not used in shipped corpus. |
Approved tag taxonomy (15 entries)
The corpus uses SPACES, not hyphens. Audited via grep -h "^tags:" src/content/blog/*.mdx. New tags require explicit Louie approval. Pick 2-4 per post. (hardware was formalised in the 2026-06-22 blog audit; the one-off pricing tag was retired to guide.)
announcementautomationclaudecloud aicoding agentcomparisonfeaturesgetting startedguidehardwareinnerzerolocal aimemoryprivacyvoice
Structure conventions
- Word count: 1200-1800 target. Comparison posts trend 1600-1700. Empirical ceiling on the corpus is 1791 (Batch 2).
- Summary box at top: 2-3 sentences answering the implicit question in the title. Acts as the answer-first chunk for AEO featured-snippet capture. Render as a markdown blockquote with a bold lead, e.g.
> **Quick summary**followed by 3-5 bullets. - 4-6+ question-style h2s. Use the literal question a reader would type into search. Good:
## Does InnerZero work offline?. Bad:## Offline functionality. Bad:## How offline works. - Answer-first chunk pattern under each h2: the FIRST 2-3 sentences are a direct declarative answer, no preamble, no "Let's dive in", no "Great question". THEN longer detail.
- 1+ comparison table where the topic supports it. CRITICAL: every table row MUST be on its own line. Single-line collapsed tables fail to render. GFM is wired via
remark-gfm(phase blog_render_gfm_enable, 2026-05-03). - 3+ internal links per post pointing to existing blog posts or pillar pages. Audit every linked slug exists on disk before commit.
- Internal links MUST use the no-trailing-slash convention:
/blog/<slug>, NOT/blog/<slug>/. Centralised insrc/lib/metadata.ts:absoluteUrl(). ## Frequently asked questionssection at the bottom (exact heading, case-insensitive). Triggers FAQPage JSON-LD viasrc/lib/blog-faq.ts. Include 4-6 h3 questions. Question max 300 chars, answer max 2000 chars (extractFaqs hard limits atsrc/lib/blog-faq.ts:24-25).- First-person voice (
I built,I tested,I noticed) for opinion or experience claims. Plural (we,InnerZero) for company-level statements.
Copy hygiene rules
These rules push out of default LLM behaviour. Apply them every time.
EM DASH BAN: never use the em dash character (Unicode U+2014, the wide horizontal dash that LLMs default to) anywhere in customer-facing copy. Use commas, periods, parentheses, or restructure the sentence. The /blog corpus is fully clean of this character. Do not regress.
BANNED AI PHRASES (zero occurrences across the post; case-insensitive grep). Each phrase is shown below as two adjacent inline-code tokens so this skill body itself does not trigger the audit when authors grep their own posts. Treat the two tokens as one phrase when reading.
InconclusionFurthermore(the conjunctive adverb)Moreover(the conjunctive adverb)diveintodelveunlockleverageseamlessrobustembarkharnesselevateWhetheryou're(sentence opener)powerful(must appear zero times per file)
BRAND NAMING (strict):
- Product name is "InnerZero" (one word, capital I, capital Z). NEVER "Inner Zero". Lowercase "innerzero" only inside the slogan and inside URLs.
- Casual reference: "Zero".
- Tagline (only when verbatim quoted):
inner peace. inner joy. innerzero.(all lowercase, three full stops). - Currency: GBP only (£). No $ or € in pricing claims.
- Local app: "Free to download" or "Free". NEVER "free forever", "always free", "for life", "permanent".
- Cloud plans: always "optional", never "required".
VOICE: direct, factual, slightly conversational. Match the existing /blog corpus tone. Read 2-3 recent comparison posts before drafting if unsure.
AEO and SEO patterns
- Question-form h2s + answer-first chunks are the load-bearing AEO mechanism for ChatGPT, Perplexity, and Google AI Overviews citations.
llms.txtathttps://innerzero.com/llms.txtis the LLM-facing site description. New pillar topics may warrant new entries; raise to Louie before adding.- BlogPosting JSON-LD is auto-emitted on every post by
src/app/(marketing)/blog/[slug]/page.tsx. No author action. - FAQPage JSON-LD is auto-emitted IF the post body contains a
## Frequently asked questionssection parseable bysrc/lib/blog-faq.ts. Always include this section. - Internal-link anchor text should be keyword-rich and descriptive. Not "click here". Not the bare URL.
/what-is-local-aiis the local-AI category pillar. Many existing posts link to it. Link from new local-AI posts when topically relevant.- robots.txt allows
/and disallows/account/*and/api/*. Sitemap is auto-generated. No manual edits needed.
Internal linking discipline
- Every internal link target must exist on disk. Before commit:
ls src/content/blog/*.mdxand verify each linked slug is present. - No duplicate internal links inside a single post. If
/blog/innerzero-vs-janis linked once, do NOT link it again later in the same post. Pick the best location. - Prefer linking newer or canonical posts over deprecated ones.
- External links: licensed websites only. Do not link to AI-generated competitor blog posts.
Encryption and privacy claim discipline
- The memory database is plain SQLite. Protection comes from OS-level disk encryption (BitLocker on Windows, FileVault on macOS, LUKS on Linux). NOT app-level encryption.
- Application-level Fernet encryption applies ONLY to: BYO API keys (
api_keys.pyin the desktop repo) and Telegram bot tokens (integrations/telegram_settings.pyin the desktop repo). - Cloud mode forwards only the current prompt with assembled context. The full memory database, files, profile facts, and prior conversation history are never sent.
- Microphone audio: processed locally. Raw audio is never uploaded. Cloud Voice Standard mode sends only transcript text.
- Connection log is local-only, viewable in app, daily rotation, not uploaded.
- Regulatory: ICO ZC122497. Company No. 16448945.
- If a post cites vendor ToS language, re-verify the quote against the current ToS the day BEFORE publish. Add an inline
Last verified [date]stamp where it matters.
Architecture moat (what stays private)
ALLOWED public mentions: Ollama, LM Studio, Qwen3, Gemma3, gpt-oss families, faster-whisper STT, Kokoro TTS, Silero VAD, SQLite, Python with PyWebView, "sleep and reflection" as a general phrase.
NOT ALLOWED in public copy:
- Internals of the 8-layer memory system
- Sleep-pipeline pass numbers or sequence
- Retrieval strategy specifics (cosine thresholds, recency decay, candidate confidence values)
- Confidence routing details
- Hidden orchestration internals
- Agent loop iteration caps
- policy_gate tier names
- Automation specialist mode names
If a post would naturally call for a private item, generalise. Good: "InnerZero processes conversations overnight to extract facts and prune duplicates". Bad: "Pass 5 of the sleep pipeline runs fact extraction with Ollama against unprocessed memories from the last 7 days at 0.6 confidence".
Queue mechanics
Queue posts at blog-queue/<slug>.md:
Top of file MAY contain an HTML comment block titled
QUICK-EDIT CHECKLISTlisting items to verify the week of publish (version numbers, vendor quotes, model names, ToS snapshots). The block is stripped on publish byscripts/publish-next-blog-post.py. The stripper's regex requires the literalQUICK-EDIT CHECKLISTsubstring inside an HTML comment at the very start of the file (after optional whitespace and BOM). The first checklist item across the corpus is a universal "verify no factual claims are stale".Empirical shape (first lines of
blog-queue/ai-coding-agent-sandbox-safety.md):<!-- QUICK-EDIT CHECKLIST (before publish day): - [ ] Verify no factual claims are stale (agent safety models, competitor feature sets) - [ ] Re-check Cursor Composer, Continue, Aider, and Devin current sandbox behaviour - [ ] Confirm InnerZero's approval flow description still matches what ships - [ ] Update if any major AI coding tool has added or removed safety features - [ ] Refresh the examples of "things an agent shouldn't do" if new incidents have become public -->Frontmatter
date:carries a REAL ISO date (the scheduled publish date). The publish script picks the earliest queue file withdate <= today UTC, with filename alphabetical tie-break. The pre-2026-05-03PUBLISH_DATE_PLACEHOLDERliteral in frontmatter is gone; the only place that token may still appear is body copy (e.g. inline "Last verified:" lines), where the publish script substitutes it using the frontmatter date.File extension is
.md, NOT.mdx. The publish script renames during the move.Target on publish:
src/content/blog/<slug>.mdx. A pre-existing target file is a fatal error (publish exits 1).Manual run:
gh workflow run blog-auto-publish.ymlfor a real publish, orgh workflow run blog-auto-publish.yml -f dry_run=truefor a dry-run that prints the first 40 lines of the transformed content without writing or committing.PROJECT_MAP.md regen: NOT triggered by the publish script. Website publishes do not touch PROJECT_MAP.
Validation gates before commit
Run all 12 before staging the change.
- Em-dash audit: zero U+2014 characters in the new or edited file (run a search for the literal Unicode codepoint).
- Banned-phrase audit: zero occurrences of the full banned list (case-insensitive grep).
power+fulcount: zero per file.Whether+you'recount: zero per file.- Internal link target audit: every
/blog/<slug>link has a corresponding.mdxon disk. - No duplicate internal links inside the same post.
- Cross-batch h2 dedup: scan corpus. The universal
## Frequently asked questionsis the only allowed repeat. - Frontmatter parses as valid YAML, all required fields present, tags from the approved 13-entry set only.
- Word count between 1200 and 1800.
## Frequently asked questionssection present with 4-6 h3 questions inside it.npm run buildclean (catches MDX syntax errors).- After publish: curl-grep on the live URL confirms
<table>elements render and FAQ JSON-LD is emitted in<head>.
Gotchas
The highest-signal section. Read every time.
- Tables collapse if rows are not on separate lines. GFM is wired via remark-gfm (phase blog_render_gfm_enable, 2026-05-03). One row per line, period.
.mdfor queue,.mdxfor published. A.mdxfile inblog-queue/will not be picked up by the publish script. A.mdfile insrc/content/blog/will not render (BLOG_DIR filter atsrc/lib/blog.ts:26requires.mdx).- Queue date is a REAL date (YYYY-MM-DD), NOT a placeholder. Today's date or any past date triggers immediate publish on the next cron tick. Future dates queue cleanly.
- Reading time is MANUAL, not auto-computed. Set
readingTime: "N min read"in frontmatter. Estimate at 200 words/minute and round up. - Do not trust chat memory for which posts exist.
ls src/content/blog/*.mdxis truth. The corpus grows on Tue and Fri. - Encryption-at-rest claims must be grounded. Memory is plain SQLite plus OS disk encryption. Only BYO API keys and Telegram tokens are app-level Fernet-encrypted.
- No duplicate internal links. A past phase caught duplicates and consolidated to single mentions.
- Vendor ToS quotes go stale fast. Re-verify the day before publish.
- Cross-batch h2 dedup is enforced. If a new h2 matches an existing h2 (other than the universal FAQ heading), rewrite.
- Use model FAMILY names, not exact strings, in evergreen posts. "Qwen3", "Gemma3", not "qwen3:14b-q4_K_M".
- Public-copy reach. Each published post hits sitemap, RSS feed, JSON feed, llms.txt context, and triggers a Vercel auto-deploy. Treat as customer-facing law on first commit.
- Trailing slash discipline. Internal links use
/blog/<slug>, not/blog/<slug>/. Centralised insrc/lib/metadata.ts:absoluteUrl(). - Tags use SPACES, not hyphens.
"local ai"not"local-ai"."coding agent"not"coding-agent". Audited across all 28 published posts. - PROJECT_MAP.md is a desktop-repo concern only. Website publishes do not regenerate it.
References
When authoring or rewriting a post, also load:
references/citation-eligibility-tactics.md: Princeton GEO study tactics for AI citation eligibility (external citations, statistics, quotations, authoritative language, plus the keyword-stuffing anti-pattern). Auto-load when a draft does not hit at least 3 of 4 missing tactics on first pass, or when the user asks for a "citation pass" / "AEO pass" / "GEO check".