IT-Journey β Branding & Content-Governance System (plan)¶
Status: implemented. This document specifies a central branding layer for collection content and how it wires into the existing CMS engine, skills, prompts, and editor instructions.
Scope update (2026-06-22): the first iteration shipped posts-scoped; coverage was then extended to quests and docs. The brand check now runs for the collections in
.cms/config.yml βΊ brand.collections(posts, quests, docs). Posts resolve a section by folder; quests and docs resolve to one section each (quest/docs) via_data/brand/sections/_registry.yml βΊ collection_defaults. Two profiles were added for them βquest-fantasy(gamified, emoji-rich; emoji density is not policed andpowerfulis relaxed as fantasy flavor) andreference(terse docs). Vendored read-only docs (any upstream content carryingsource_repo/source_url) remain exempt. Notes/quickstart/about/etc. stay out of scope.β οΈ Post-overhaul note (2026-06-25): this plan predates the 2026 de-blog overhaul and is kept as a historical record. The
_posts/_draftsblog was removed (general content moved to the lifehacker.dev repo), so all posts-//news/-/muse-scoped mechanics below are obsolete: the 13 per-category section guides under_data/brand/sections/were deleted (onlyquest.md+docs.mdremain),_registry.ymlcarries just thequest+docssections,brand.collectionsis nowquests, docs, and the referencedindex-hub.instructions.md/posts.instructions.mdfiles no longer exist. Treat the brand layer as governing quests and docs only.
IT-Journey has rich brand raw material scattered across the repo β the mission and seven core principles in pages/_about/about.md, the philosophy in pages/_about/purpose.md, identity plus a 24-token color palette in _config.yml, and the "Chronicles: From Zer0 to Her0" magazine framing in pages/news.md β but no single, loadable place that tells an author (human or agent) how the brand sounds, what it values, who it writes for, and how each posts section differs. When drafting a new article or a short-form devops-news "muse," there is no governing voice/tone/style guide to pull from, and no automated way to catch brand drift on existing content.
This plan builds a central brand store (the source of truth for tone, style, color, team, values, niche), a backbone of per-section narrative guides, three consumption surfaces (a Claude skill, slash prompts, editor instructions), and an extension of the .cms engine that scores brand drift in the daily worklist.
It is the branding counterpart to the content-health layer already documented in EXTENSION_DESIGN.md: it reuses the same engine, lanes, and loop rather than re-implementing them.
layer 1 Content model + index β scripts/cms/cms.py β .cms/
layer 2 Analysis & reporting β .cms/reports/, .cms/index/summary.json
layer 3 Daily agent loop β .claude/skills/cms-curator + CI workflow
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
NEW Brand store + guides β _data/brand/** (facts)
NEW Brand enforcement knobs β .cms/config.yml βΊ brand: (thresholds)
NEW Brand validation β scripts/cms/cms.py βΊ _check_brand
NEW Authoring surfaces β brand-voice skill Β· /draft-muse Β· /brand-audit
brand.instructions.md
Scope decisions¶
- Granularity = Hybrid β per-section guides as the backbone, anchored to one central store, plus an optional per-article frontmatter pointer.
- Enforcement = Guidance + automated validation β extend
scripts/cms/cms.pyand.cms/config.ymlto flag brand drift alongside the existing SEO/freshness/required-field checks. - Mechanism = all three wired together β central store β skill β slash prompts β instructions files.
Posts only. The 94 files in pages/_posts/** across 13 category folders, plus the devops-news/muse format surfaced via pages/news.md. The quest/docs/notes content types are untouched.
Guiding principles (from the repo's own conventions)¶
- DRY against one store. Guides and instructions point at the store; they never restate values or voice pillars.
- Two layers per artifact. Machine-readable YAML core + human/narrative Markdown
twin (mirrors
config.yml+instructions/*.md,frontmatter.json+FRONTMATTER.md). - Detect, don't duplicate / don't rewrite. Brand checks score and flag; they
never rewrite prose β the same rule
cms.pyalready follows. - Brand is advisory. Brand issues are
lane=substantiveand never touch the health score or the mechanical auto-merge lane. The whole system is reversible via abrand.enabledflag.
1. Central brand store β _data/brand/¶
Home: _data/brand/ (not a new .brand/ top-level dir, not .cms/brand/). Rationale: _data/ is the canonical Jekyll data home, already build-consumed (navigation/, quests/, ui-text.yml), so the store is site.data.brand.* -addressable and can later drive on-page rendering (section color chips, a values block) with zero duplication. The engine reads the files directly from disk (it already does REPO_ROOT / ... reads).
Split of concerns: brand facts live in _data/brand/; brand enforcement thresholds live in .cms/config.yml under a new brand: block (Β§7). A single pointer key brand.store_root: _data/brand links them β mirroring how enrich_from: already points the engine at external report paths.
Files to create (YAML core, sourced from existing material β do not invent values; mine pages/_about/about.md, pages/_about/purpose.md, _config.yml):
| File | Contents |
|---|---|
_data/brand/identity.yml |
name, tagline "From zer0 to her0", domain, founder (Amr / bamr87), location, theme, default_author: bamr87, team_author_label: "IT-Journey Team", level_motif (binary 0000β1111, zer0βher0). |
_data/brand/values.yml |
7 principles (DFF, DRY, KIS, REnO, MVP, COLAB, AIPD) + 3 approaches (PoLR, OSAP, KISS) + the mission line, each as {code, name, one_liner, writing_implication}. The writing_implication field is what makes this load-bearing for authoring (e.g. DRY β "link the canonical explanation, don't re-explain"; KIS β "clarity over cleverness in prose"). |
_data/brand/colors.yml |
the theme_color palette verbatim from _config.yml (24 tokens) + theme_skin: dark + a section_accent map (category β token + Bootstrap icon, reusing the icon map in index-hub.instructions.md Β§4). Cross-references (does not copy) the RPG tokens in assets/css/quest-system.css + assets/css/contributor-profile.css. |
_data/brand/voice.yml |
machine-readable profile registry the validator scores against: default_profile: practitioner-chronicle + a profiles: map, each {name, formality band, person, emoji_intensity, sentence_length, banned_terms, preferred_terms}. Includes practitioner-chronicle and muse-opinion. |
_data/brand/glossary.yml |
canonical spellings + term governance: {term: GitHub, not: [Github, github]}, CI/CD, the principle acronyms, zer0/her0 stylization, quest/chronicle/muse definitions. |
_data/brand/personas.yml |
audience personas from about.md "Get Involved" (Learners, Contributors, AI Enthusiasts, Educators) + the skill ladder (beginner/intermediate/advanced), each {id, label, knows, wants, reading_for}. |
Narrative twin (Markdown β the prose an author reads when drafting):
_data/brand/voice.mdβ the voice-and-tone bible: expands eachvoice.ymlprofile into pillars + do/don't + example openings. The single place guides point to for "how we sound."_data/brand/style.mdβ prose/formatting rules (imperative for instructions, past tense for chronicles, one H1, no marketing fluff, language tags on code blocks, Mermaid for flows). Absorbs the duplicated rules currently inposts.instructions.mdΒ§7, which then shrinks to a pointer._data/brand/README.mdβ explains authoritative-vs-derived and the "facts here, enforcement thresholds in.cms/config.yml" rule.
2. Per-section narrative guides β _data/brand/sections/¶
Co-located under the store so one git view shows the whole governance surface. One file per category, named by the existing folder slug so resolution is mechanical:
_data/brand/sections/<slug>.md # 13: ai-machine-learning, business,
# creative-experimental, culture-society,
# data-analytics, devops, learning,
# programming, system-administration,
# technology, tools-environment,
# trends-ideas, web-development
_data/brand/sections/devops-news-muse.md # 14th: the distinct short-form guide
_data/brand/sections/_registry.yml # slug -> {title, permalink, voice_profile,
# personas, icon, accent_color}
_registry.yml reuses the canonical category map in index-hub.instructions.md Β§4 (single source β that table then references the registry).
Each guide is thin and pointer-based (frontmatter declares pointers; prose holds only section-specific deltas + examples β never restates voice/values):
---
section: devops
title: DevOps
voice_profile: practitioner-chronicle # -> _data/brand/voice.yml
personas: [contributor, ai-enthusiast] # -> _data/brand/personas.yml
permalink: /news/devops/
accent: teal # -> _data/brand/colors.yml
---
# DevOps β Section Guide
> Voice/tone/values live in the central store. State only what is *specific* here.
## Audience (link personas)
## Voice profile (name profile + section delta)
## Tone / Structure (deltas vs posts.instructions.md body skeleton)
## Do / Don't
## Example openings
## Section taxonomy (preferred categories/tags from taxonomyDb.json)
devops-news-muse.md is structurally identical but declares voice_profile: muse-opinion (shorter, first-person, opinion-forward, higher emoji tolerance), references the pages/news.md "Chronicles" framing, sets a lower word-count expectation, and uses a hook β take β one-link-out structure.
3. Hybrid frontmatter pointer (optional, zero churn on existing posts)¶
Two new optional keys on posts:
section_guide:β guide slug (e.g.devops,devops-news-muse).voice_profile:β optional profile override (normally omitted; the guide wins).
Resolution order (deterministic, so all 94 existing posts resolve for free):
- article
voice_profile:β use it; else - article
section_guide:β use that guide's profile from_registry.yml; else - infer from folder
pages/_posts/<category>/β<category>guide; else voice.yml: default_profile(practitioner-chronicle).
Integration:
- Register both keys as optional
choicefields on thedefaultcontent type infrontmatter.json(dropdowns: 14 slugs / profile names) β prevents typos, mirrors the taxonomy approach. Do not touch thequestcontent type. - Add both to
collections.posts.recommendedin.cms/config.yml(a nudge, never a CI-blocking required field). - Add
section_guide:(pre-filled/commented) to the.frontmatter/templates/post template so new drafts start governed.
4. The skill β .claude/skills/brand-voice/SKILL.md¶
New skill brand-voice. Trigger (the description: frontmatter):
"Load IT-Journey brand context (values, voice, style, section guide) before drafting or editing a post or muse. Use when writing a new article, writing a short-form devops-news muse, rewriting for voice/tone, or checking content matches a section's brand profile."
Procedure: (1) resolve section via Β§3 order; (2) load the store lean β identity.yml, values.yml (read writing_implication), the resolved voice.yml profile, voice.md, style.md, glossary.yml, then sections/<slug>.md; (3) hold the profile as the active constraint while drafting/editing (preferred/banned terms, formality band, emoji intensity, structure deltas, value writing-implications); (4) self-check against the same knobs _check_brand uses so skill output and validator agree; (5) hand off β do not write files unless asked, do not own git (mirrors draft-article/cms-curator).
Relationship: composes with, doesn't overlap β cms-curator is the batch loop (consumes worklist, packages PRs); brand-voice is the per-piece authoring/quality loader. /draft-article stays the structural generator and invokes brand-voice for voice.
5. Slash prompts β .github/prompts/¶
All follow the canonical .prompt.md schema (mode: agent, double-quoted description β€160, ISO-ms date/lastmod).
New:
draft-muse.prompt.md(/draft-muse) β short-form sibling of/draft-article. Loadsbrand-voicewithsection_guide: devops-news-muse; emits a muse skeleton (hook β take β one-link β zer0-to-her0 tie-back), frontmatter withsection_guide: devops-news-muse, a tighter word count, optionalfeatured/breakingnews keys. Targetspages/_posts/devops/.brand-audit.prompt.md(/brand-audit) β read-only reporter (like/quest-permalink-audit). Runsmake cms-all, surfacesbrand_driftissues from the index/worklist grouped by section, naming the offending term/metric + the rule.
Extend (each gains a "load brand-voice for the resolved section" step + a brand item in its quality checklist; reference, don't restate): draft-article.prompt.md, expand-content.prompt.md, generate-frontmatter.prompt.md, validate-content.prompt.md.
6. Instructions files β .github/instructions/¶
Canonical .instructions.md schema (double-quoted applyTo glob + description β€160
+ ISO-ms dates).
- New
brand.instructions.md,applyTo: "pages/_posts/**/*.md"β auto-loads when a post is open. Pointer-form brand contract: the resolution rule, the two keys, the top banned terms, links tovoice.md/style.md/section guides. One concern: applying brand to posts (structural rules stay inposts.instructions.md). - Extend
posts.instructions.mdβ replace the Β§7 "Style" body with a pointer to_data/brand/style.md+brand.instructions.md(DRY); add the two keys to Β§2 Recommended. - Extend
index-hub.instructions.mdβ note Β§4's category map now mirrors_data/brand/sections/_registry.ymland must stay in sync (registry is the source).
7. Automated validation β extend cms.py + .cms/config.yml¶
New brand: block in .cms/config.yml (hand-edited knob-board; global rules + per-section overrides keyed by guide slug):
brand:
enabled: true
store_root: _data/brand
advisory_only: true # never blocks CI, never auto-merges
global:
banned_terms: [comprehensive, powerful, seamless, cutting-edge, revolutionary]
preferred_terms: { Github: GitHub, "ci/cd": CI/CD }
emoji_intensity: { min: 0, max: 8 }
formality_target: 55
sections:
devops: { formality_target: 60, required_structural: ["Verify"] }
devops-news-muse: { emoji_intensity: {min: 1, max: 12}, formality_target: 45,
word_count: {min: 150, max_suggest: 600} }
creative-experimental: { banned_terms_relax: [cutting-edge] }
New _check_brand(rec, fm, body, cfg) in scripts/cms/cms.py β called from analyze_file only when brand.enabled and the record is a post (not read-only/structural/generated). It:
- Resolves the profile via Β§3 (reads
sections/_registry.yml+voice.yml, cached at config-load like the existing_GLOB_CACHE). - Merges
brand.globalβbrand.sections[<slug>]. - Emits standard
Issuerecords, alllane="substantive", with a newkindfamilybrand_drift:<rule>βbanned_term(warning),preferred_term(info),emoji_intensity(info),formality(info, cheap heuristic),missing_structural(info),guide_unresolved(info). Reuse the existingCODE_FENCE_REto strip code before term/emoji scanning (so commands aren't flagged), ascount_wordsdoes. FileRecordgains optionalsection_guide,voice_profile, and abrand_issue_countcomputed property (like the existingerror_count).
Scoring β advisory, NOT a health factor (recommended). Do not add brand to _score / health_weights. Voice/formality are heuristic and subjective; folding them into the CI-aligned health number would make it noisy and silently re-rank the worklist on soft signals. Brand issues are real Issues (they appear in the index, the report's "Top issue types," and the worklist's substantive lane via existing plumbing) but health is unchanged. advisory_only: true + enabled: false make it fully reversible. (If wanted later: add a 5β10% brand weight + a brand_score term.)
Surfacing + curator pickup:
cmd_analyze: the existing "Top issue types" auto-countsbrand_drift; optionally add a small "Brand drift by section" table (a few lines).cmd_plan: brand issues are substantive β already flow into Lane B (no planner change)..claude/skills/cms-curator/SKILL.md: add_data/brand/to its Phase 0 policy reads, and one Lane B bullet β "brand_drift:*β loadbrand-voice, resolve the guide, apply the smallest term/voice fix; never rewrite wholesale."
8. Sequencing (each phase independently shippable + CI-safe)¶
Branch first; never commit to main. New post keys are all optional and _data/brand/** is non-rendered data, so frontmatter-validation + make build-ci + make content-audit stay green throughout.
- Store (data only, zero behavior change). Create the nine
_data/brand/*files fromabout.md/purpose.md/_config.yml. Verifymake build-cistill builds. - Section guides + registry. Create
_registry.yml+ 14sections/*.md(13 categories +devops-news-muse.md), thin/pointer-based; populate the registry fromindex-hub.instructions.mdΒ§4. - Frontmatter pointer. Edit
frontmatter.json(2 optional choice fields),.cms/config.yml(collections.posts.recommended),.frontmatter/templates/post template. Verifymake content-validate+make build-ci. - Engine validation. Edit
scripts/cms/cms.py(_check_brand,FileRecordfields, cached store loader,cmd_analyzetable; wire behindbrand.enabled) +.cms/config.yml(brand:block + document the newbrand_driftkinds as substantive). Runmake cms-all; confirm issues emit andhealthis unchanged. (Blast radius: only.cms/outputs, which Jekyll ignores.) - Authoring surface. Create
.claude/skills/brand-voice/SKILL.md, thedraft-muse/brand-auditprompts,brand.instructions.md; extend the 4 prompts, trimposts.instructions.mdΒ§7, noteindex-hub.instructions.mdΒ§4, and add the brand bullet + policy read tocms-curator/SKILL.md. All.prompt.md/.instructions.mdpass theFRONTMATTER.mdfour-key check.
Reuse: Issue/FileRecord dataclasses, the lane plumbing, _GLOB_CACHE/ glob_match, CODE_FENCE_RE, cmd_plan substantive routing, the worklist/report writers, the index-hub.instructions.md Β§4 category map, taxonomyDb.json, the SKILL/prompt/instruction conventions, make cms-all, the cms-daily-loop workflow.
Build new: _data/brand/**, the 14 guides + registry, _check_brand + the brand: config block, the brand-voice skill, the draft-muse/brand-audit prompts, brand.instructions.md.
- Upstream to the zer0-mistakes theme (separate PR, after validation). Once
Phases 1β5 are merged in it-journey and validated (build-ci + content-audit green;
_check_brandproducing sane drift output; the skill/prompts exercised on real drafts), generalize the framework and open a separate draft PR tobamr87/zer0-mistakes. See Β§9.
9. Follow-up β upstream the framework to the zer0-mistakes theme¶
Why. _config.yml line 227 already states the governing philosophy β "Keep implementation in the zer0-mistakes theme; configure only site paths here." And .github/FRONTMATTER.md is already a shared canonical asset across bashconsultants / zer0-mistakes / it-journey, with zer0-mistakes as the source of truth (FRONTMATTER.md: "Keep the three copies in sync"). So the branding-governance framework is a natural theme feature: any site on bamr87/zer0-mistakes inherits the scaffolding and overrides only its own brand facts. This task ports the reusable parts upstream after they have proven out in it-journey.
Gate (do this only after it-journey is finalized/validated). PR #338 + Phases 1β5 merged; make build-ci + make content-audit green; brand-drift output reviewed; the brand-voice skill and /draft-muse / /brand-audit prompts used on at least one real post and one real muse.
Theme vs site split (the core design rule of the upstream):
- Upstream to the theme (generic framework / scaffolding):
- The
_data/brand/schema + a neutral default set + README β the theme ships sensible empty/example defaults; consuming sites override with their own values. - The section-guide template + the
_registry.ymlschema (the shape, not IT-Journey's 14 concrete guides). - The authoring surfaces as canonical templates, mirrored per the FRONTMATTER.md
precedent:
brand-voiceskill,brand.instructions.md,/draft-muse+/brand-auditprompts land in zer0-mistakes.claude//.github/as the source of truth, then mirror back into it-journey + bashconsultants. - Optional theme Liquid includes that render
site.data.brand.*(section color chips, a values block) β rendering is the theme's job. - Feature doc under the theme's
docs/features/(matches the existing pattern referenced at_config.ymlline 574) + a theme version bump (the theme is versioned;_data/navigation/README.mdnotes the v0.17+ convention). - Stays site-specific (NOT upstreamed): IT-Journey's actual brand facts
(
values.yml/voice.yml/colors.yml/personas.ymlcontent), the 14 concrete section guides, and the site'ssection_accentmappings.
Open decision to resolve at upstream time: whether the theme also ships the CMS engine (scripts/cms/cms.py + _check_brand) or only the brand-store schema + authoring templates + docs. cms.py is currently it-journey tooling, not part of the theme. Recommended: upstream the store schema, authoring surfaces, rendering includes, and docs first; treat the engine _check_brand as a documented, optional add-on for sites that also run the CMS engine β keeps the theme PR focused on what every themed site can consume.
Deliverable: a draft PR to bamr87/zer0-mistakes containing the generalized framework + feature doc + version bump, plus the mirror-sync of the shared .github canonical assets.
Critical files¶
Modify: scripts/cms/cms.py, .cms/config.yml, frontmatter.json, .github/instructions/posts.instructions.md, .github/instructions/index-hub.instructions.md, .claude/skills/cms-curator/SKILL.md, the .frontmatter/templates/ post template, and the four prompts (draft-article, expand-content, generate-frontmatter, validate-content).
Create: _data/brand/{identity,values,colors,voice,glossary,personas}.yml, _data/brand/{voice,style,README}.md, _data/brand/sections/_registry.yml + 14 sections/*.md, .claude/skills/brand-voice/SKILL.md, .github/prompts/{draft-muse,brand-audit}.prompt.md, .github/instructions/brand.instructions.md.
Mine for source material (read-only): pages/_about/about.md, pages/_about/purpose.md, _config.yml, pages/news.md, .github/instructions/index-hub.instructions.md.
Verification¶
- Build stays green:
make build-ciandmake content-auditafter Phases 1β3 (and again at the end)._data/brand/**is data, so it cannot fail post frontmatter validation; the new post keys are optional. - Engine emits brand issues without perturbing health:
make cms-all, then check that.cms/index/content-index.jsonand.cms/reports/<date>.mdshowbrand_drift:*issues in the substantive lane, and confirm collectionhealthnumbers are unchanged vs the pre-changesummary.json(diffindex/summary.json). - Resolution defaults work for legacy posts: spot-check that a post in
pages/_posts/devops/with nosection_guidestill resolves to thedevopsguide (it appears under devops in the new "Brand drift by section" table). - Skill loads end-to-end: invoke
brand-voice(or/draft-muse) on a sample devops topic and confirm it loads the store + thedevops-news-museguide and produces a draft honoring themuse-opinionprofile (no banned terms, emoji in band, hookβtakeβlink structure). - Audit reporter works: run
/brand-auditand confirm it lists drift grouped by section with the offending term + rule. - Reversibility: set
brand.enabled: falsein.cms/config.yml, runmake cms-all, and confirmbrand_driftissues disappear and outputs match the pre-feature baseline.