import{_ as n,C as r,o as e,c as l,a2 as i,b as p,w as a,a as o,E as c,a3 as d}from"./chunks/framework.CJEbdX1t.js";const C=JSON.parse('{"title":"System Design","description":"","frontmatter":{},"headers":[],"relativePath":"architecture/system-design.md","filePath":"architecture/system-design.md"}'),h={name:"architecture/system-design.md"};function k(g,s,u,y,m,E){const t=r("Mermaid");return e(),l("div",null,[s[1]||(s[1]=i(`<h1 id="system-design" tabindex="-1">System Design <a class="header-anchor" href="#system-design" aria-label="Permalink to &quot;System Design&quot;">​</a></h1><p>Specwright is a Python application built on FastAPI, with modular components for spec parsing, agent analysis, and ticket synchronization.</p><h2 id="platform-components" tabindex="-1">Platform Components <a class="header-anchor" href="#platform-components" aria-label="Permalink to &quot;Platform Components&quot;">​</a></h2><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>┌──────────────┐  ┌──────────────┐  ┌─────────────┐</span></span>
<span class="line"><span>│  Doc Engine   │  │ Agent Runner │  │  Knowledge  │</span></span>
<span class="line"><span>│               │  │              │  │    Base     │</span></span>
<span class="line"><span>│ Parse all md  │  │ Claude calls │  │ Vector DB   │</span></span>
<span class="line"><span>│ Track specs   │  │ PR analysis  │  │ BM25 index  │</span></span>
<span class="line"><span>│ Diff &amp; delta  │  │ Realization  │  │ Hybrid      │</span></span>
<span class="line"><span>│ Verify intent │  │ Doc gen      │  │ search      │</span></span>
<span class="line"><span>└───────────────┘  └──────────────┘  └─────────────┘</span></span>
<span class="line"><span>         │               │               │</span></span>
<span class="line"><span>    ┌────┴───┐     ┌─────┴────┐    ┌─────┴────┐</span></span>
<span class="line"><span>    │ Tickets│     │  Comms   │    │Dev Tools │</span></span>
<span class="line"><span>    │        │     │          │    │          │</span></span>
<span class="line"><span>    │ Jira   │     │ Slack    │    │ MCP      │</span></span>
<span class="line"><span>    │ Linear │     │ Teams    │    │ VS Code  │</span></span>
<span class="line"><span>    │ GitHub │     │ Email    │    │ Cursor   │</span></span>
<span class="line"><span>    └────────┘     └──────────┘    └──────────┘</span></span></code></pre></div><h2 id="fastapi-app" tabindex="-1">FastAPI App <a class="header-anchor" href="#fastapi-app" aria-label="Permalink to &quot;FastAPI App {#fastapi-app}&quot;">​</a></h2><p><strong>File:</strong> <code>src/specwright/main.py</code></p><p>The main application exposes:</p><ul><li><code>POST /webhook</code> — GitHub App webhook endpoint</li><li><code>GET /healthz</code> — Health check</li><li><code>POST /auth/device/code</code> + <code>/auth/device/token</code> — Device Authorization Grant for CLI login</li><li><code>POST /{org}/api/tickets/*</code> — Server-proxied ticket CRUD (used by CLI sync)</li><li>Web routes for the SPA and fallback templates</li></ul><p>All webhook events are verified using HMAC SHA-256 signature checking before being dispatched to handlers.</p><h2 id="spec-parser" tabindex="-1">Spec Parser <a class="header-anchor" href="#spec-parser" aria-label="Permalink to &quot;Spec Parser {#spec-parser}&quot;">​</a></h2><p><strong>Files:</strong> <code>src/specwright/parser/</code></p><p>The parser processes markdown spec files into structured Pydantic models:</p><ul><li><code>parse.py</code> — Main parser (frontmatter extraction + section parsing + status comment parsing)</li><li><code>models.py</code> — Pydantic models: <code>SpecDocument</code>, <code>SpecSection</code>, <code>AcceptanceCriterion</code></li><li><code>writer.py</code> — Writes back to spec files (insert ticket links, update status comments)</li></ul><h3 id="parse-flow" tabindex="-1">Parse Flow <a class="header-anchor" href="#parse-flow" aria-label="Permalink to &quot;Parse Flow&quot;">​</a></h3>`,14)),(e(),p(d,null,{default:a(()=>[c(t,{id:"mermaid-78",class:"mermaid",graph:"flowchart%20LR%0A%20%20%20%20MD%5BMarkdown%20File%5D%20--%3E%20FM%5BExtract%20Frontmatter%5D%0A%20%20%20%20FM%20--%3E%20SEC%5BParse%20Sections%5D%0A%20%20%20%20SEC%20--%3E%20AC%5BExtract%20ACs%5D%0A%20%20%20%20SEC%20--%3E%20ST%5BParse%20Status%20Comments%5D%0A%20%20%20%20SEC%20--%3E%20TK%5BParse%20Ticket%20Links%5D%0A%20%20%20%20AC%20--%3E%20DOC%5BSpecDocument%5D%0A%20%20%20%20ST%20--%3E%20DOC%0A%20%20%20%20TK%20--%3E%20DOC%0A"})]),fallback:a(()=>[...s[0]||(s[0]=[o(" Loading... ",-1)])]),_:1})),s[2]||(s[2]=i(`<h2 id="agent-runtime" tabindex="-1">Agent Runtime <a class="header-anchor" href="#agent-runtime" aria-label="Permalink to &quot;Agent Runtime {#agent-runtime}&quot;">​</a></h2><p><strong>Files:</strong> <code>src/specwright/agent/</code></p><p>The Claude agent runtime handles PR analysis:</p><ul><li><code>client.py</code> — Anthropic SDK wrapper with model config and error types</li><li><code>analyzer.py</code> — Analysis orchestration, response parsing, comment formatting</li><li><code>prompts.py</code> — System prompt construction, user message builder, token estimation</li></ul><h3 id="model-configuration" tabindex="-1">Model Configuration <a class="header-anchor" href="#model-configuration" aria-label="Permalink to &quot;Model Configuration&quot;">​</a></h3><p>Default values as of the current release — see <code>agent/client.py</code> for the latest:</p><table tabindex="0"><thead><tr><th>Parameter</th><th>Value</th></tr></thead><tbody><tr><td>Model</td><td>Claude Sonnet 4.5</td></tr><tr><td>Max input tokens</td><td>128,000</td></tr><tr><td>Max output tokens</td><td>16,000</td></tr><tr><td>Temperature</td><td>0 (deterministic)</td></tr></tbody></table><h3 id="prompt-architecture" tabindex="-1">Prompt Architecture <a class="header-anchor" href="#prompt-architecture" aria-label="Permalink to &quot;Prompt Architecture&quot;">​</a></h3><p>The system prompt is assembled from three pieces:</p><ol><li><strong>Base system prompt</strong> — Agent role, rules, severity levels</li><li><strong>Realization instructions</strong> — AC evaluation criteria (when enabled)</li><li><strong>JSON schema</strong> — Structured output enforcement</li></ol><p>The user message includes PR metadata, spec summaries, context docs, changed file list, and diffs (budget-limited).</p><h2 id="github-client" tabindex="-1">GitHub Client <a class="header-anchor" href="#github-client" aria-label="Permalink to &quot;GitHub Client {#github-client}&quot;">​</a></h2><p><strong>Files:</strong> <code>src/specwright/github/</code></p><ul><li><code>verify.py</code> — HMAC SHA-256 webhook signature verification</li><li><code>client.py</code> — GitHub API client (JWT auth via GitHub App, httpx)</li><li><code>spec_utils.py</code> — Spec file detection and loading from GitHub</li><li><code>handlers/</code> — Event handlers for push, PR, issue_comment, etc.</li></ul><h2 id="ticket-sync" tabindex="-1">Ticket Sync <a class="header-anchor" href="#ticket-sync" aria-label="Permalink to &quot;Ticket Sync {#ticket-sync}&quot;">​</a></h2><p><strong>Files:</strong> <code>src/specwright/sync/</code></p><p>Bidirectional synchronization between spec sections and external ticket systems:</p><ul><li><code>engine.py</code> — Forward/reverse sync logic</li><li><code>status_map.py</code> — Bidirectional spec-to-ticket status mapping</li><li><code>models.py</code> — Pydantic models for sync operations</li><li><code>adapters/</code> — Per-system adapters (Jira, Linear, GitHub Issues, API Proxy)</li></ul><h3 id="server-proxied-sync" tabindex="-1">Server-Proxied Sync <a class="header-anchor" href="#server-proxied-sync" aria-label="Permalink to &quot;Server-Proxied Sync&quot;">​</a></h3><p><strong>Files:</strong> <code>src/specwright/sync/adapters/api_proxy.py</code>, <code>src/specwright/web/ticket_routes.py</code></p><p>When CLI users are logged in (<code>specwright login</code>), the sync engine uses the <code>SpecwrightApiAdapter</code> instead of calling GitHub directly. This adapter routes ticket CRUD through the Specwright server&#39;s <code>/api/tickets/*</code> endpoints, which use the GitHub App installation token. The server endpoints are in <code>web/ticket_routes.py</code>.</p><h3 id="adapter-interface" tabindex="-1">Adapter Interface <a class="header-anchor" href="#adapter-interface" aria-label="Permalink to &quot;Adapter Interface&quot;">​</a></h3><div class="language-python vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">python</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> TicketAdapter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> create_ticket</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self, input: CreateTicketInput) -&gt; CreateTicketResult</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> update_ticket(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, changes: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">dict</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;">-&gt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> None</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> sync_status(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;">-&gt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> str</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> link_pr(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, ticket_id: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, pr_url: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;">-&gt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> None</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> close_ticket(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, evidence: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;">-&gt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> None</span></span></code></pre></div><h2 id="config-parser" tabindex="-1">Config Parser <a class="header-anchor" href="#config-parser" aria-label="Permalink to &quot;Config Parser {#config-parser}&quot;">​</a></h2><p><strong>Files:</strong> <code>src/specwright/config/</code></p><ul><li><code>parse.py</code> — YAML parsing into Pydantic models with validation</li></ul><p>Validates <code>SPECWRIGHT.yaml</code> files and provides typed access to all configuration values. Unknown keys produce warnings rather than errors for forward compatibility.</p><h2 id="cron-jobs" tabindex="-1">Cron Jobs <a class="header-anchor" href="#cron-jobs" aria-label="Permalink to &quot;Cron Jobs&quot;">​</a></h2><p><strong>Files:</strong> <code>src/specwright/cron/</code></p><ul><li><code>sync_status.py</code> — Standalone reverse sync script run as a Kubernetes CronJob</li></ul><p>Periodically polls ticket systems for status changes and updates spec files accordingly.</p>`,31))])}const b=n(h,[["render",k]]);export{C as __pageData,b as default};
