JLongshore's picture
Deploy intentsolutions.io from CI — 51a73eaa5ce48cc2db63fd6bf93ed17b9ab737f1
a3d6e8c
Raw
History Blame Contribute Delete
49.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta name="generator" content="Astro v5.18.2">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<!-- SEO -->
<title>Knowledge Base from Zero to API: Lifecycle State Machines and Policy Engines | Field Notes</title>
<link rel="canonical" href="https://startaitools.com/posts/knowledge-base-zero-to-api-lifecycle-state-machine/">
<meta name="description" content="Building a team knowledge base with lifecycle state machines, policy engines, and git-exported articles — from empty repo to working API in one day.">
<meta name="robots" content="index, follow">
<meta property="og:title" content="Knowledge Base from Zero to API: Lifecycle State Machines and Policy Engines | Field Notes">
<meta property="og:type" content="website">
<meta property="og:image" content="https://intentsolutions.io/og-image.svg">
<meta property="og:url" content="https://startaitools.com/posts/knowledge-base-zero-to-api-lifecycle-state-machine/">
<meta property="og:description" content="Building a team knowledge base with lifecycle state machines, policy engines, and git-exported articles — from empty repo to working API in one day.">
<meta property="og:site_name" content="intent solutions io">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Knowledge Base from Zero to API: Lifecycle State Machines and Policy Engines | Field Notes">
<meta name="twitter:image" content="https://intentsolutions.io/og-image.svg">
<meta name="twitter:description" content="Building a team knowledge base with lifecycle state machines, policy engines, and git-exported articles — from empty repo to working API in one day.">
<!-- Site-level structured data (Organization + WebSite) -->
<script type="application/ld+json">[{"@context":"https://schema.org","@type":"Organization","name":"Intent Solutions","url":"https://intentsolutions.io/","logo":"https://intentsolutions.io/favicon.svg","sameAs":["https://github.com/jeremylongshore","https://linkedin.com/in/jeremylongshore","https://claudecodeplugins.io"]},{"@context":"https://schema.org","@type":"WebSite","name":"Intent Solutions","url":"https://intentsolutions.io/"}]</script>
<!-- Umami analytics — self-hosted at analytics.intentsolutions.io (Intent
Solutions VPS). Replaces the Firebase Analytics block on 2026-06-10 to
remove the last GCP dependency from the marketing site. Umami auto-
tracks page views; emit custom events with window.umami.track(name, props). -->
<script defer src="https://analytics.intentsolutions.io/script.js" data-website-id="474bce85-f97d-409c-aba5-1e1ff36ee571" data-domains="intentsolutions.io"></script>
<link rel="stylesheet" href="/_astro/a2a.CeHTlFWO.css">
<style>.nav-panel[data-astro-cid-4lunwx2e]{position:absolute;top:100%;left:50%;transform:translate(-50%) translateY(6px);padding-top:10px;opacity:0;visibility:hidden;transition:opacity .18s ease,transform .18s ease,visibility .18s;z-index:60}.nav-group[data-astro-cid-4lunwx2e]:hover .nav-panel[data-astro-cid-4lunwx2e],.nav-group[data-astro-cid-4lunwx2e]:focus-within .nav-panel[data-astro-cid-4lunwx2e],.nav-group[data-astro-cid-4lunwx2e].open .nav-panel[data-astro-cid-4lunwx2e]{opacity:1;visibility:visible;transform:translate(-50%) translateY(0)}.nav-panel-inner[data-astro-cid-4lunwx2e]{min-width:270px;background:#09090bf5;border:1px solid rgba(63,63,70,.6);border-radius:.75rem;padding:.5rem;box-shadow:0 20px 50px #0000008c,0 0 0 1px #f973160f;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.nav-item[data-astro-cid-4lunwx2e]{display:flex;flex-direction:column;gap:.1rem;padding:.55rem .7rem;border-radius:.5rem;text-decoration:none;transition:background .15s ease}.nav-item[data-astro-cid-4lunwx2e]:hover{background:#f9731614}.nav-item-label[data-astro-cid-4lunwx2e]{font-size:.86rem;font-weight:600;color:#e4e4e7;transition:color .15s ease}.nav-item[data-astro-cid-4lunwx2e]:hover .nav-item-label[data-astro-cid-4lunwx2e]{color:#ff8a2a}.nav-item-desc[data-astro-cid-4lunwx2e]{font-size:.72rem;color:#71717a}.nav-ext[data-astro-cid-4lunwx2e]{font-size:.7rem;color:#71717a;margin-left:.3rem}.nav-chevron[data-astro-cid-4lunwx2e]{transition:transform .18s ease}.nav-group[data-astro-cid-4lunwx2e]:hover .nav-chevron[data-astro-cid-4lunwx2e],.nav-group[data-astro-cid-4lunwx2e]:focus-within .nav-chevron[data-astro-cid-4lunwx2e],.nav-group[data-astro-cid-4lunwx2e].open .nav-chevron[data-astro-cid-4lunwx2e]{transform:rotate(180deg)}.burger-bar[data-astro-cid-4lunwx2e]{display:block;width:18px;height:1.6px;background:currentColor;border-radius:2px;transition:transform .2s ease,opacity .2s ease}#nav-burger[data-astro-cid-4lunwx2e][aria-expanded=true] .burger-bar[data-astro-cid-4lunwx2e]:nth-child(1){transform:translateY(6.6px) rotate(45deg)}#nav-burger[data-astro-cid-4lunwx2e][aria-expanded=true] .burger-bar[data-astro-cid-4lunwx2e]:nth-child(2){opacity:0}#nav-burger[data-astro-cid-4lunwx2e][aria-expanded=true] .burger-bar[data-astro-cid-4lunwx2e]:nth-child(3){transform:translateY(-6.6px) rotate(-45deg)}
</style></head>
<body>
<nav class="sticky top-0 z-50 backdrop-blur-md bg-zinc-950/70 border-b border-zinc-800/60" data-astro-cid-4lunwx2e>
<div class="container mx-auto px-6 sm:px-8" data-astro-cid-4lunwx2e>
<div class="flex items-center justify-between h-16" data-astro-cid-4lunwx2e>
<a href="/" class="home-link" aria-label="Intent Solutions home" style="gap: 0.6rem;" data-astro-cid-4lunwx2e>
<!-- Brand mark — actual logo render -->
<img src="/images/logo-mark.png" alt="" width="28" height="28" aria-hidden="true" style="flex-shrink: 0; display: block;" data-astro-cid-4lunwx2e>
<span class="hidden sm:inline-block font-display font-bold tracking-tight text-zinc-50" style="font-size: 1.02rem; line-height: 1;" data-astro-cid-4lunwx2e>
Intent<span style="color: #FF7A1A;" data-astro-cid-4lunwx2e>&nbsp;Solutions</span>
</span>
</a>
<!-- Desktop navigation -->
<div class="hidden lg:flex items-center gap-1" data-astro-cid-4lunwx2e>
<div class="nav-group relative" data-astro-cid-4lunwx2e>
<button type="button" class="nav-trigger flex items-center gap-1.5 px-3 py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth rounded-md" aria-expanded="false" aria-haspopup="true" data-astro-cid-4lunwx2e>
Solutions
<svg class="nav-chevron" width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true" data-astro-cid-4lunwx2e>
<path d="M1.5 3.5 L5 7 L8.5 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" data-astro-cid-4lunwx2e></path>
</svg>
</button>
<div class="nav-panel" role="menu" data-astro-cid-4lunwx2e>
<div class="nav-panel-inner" data-astro-cid-4lunwx2e>
<a href="/#solutions" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Claude Code Systems
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Custom setups, plugins &amp; team training</span>
</a><a href="/agents" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
AI Agents
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Intent Agent Models for any workflow</span>
</a><a href="/private-ai" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Private AI
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>ChatGPT-class AI on your own cloud</span>
</a><a href="/automation" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Automation
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>n8n workflows that erase manual work</span>
</a><a href="/cloud" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Cloud &amp; Data
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Google Cloud-native architectures</span>
</a>
</div>
</div>
</div><div class="nav-group relative" data-astro-cid-4lunwx2e>
<button type="button" class="nav-trigger flex items-center gap-1.5 px-3 py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth rounded-md" aria-expanded="false" aria-haspopup="true" data-astro-cid-4lunwx2e>
Resources
<svg class="nav-chevron" width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true" data-astro-cid-4lunwx2e>
<path d="M1.5 3.5 L5 7 L8.5 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" data-astro-cid-4lunwx2e></path>
</svg>
</button>
<div class="nav-panel" role="menu" data-astro-cid-4lunwx2e>
<div class="nav-panel-inner" data-astro-cid-4lunwx2e>
<a href="/field-notes" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Field Notes
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Notes from the build</span>
</a><a href="/projects" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Projects
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Shipped work &amp; case studies</span>
</a><a href="https://claudecodeplugins.io" target="_blank" rel="noopener noreferrer" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Plugin Marketplace
<span class="nav-ext" aria-hidden="true" data-astro-cid-4lunwx2e></span>
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>270+ Claude Code plugins</span>
</a>
</div>
</div>
</div><div class="nav-group relative" data-astro-cid-4lunwx2e>
<button type="button" class="nav-trigger flex items-center gap-1.5 px-3 py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth rounded-md" aria-expanded="false" aria-haspopup="true" data-astro-cid-4lunwx2e>
Work With Us
<svg class="nav-chevron" width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true" data-astro-cid-4lunwx2e>
<path d="M1.5 3.5 L5 7 L8.5 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" data-astro-cid-4lunwx2e></path>
</svg>
</button>
<div class="nav-panel" role="menu" data-astro-cid-4lunwx2e>
<div class="nav-panel-inner" data-astro-cid-4lunwx2e>
<a href="/learn" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Learn
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Executive onramps &amp; team enablement</span>
</a><a href="/colab" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Colab
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Implementation partner in your repo</span>
</a><a href="/resellers" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Resellers
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Distribution partner program</span>
</a><a href="/#contact" class="nav-item" role="menuitem" data-astro-cid-4lunwx2e>
<span class="nav-item-label" data-astro-cid-4lunwx2e>
Contact
</span>
<span class="nav-item-desc" data-astro-cid-4lunwx2e>Start a conversation</span>
</a>
</div>
</div>
</div>
<a href="/about" class="px-3 py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>About</a>
<a href="https://calendar.app.google/Wqbt8EJuEh5xvvV58" target="_blank" rel="noopener" class="btn-primary btn-sm text-sm ml-4" data-astro-cid-4lunwx2e>
Book a Call
</a>
</div>
<!-- Mobile / tablet: CTA + hamburger -->
<div class="lg:hidden flex items-center gap-2" data-astro-cid-4lunwx2e>
<a href="https://calendar.app.google/Wqbt8EJuEh5xvvV58" target="_blank" rel="noopener" class="btn-primary btn-sm text-sm whitespace-nowrap" data-astro-cid-4lunwx2e>
Book a Call
</a>
<button type="button" id="nav-burger" class="flex flex-col items-center justify-center w-11 h-11 gap-[5px] rounded-md text-zinc-300" aria-expanded="false" aria-controls="nav-mobile" aria-label="Open menu" data-astro-cid-4lunwx2e>
<span class="burger-bar" data-astro-cid-4lunwx2e></span>
<span class="burger-bar" data-astro-cid-4lunwx2e></span>
<span class="burger-bar" data-astro-cid-4lunwx2e></span>
</button>
</div>
</div>
</div>
<!-- Mobile menu -->
<div id="nav-mobile" class="lg:hidden hidden border-t border-zinc-800/60 bg-zinc-950/95 backdrop-blur-md" data-astro-cid-4lunwx2e>
<div class="container mx-auto px-6 py-5 flex flex-col gap-5" data-astro-cid-4lunwx2e>
<div data-astro-cid-4lunwx2e>
<p class="font-display text-[0.62rem] font-bold uppercase tracking-[0.26em] mb-2" style="color: #FF7A1A;" data-astro-cid-4lunwx2e>
Solutions
</p>
<div class="flex flex-col" data-astro-cid-4lunwx2e>
<a href="/#solutions" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Claude Code Systems
</a><a href="/agents" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
AI Agents
</a><a href="/private-ai" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Private AI
</a><a href="/automation" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Automation
</a><a href="/cloud" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Cloud &amp; Data
</a>
</div>
</div><div data-astro-cid-4lunwx2e>
<p class="font-display text-[0.62rem] font-bold uppercase tracking-[0.26em] mb-2" style="color: #FF7A1A;" data-astro-cid-4lunwx2e>
Resources
</p>
<div class="flex flex-col" data-astro-cid-4lunwx2e>
<a href="/field-notes" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Field Notes
</a><a href="/projects" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Projects
</a><a href="https://claudecodeplugins.io" target="_blank" rel="noopener noreferrer" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Plugin Marketplace ↗
</a>
</div>
</div><div data-astro-cid-4lunwx2e>
<p class="font-display text-[0.62rem] font-bold uppercase tracking-[0.26em] mb-2" style="color: #FF7A1A;" data-astro-cid-4lunwx2e>
Work With Us
</p>
<div class="flex flex-col" data-astro-cid-4lunwx2e>
<a href="/learn" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Learn
</a><a href="/colab" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Colab
</a><a href="/resellers" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Resellers
</a><a href="/#contact" class="py-2 text-sm text-zinc-300 hover:text-zinc-50 transition-smooth" data-astro-cid-4lunwx2e>
Contact
</a>
</div>
</div>
<a href="/about" class="py-1 text-sm font-medium text-zinc-200" data-astro-cid-4lunwx2e>About</a>
</div>
</div>
</nav>
<script type="module">function d(){const n=document.getElementById("nav-burger"),a=document.getElementById("nav-mobile");n&&a&&!n.dataset.bound&&(n.dataset.bound="1",n.addEventListener("click",()=>{const e=n.getAttribute("aria-expanded")==="true";n.setAttribute("aria-expanded",String(!e)),n.setAttribute("aria-label",e?"Open menu":"Close menu"),a.classList.toggle("hidden",e)})),document.querySelectorAll(".nav-group").forEach(e=>{const t=e.querySelector(".nav-trigger");!t||t.dataset.bound||(t.dataset.bound="1",t.addEventListener("click",s=>{s.stopPropagation();const r=e.classList.contains("open");document.querySelectorAll(".nav-group.open").forEach(o=>{o.classList.remove("open"),o.querySelector(".nav-trigger")?.setAttribute("aria-expanded","false")}),e.classList.toggle("open",!r),t.setAttribute("aria-expanded",String(!r))}))}),document.documentElement.dataset.navBound||(document.documentElement.dataset.navBound="1",document.addEventListener("click",()=>{document.querySelectorAll(".nav-group.open").forEach(e=>{e.classList.remove("open"),e.querySelector(".nav-trigger")?.setAttribute("aria-expanded","false")})}),document.addEventListener("keydown",e=>{e.key==="Escape"&&document.querySelectorAll(".nav-group.open").forEach(t=>{t.classList.remove("open"),t.querySelector(".nav-trigger")?.setAttribute("aria-expanded","false")})}))}document.addEventListener("DOMContentLoaded",d);document.addEventListener("astro:page-load",d);</script>
<main class="min-h-screen bg-zinc-950">
<article class="py-16 sm:py-24">
<div class="container mx-auto px-6 sm:px-8 max-w-3xl">
<a href="/field-notes/" class="inline-flex items-center gap-1.5 text-sm text-zinc-500 hover:text-zinc-300 transition-smooth mb-10">
&larr; Field Notes
</a>
<header class="mb-12">
<div class="flex flex-wrap items-center gap-3 text-sm text-zinc-500 mb-4">
<time datetime="2026-03-18">March 18, 2026</time>
<span class="text-zinc-700">&middot;</span>
<div class="flex flex-wrap gap-2">
<span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800/60 text-zinc-500 border border-zinc-700/30">
typescript
</span><span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800/60 text-zinc-500 border border-zinc-700/30">
architecture
</span><span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800/60 text-zinc-500 border border-zinc-700/30">
full-stack
</span><span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800/60 text-zinc-500 border border-zinc-700/30">
automation
</span><span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800/60 text-zinc-500 border border-zinc-700/30">
monorepo
</span>
</div>
</div>
<h1 class="text-h1 font-bold text-zinc-50 mb-4">Knowledge Base from Zero to API: Lifecycle State Machines and Policy Engines</h1>
<p class="text-body-lg text-zinc-400">Building a team knowledge base with lifecycle state machines, policy engines, and git-exported articles — from empty repo to working API in one day.</p>
</header>
<div class="prose-field-notes">
<p>Most knowledge bases are wikis with a search box. Articles go in, articles rot, nobody notices. The problem isn’t storage. It’s governance.</p>
<p>Articles have lifecycles. A draft isn’t ready for the team. A published article that hasn’t been reviewed in six months is a liability. An archived article should still be findable but clearly marked stale. If your knowledge base doesn’t model these states explicitly, you’re building a graveyard with good intentions.</p>
<p>That’s what drove <code>qmd-team-intent-kb</code> — Intent Solutions’ internal knowledge management system, built from empty directory to working API in a single day.</p>
<h2 id="the-state-machine">The State Machine</h2>
<p>Every knowledge article has a lifecycle with four states:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>draft → review → published → archived</span></span></code></pre>
<p>This isn’t a database column with free-text values. It’s a state machine with explicit transitions and guards:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">type</span><span style="color:#B392F0"> ArticleState</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "draft"</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> "review"</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> "published"</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> "archived"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">interface</span><span style="color:#B392F0"> StateTransition</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#FFAB70"> from</span><span style="color:#F97583">:</span><span style="color:#B392F0"> ArticleState</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#FFAB70"> to</span><span style="color:#F97583">:</span><span style="color:#B392F0"> ArticleState</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0"> guard</span><span style="color:#F97583">?:</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">article</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Article</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">actor</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Actor</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#79B8FF"> boolean</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> VALID_TRANSITIONS</span><span style="color:#F97583">:</span><span style="color:#B392F0"> StateTransition</span><span style="color:#E1E4E8">[] </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> [</span></span>
<span class="line"><span style="color:#E1E4E8"> { from: </span><span style="color:#9ECBFF">"draft"</span><span style="color:#E1E4E8">, to: </span><span style="color:#9ECBFF">"review"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">guard</span><span style="color:#E1E4E8">: (</span><span style="color:#FFAB70">a</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> a.body.</span><span style="color:#79B8FF">length</span><span style="color:#F97583"> ></span><span style="color:#79B8FF"> 0</span><span style="color:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#E1E4E8"> { from: </span><span style="color:#9ECBFF">"review"</span><span style="color:#E1E4E8">, to: </span><span style="color:#9ECBFF">"published"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">guard</span><span style="color:#E1E4E8">: (</span><span style="color:#FFAB70">_</span><span style="color:#E1E4E8">, </span><span style="color:#FFAB70">actor</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> actor.role </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> "reviewer"</span><span style="color:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#E1E4E8"> { from: </span><span style="color:#9ECBFF">"review"</span><span style="color:#E1E4E8">, to: </span><span style="color:#9ECBFF">"draft"</span><span style="color:#E1E4E8"> }, </span><span style="color:#6A737D">// rejection</span></span>
<span class="line"><span style="color:#E1E4E8"> { from: </span><span style="color:#9ECBFF">"published"</span><span style="color:#E1E4E8">, to: </span><span style="color:#9ECBFF">"archived"</span><span style="color:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#E1E4E8"> { from: </span><span style="color:#9ECBFF">"published"</span><span style="color:#E1E4E8">, to: </span><span style="color:#9ECBFF">"review"</span><span style="color:#E1E4E8"> }, </span><span style="color:#6A737D">// content update requires re-review</span></span>
<span class="line"><span style="color:#E1E4E8"> { from: </span><span style="color:#9ECBFF">"archived"</span><span style="color:#E1E4E8">, to: </span><span style="color:#9ECBFF">"draft"</span><span style="color:#E1E4E8"> }, </span><span style="color:#6A737D">// resurrection</span></span>
<span class="line"><span style="color:#E1E4E8">];</span></span></code></pre>
<p>You can’t publish an empty draft. You can’t skip review. You can’t go from archived straight to published without going through draft and review again. The state machine enforces these rules at the domain level — not in a UI form validator that someone can bypass.</p>
<p>The guard functions are the key insight. They encode business rules. “Only reviewers can publish” isn’t a UI concern. It’s a domain invariant.</p>
<h2 id="the-policy-engine">The Policy Engine</h2>
<p>State transitions tell you what’s allowed. The policy engine tells you what <em>should happen</em>. Different concerns.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">interface</span><span style="color:#B392F0"> Policy</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#FFAB70"> name</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#FFAB70"> trigger</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> "on_transition"</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> "on_schedule"</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> "on_content_change"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0"> evaluate</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">article</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Article</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#B392F0"> PolicyResult</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// Example: stale content detection</span></span>
<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> staleContentPolicy</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Policy</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> name: </span><span style="color:#9ECBFF">"stale-content-check"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> trigger: </span><span style="color:#9ECBFF">"on_schedule"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#B392F0"> evaluate</span><span style="color:#E1E4E8">: (</span><span style="color:#FFAB70">article</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">=></span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583"> const</span><span style="color:#79B8FF"> daysSinceUpdate</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> daysBetween</span><span style="color:#E1E4E8">(article.updatedAt, </span><span style="color:#B392F0">now</span><span style="color:#E1E4E8">());</span></span>
<span class="line"><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (article.state </span><span style="color:#F97583">===</span><span style="color:#9ECBFF"> "published"</span><span style="color:#F97583"> &#x26;&#x26;</span><span style="color:#E1E4E8"> daysSinceUpdate </span><span style="color:#F97583">></span><span style="color:#79B8FF"> 180</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E1E4E8"> action: </span><span style="color:#9ECBFF">"flag"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> severity: </span><span style="color:#9ECBFF">"warning"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> message: </span><span style="color:#9ECBFF">`Published ${</span><span style="color:#E1E4E8">daysSinceUpdate</span><span style="color:#9ECBFF">} days ago without update`</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> };</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> { action: </span><span style="color:#9ECBFF">"pass"</span><span style="color:#E1E4E8"> };</span></span>
<span class="line"><span style="color:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#E1E4E8">};</span></span></code></pre>
<p>Three trigger types. Transition policies fire on state changes — notify the team when something gets published. Schedule policies run periodically — the stale content check above. Content change policies fire on edits — flag articles that lose more than 50% of their content.</p>
<p>Without this separation, governance logic leaks into every handler and cron job. The policy engine centralizes the rules and makes them auditable.</p>
<h2 id="monorepo-scaffolding">Monorepo Scaffolding</h2>
<p>pnpm workspaces. Not because monorepos are trendy — because the knowledge base has distinct packages that share types:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>packages/</span></span>
<span class="line"><span>├── core/ # Domain model, state machine, types</span></span>
<span class="line"><span>├── policy/ # Policy engine and built-in policies</span></span>
<span class="line"><span>├── store/ # Persistent storage layer</span></span>
<span class="line"><span>├── api/ # REST endpoints (CRUD + search)</span></span>
<span class="line"><span>├── curator/ # Automated content curation</span></span>
<span class="line"><span>└── git-exporter/ # Export articles as git-tracked markdown</span></span></code></pre>
<p><code>core</code> owns the <code>Article</code> type and the state machine. Every other package depends on <code>core</code> but not on each other. The API doesn’t know about the git exporter. The curator doesn’t know about the API. Clean dependency graph from day one.</p>
<h2 id="the-curator">The Curator</h2>
<p>The curator is the automated maintenance layer. It’s the package that actually uses the policy engine to do work:</p>
<ul>
<li>Flags stale published articles for re-review</li>
<li>Detects duplicate or near-duplicate content</li>
<li>Surfaces articles that are frequently linked but haven’t been updated</li>
<li>Generates review queues for team leads</li>
</ul>
<p>Without a curator, governance policies are just documentation. With one, they’re enforced.</p>
<h2 id="the-git-exporter">The Git Exporter</h2>
<p>Every published article gets exported as a markdown file in a git repository. Changes create commits. Metadata becomes front matter.</p>
<p>This gives you three things no knowledge base UI provides: version history (<code>git log --follow</code> on any article), portability (your content is markdown files, not vendor-locked blobs), and review workflows (article updates can trigger PRs where reviewers use diff views and inline comments instead of whatever review UI the KB ships with).</p>
<h2 id="the-full-day">The Full Day</h2>
<p>Phase 0 was repo foundation — monorepo scaffolding, pnpm workspace config, CI pipeline, security hygiene (removing credential keys from tracking on the first commit, not the tenth).</p>
<p>Phase 1 delivered the core domain model and the lifecycle state machine. This is the load-bearing code. Everything else is plumbing around it.</p>
<p>Phases 2-3 hardened CI. Prettierignore for generated files, pnpm version pinning to avoid CI drift.</p>
<p>Phases 4-6 shipped the rest: policy engine, storage layer, REST API, curator, and git exporter. PR #14 merged all of it to main.</p>
<h2 id="the-side-quests">The Side Quests</h2>
<p>Three other projects shipped on the same day: <strong>excel-analyst-pro v1.1.0</strong> (skill-creator compliance overhaul across all four skills), <strong>claude-code-plugins v2.1.78</strong> (new <code>pr-to-spec</code> MCP plugin in the marketplace), and <strong>intent-blueprint-docs v2.9.0</strong> (gstack integration plus repo health audit). Multi-project days are the norm when your CI is solid enough to trust.</p>
<h2 id="the-takeaway">The Takeaway</h2>
<p>Knowledge management for engineering teams isn’t a wiki problem. It’s a state machine problem. Model the lifecycle explicitly. Enforce governance with policies, not discipline. Automate maintenance with a curator. Export to git for portability.</p>
<p>The hard part isn’t the tooling — a state machine is a lookup table, a policy engine is a list of rules. The hard part is deciding to treat knowledge articles as first-class domain objects instead of blobs of markdown in a database.</p>
<hr>
<h3 id="related-posts">Related Posts</h3>
<ul>
<li><a href="/field-notes/zero-to-ci-full-stack-dashboard-one-session">Zero to CI: Full-Stack Dashboard in One Session</a> — Another greenfield project from empty repo to working system in one day</li>
<li><a href="https://startaitools.com/posts/from-tool-to-platform-intentcad-five-epics-one-day/">From Tool to Platform: IntentCAD Ships Five EPICs in One Day</a> — Multi-EPIC shipping day with architectural pivots</li>
<li><a href="https://startaitools.com/posts/production-release-engineering-v450/">Production Release Engineering: Shipping v4.5.0</a> — Automated release workflows and CI discipline at scale</li>
</ul>
</div>
</div>
</article>
</main>
<style>astro-island,astro-slot,astro-static-slot{display:contents}</style><script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event("astro:load"));})();</script><script>(()=>{var A=Object.defineProperty;var g=(i,o,a)=>o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=>g(i,typeof o!="symbol"?o+"":o,a);{let i={0:t=>m(t),1:t=>a(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(a(t)),5:t=>new Set(a(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>1/0*t},o=t=>{let[l,e]=t;return l in i?i[l](e):void 0},a=t=>t.map(o),m=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=>[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,"Component");d(this,"hydrator");d(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let c=this.querySelectorAll("astro-slot"),n={},h=this.querySelectorAll("template[data-astro-template]");for(let r of h){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("data-astro-template")||"default"]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("name")||"default"]=r.innerHTML)}let p;try{p=this.hasAttribute("props")?m(JSON.parse(this.getAttribute("props"))):{}}catch(r){let s=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute("props"),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});d(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),c=this.getAttribute("client");if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=>this.start(),{once:!0});return}try{await Astro[c](async()=>{let n=this.getAttribute("renderer-url"),[h,{default:p}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),u=this.getAttribute("component-export")||"default";if(!u.includes("."))this.Component=h[u];else{this.Component=h;for(let f of u.split("."))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute("component-url")}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",y)}})();</script><astro-island uid="ZHlEDc" prefix="r1" component-url="/_astro/Footer.D_bfTucM.js" component-export="default" renderer-url="/_astro/client.B_PwMJWB.js" props="{}" ssr client="load" opts="{&quot;name&quot;:&quot;Footer&quot;,&quot;value&quot;:true}" await-children><footer style="background:rgb(9 9 11);border-top:1px solid rgba(39,39,42,0.6);padding:4.5rem 0 2rem"><div style="max-width:72rem;margin:0 auto;padding:0 2rem"><div class="footer-top" style="display:grid;grid-template-columns:1.4fr 0.8fr 0.9fr 1.4fr;gap:2.5rem;margin-bottom:3.5rem"><div><div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:1rem"><span style="width:0.5rem;height:0.5rem;border-radius:50%;background:rgb(251 146 60);box-shadow:0 0 8px rgba(249,115,22,0.55);flex-shrink:0"></span><span style="font-family:&#x27;Syne&#x27;, system-ui, sans-serif;font-size:0.95rem;font-weight:700;letter-spacing:0.04em;color:rgb(228 228 231)">Intent <span style="color:#FF7A1A">Solutions</span></span></div><p style="font-size:0.875rem;color:rgb(82 82 91);line-height:1.7;max-width:22rem;margin-bottom:1.5rem">creating industries that don&#x27;t exist</p><p style="font-family:&#x27;Syne&#x27;, system-ui, sans-serif;font-size:0.6rem;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;color:rgb(63 63 70);margin:0">gulf shores, alabama</p></div><div><p style="font-family:&#x27;Syne&#x27;, system-ui, sans-serif;font-size:0.6rem;font-weight:700;letter-spacing:0.22em;text-transform:uppercase;color:rgb(82 82 91);margin-bottom:1.25rem">Services</p><a href="/learn" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Learn</a><a href="/colab" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Colab</a><a href="/agents" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Agents</a><a href="/private-ai" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Private AI</a><a href="/automation" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Automation</a><a href="/cloud" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Cloud &amp; Data</a><a href="/resellers" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Resellers</a></div><div><p style="font-family:&#x27;Syne&#x27;, system-ui, sans-serif;font-size:0.6rem;font-weight:700;letter-spacing:0.22em;text-transform:uppercase;color:rgb(82 82 91);margin-bottom:1.25rem">Resources</p><a href="/field-notes" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Field Notes</a><a href="https://claudecodeplugins.io" target="_blank" rel="noopener noreferrer" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Claude Code Plugins</a><a href="https://github.com/jeremylongshore" target="_blank" rel="noopener noreferrer" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">GitHub</a><a href="https://huggingface.co/intent-solutions-io" target="_blank" rel="noopener noreferrer" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Hugging Face</a><a href="https://whop.com/claude-code-cowork-accelerator/" target="_blank" rel="noopener noreferrer" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">Cowork Accelerator</a><div style="margin-top:1.25rem"><a href="mailto:jeremy@intentsolutions.io" style="font-size:0.875rem;color:rgb(113 113 122);text-decoration:none;transition:color 0.2s ease;display:block;padding-bottom:0.5rem">jeremy@intentsolutions.io</a></div></div><div><p style="font-family:&#x27;Syne&#x27;, system-ui, sans-serif;font-size:0.6rem;font-weight:700;letter-spacing:0.22em;text-transform:uppercase;color:rgb(82 82 91);margin-bottom:1.25rem">FAQ</p><div style="display:flex;flex-direction:column"><details class="footer-faq"><summary><span>What exactly is a Claude Code setup?</span><svg class="faq-chevron" width="11" height="11" viewBox="0 0 10 10" fill="none" aria-hidden="true"><path d="M1.5 3.5 L5 7 L8.5 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"></path></svg></summary><p>We configure Claude Code for your specific tech stack, team size, and workflows — custom CLAUDE.md files, curated plugin packs, slash commands, MCP connections, and hooks, tuned so your team gets value from day one.</p></details><details class="footer-faq"><summary><span>Do I need to know how to code?</span><svg class="faq-chevron" width="11" height="11" viewBox="0 0 10 10" fill="none" aria-hidden="true"><path d="M1.5 3.5 L5 7 L8.5 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"></path></svg></summary><p>Not at all. The Learn track is built for decision-makers and team leads — we show you what Claude Code can do and how it fits your org, no coding required.</p></details><details class="footer-faq"><summary><span>What&#x27;s included in the discovery call?</span><svg class="faq-chevron" width="11" height="11" viewBox="0 0 10 10" fill="none" aria-hidden="true"><path d="M1.5 3.5 L5 7 L8.5 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"></path></svg></summary><p>A 30-minute conversation where we learn about your team, workflows, and goals. You&#x27;ll get honest recommendations — or an honest &#x27;not a fit.&#x27; No pressure, no pitch deck.</p></details><details class="footer-faq"><summary><span>What happens after the initial setup?</span><svg class="faq-chevron" width="11" height="11" viewBox="0 0 10 10" fill="none" aria-hidden="true"><path d="M1.5 3.5 L5 7 L8.5 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"></path></svg></summary><p>Every engagement includes a support window. We stay available for questions, plugin tweaks, and workflow adjustments — enterprise clients get ongoing retainers with quarterly workshops.</p></details></div></div></div><div aria-hidden="true" style="height:1px;background:linear-gradient(to right, transparent, rgba(39,39,42,0.8), transparent);margin-bottom:1.75rem"></div><div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:1rem"><p style="font-size:0.75rem;color:rgb(63 63 70);margin:0">© 2025 intent solutions io. all rights reserved.</p><div style="display:flex;align-items:center;gap:1.5rem"><a href="/terms" style="font-size:0.72rem;color:rgb(63 63 70);text-decoration:none;transition:color 0.2s ease">Terms of Service</a><a href="/privacy" style="font-size:0.72rem;color:rgb(63 63 70);text-decoration:none;transition:color 0.2s ease">Privacy Policy</a><a href="/acceptable-use" style="font-size:0.72rem;color:rgb(63 63 70);text-decoration:none;transition:color 0.2s ease">Acceptable Use</a></div></div></div></footer><!--astro:end--></astro-island>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Knowledge Base from Zero to API: Lifecycle State Machines and Policy Engines","description":"Building a team knowledge base with lifecycle state machines, policy engines, and git-exported articles — from empty repo to working API in one day.","datePublished":"2026-03-18","author":{"@type":"Person","name":"Jeremy Longshore"},"publisher":{"@type":"Organization","name":"intent solutions io","url":"https://intentsolutions.io"},"mainEntityOfPage":"https://startaitools.com/posts/knowledge-base-zero-to-api-lifecycle-state-machine/"}</script>
</body></html>