@spec-engine/spec-engine 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -461,7 +461,7 @@
461
461
  </ol>
462
462
  <div class="sl-heading-wrapper level-h2"><h2 id="http-path">HTTP path</h2><a class="sl-anchor-link" href="#http-path"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “HTTP path”</span></a></div>
463
463
  <ol>
464
- <li><code dir="auto">spec serve</code> composes the engine’s API routes and the webapp’s pages onto one Hono app, bound to <code dir="auto">127.0.0.1</code>.</li>
464
+ <li><code dir="auto">spec serve</code> composes the engine’s API routes and the webapp’s pages onto one Hono app, bound to <code dir="auto">127.0.0.1</code> or one private <code dir="auto">--host</code> address.</li>
465
465
  <li>Pages call <code dir="auto">/api/*</code> in-process. There is no loopback fetch.</li>
466
466
  <li>A feature whose flag is off answers 404 on its endpoints and renders a placeholder page.</li>
467
467
  </ol>
@@ -70,7 +70,7 @@
70
70
  })();
71
71
  </script> </sl-sidebar-state-persist> <div class="md:sl-hidden"> <div class="mobile-preferences sl-flex astro-2lx56rho"> <div class="social-icons astro-2lx56rho"> <a href="https://github.com/spec-engine/spec-engine" rel="me" class="sl-flex astro-opyhioab"><span class="sr-only astro-opyhioab">GitHub</span><svg aria-hidden="true" class="astro-opyhioab astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.83 1.24 1.83 1.24 1.08 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3Z"/></svg></a> </div> <starlight-theme-select> <label style="--sl-select-width: 6.25em" class="astro-oxpvutiw"> <span class="sr-only astro-oxpvutiw">Select theme</span> <svg aria-hidden="true" class="icon label-icon astro-oxpvutiw astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21 14h-1V7a3 3 0 0 0-3-3H7a3 3 0 0 0-3 3v7H3a1 1 0 0 0-1 1v2a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-2a1 1 0 0 0-1-1ZM6 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7H6V7Zm14 10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1h16v1Z"/></svg> <select autocomplete="off" class="astro-oxpvutiw"> <option value="dark" class="astro-oxpvutiw">Dark</option><option value="light" class="astro-oxpvutiw">Light</option><option value="auto" selected class="astro-oxpvutiw">Auto</option> </select> <svg aria-hidden="true" class="icon caret astro-oxpvutiw astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M17 9.17a1 1 0 0 0-1.41 0L12 12.71 8.46 9.17a1 1 0 1 0-1.41 1.42l4.24 4.24a1.002 1.002 0 0 0 1.42 0L17 10.59a1.002 1.002 0 0 0 0-1.42Z"/></svg> </label> </starlight-theme-select> <script>
72
72
  StarlightThemeProvider.updatePickers();
73
- </script> </div> </div> </div> </div> </nav> <div class="main-frame astro-to3xypmc"> <script type="module">const a=document.getElementById("starlight__sidebar"),n=a?.querySelector("sl-sidebar-state-persist"),o="sl-sidebar-state",i=()=>{let t=[];const e=n?.dataset.hash||"";try{const s=sessionStorage.getItem(o),r=JSON.parse(s||"{}");Array.isArray(r.open)&&r.hash===e&&(t=r.open)}catch{}return{hash:e,open:t,scroll:a?.scrollTop||0}},c=t=>{try{sessionStorage.setItem(o,JSON.stringify(t))}catch{}},d=()=>c(i()),l=(t,e)=>{const s=i();s.open[e]=t,c(s)};n?.addEventListener("click",t=>{if(!(t.target instanceof Element))return;const e=t.target.closest("summary")?.closest("details");if(!e)return;const s=e.querySelector("sl-sidebar-restore"),r=parseInt(s?.dataset.index||"");isNaN(r)||l(!e.open,r)});addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&d()});addEventListener("pageHide",d);</script> <div class="lg:sl-flex astro-blhl7r6q"> <aside class="right-sidebar-container print:hidden astro-blhl7r6q"> <div class="right-sidebar astro-blhl7r6q"> <div class="lg:sl-hidden astro-l5m3cfel"><mobile-starlight-toc data-min-h="2" data-max-h="3" class="astro-goxitxj7"><nav aria-labelledby="starlight__on-this-page--mobile" class="astro-goxitxj7"><details id="starlight__mobile-toc" class="astro-goxitxj7"><summary id="starlight__on-this-page--mobile" class="sl-flex astro-goxitxj7"><span class="toggle sl-flex astro-goxitxj7">On this page<svg aria-hidden="true" class="caret astro-goxitxj7 astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></span><span class="display-current astro-goxitxj7"></span></summary><div class="dropdown astro-goxitxj7"><ul class="isMobile astro-xz67lhkg" style="--depth: 0;"> <li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#_top" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Overview</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#exit-code-contract" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Exit code contract</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#common-flags" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Common flags</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#command-reference" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Command reference</span> </a> <ul class="isMobile astro-xz67lhkg" style="--depth: 1;"> <li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-index-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec index [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-check-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec check [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-guard-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec guard [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-map-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec map [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-get-key-nnn-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec get &lt;KEY-NNN&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-list-platformdir---domain-key---status-s" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec list [platformDir] [--domain KEY] [--status S]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-query-text-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec query &lt;text&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-relations-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec relations [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-provenance-issueid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec provenance [issueId] [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-resolve-files-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec resolve &lt;files...&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-propagation-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec propagation &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-gate-repo-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec gate &lt;repo&gt; &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-init-repo" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec init [repo]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-domain-new-name--spec-domain-list-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec domain new &lt;name&gt; / spec domain list [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-migrate-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec migrate [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-req-domain-prefix-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec req &lt;domain-prefix&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-term-" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec term …</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-glossary-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec glossary [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-supersede-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec supersede &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-move-reqid-new-domain-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec move &lt;reqId&gt; &lt;NEW-DOMAIN&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-amend-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec amend &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-accept-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec accept &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-serve-platformdir---port-n" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec serve [platformDir] [--port N]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-mcp-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec mcp [platformDir]</span> </a> </li> </ul> </li> </ul></div></details></nav></mobile-starlight-toc><script type="module" src="/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.hwBsy0Mo.js"></script></div><div class="right-sidebar-panel sl-hidden lg:sl-block astro-l5m3cfel"><div class="sl-container astro-l5m3cfel"><starlight-toc data-min-h="2" data-max-h="3"><nav aria-labelledby="starlight__on-this-page"><h2 id="starlight__on-this-page">On this page</h2><ul class="astro-xz67lhkg" style="--depth: 0;"> <li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#_top" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Overview</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#exit-code-contract" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Exit code contract</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#common-flags" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Common flags</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#command-reference" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Command reference</span> </a> <ul class="astro-xz67lhkg" style="--depth: 1;"> <li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-index-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec index [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-check-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec check [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-guard-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec guard [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-map-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec map [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-get-key-nnn-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec get &lt;KEY-NNN&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-list-platformdir---domain-key---status-s" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec list [platformDir] [--domain KEY] [--status S]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-query-text-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec query &lt;text&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-relations-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec relations [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-provenance-issueid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec provenance [issueId] [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-resolve-files-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec resolve &lt;files...&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-propagation-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec propagation &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-gate-repo-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec gate &lt;repo&gt; &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-init-repo" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec init [repo]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-domain-new-name--spec-domain-list-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec domain new &lt;name&gt; / spec domain list [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-migrate-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec migrate [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-req-domain-prefix-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec req &lt;domain-prefix&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-term-" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec term …</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-glossary-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec glossary [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-supersede-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec supersede &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-move-reqid-new-domain-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec move &lt;reqId&gt; &lt;NEW-DOMAIN&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-amend-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec amend &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-accept-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec accept &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-serve-platformdir---port-n" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec serve [platformDir] [--port N]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-mcp-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec mcp [platformDir]</span> </a> </li> </ul> </li> </ul></nav></starlight-toc><script type="module" src="/_astro/TableOfContents.astro_astro_type_script_index_0_lang.FuRcXuRY.js"></script></div></div> </div> </aside> <div class="main-pane astro-blhl7r6q"> <main data-pagefind-body class="astro-ioexmpvj" lang="en" dir="ltr"> <div class="content-panel astro-sfndtt72"> <div class="sl-container astro-sfndtt72"> <h1 id="_top" class="astro-4b4mtvou">Commands</h1> </div> </div> <div class="content-panel astro-sfndtt72"> <div class="sl-container astro-sfndtt72"> <div class="sl-markdown-content"> <p>Install the CLI from npm — <code dir="auto">bun add -g @spec-engine/spec-engine</code> puts <code dir="auto">spec</code> on your PATH, and every command below is written that way. Without a global install, run any of them as <code dir="auto">bunx @spec-engine/spec-engine &#x3C;command> [args]</code>; from a checkout, as <code dir="auto">bun packages/engine/src/cli.ts &#x3C;command> [args]</code>. See <a href="/getting-started/">Getting Started</a> for the details.</p>
73
+ </script> </div> </div> </div> </div> </nav> <div class="main-frame astro-to3xypmc"> <script type="module">const a=document.getElementById("starlight__sidebar"),n=a?.querySelector("sl-sidebar-state-persist"),o="sl-sidebar-state",i=()=>{let t=[];const e=n?.dataset.hash||"";try{const s=sessionStorage.getItem(o),r=JSON.parse(s||"{}");Array.isArray(r.open)&&r.hash===e&&(t=r.open)}catch{}return{hash:e,open:t,scroll:a?.scrollTop||0}},c=t=>{try{sessionStorage.setItem(o,JSON.stringify(t))}catch{}},d=()=>c(i()),l=(t,e)=>{const s=i();s.open[e]=t,c(s)};n?.addEventListener("click",t=>{if(!(t.target instanceof Element))return;const e=t.target.closest("summary")?.closest("details");if(!e)return;const s=e.querySelector("sl-sidebar-restore"),r=parseInt(s?.dataset.index||"");isNaN(r)||l(!e.open,r)});addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&d()});addEventListener("pageHide",d);</script> <div class="lg:sl-flex astro-blhl7r6q"> <aside class="right-sidebar-container print:hidden astro-blhl7r6q"> <div class="right-sidebar astro-blhl7r6q"> <div class="lg:sl-hidden astro-l5m3cfel"><mobile-starlight-toc data-min-h="2" data-max-h="3" class="astro-goxitxj7"><nav aria-labelledby="starlight__on-this-page--mobile" class="astro-goxitxj7"><details id="starlight__mobile-toc" class="astro-goxitxj7"><summary id="starlight__on-this-page--mobile" class="sl-flex astro-goxitxj7"><span class="toggle sl-flex astro-goxitxj7">On this page<svg aria-hidden="true" class="caret astro-goxitxj7 astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></span><span class="display-current astro-goxitxj7"></span></summary><div class="dropdown astro-goxitxj7"><ul class="isMobile astro-xz67lhkg" style="--depth: 0;"> <li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#_top" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Overview</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#exit-code-contract" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Exit code contract</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#common-flags" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Common flags</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#command-reference" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Command reference</span> </a> <ul class="isMobile astro-xz67lhkg" style="--depth: 1;"> <li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-index-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec index [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-check-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec check [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-guard-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec guard [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-map-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec map [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-get-key-nnn-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec get &lt;KEY-NNN&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-list-platformdir---domain-key---status-s" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec list [platformDir] [--domain KEY] [--status S]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-query-text-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec query &lt;text&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-relations-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec relations [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-provenance-issueid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec provenance [issueId] [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-resolve-files-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec resolve &lt;files...&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-propagation-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec propagation &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-gate-repo-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec gate &lt;repo&gt; &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-init-repo" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec init [repo]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-domain-new-name--spec-domain-list-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec domain new &lt;name&gt; / spec domain list [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-migrate-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec migrate [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-req-domain-prefix-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec req &lt;domain-prefix&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-term-" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec term …</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-glossary-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec glossary [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-supersede-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec supersede &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-move-reqid-new-domain-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec move &lt;reqId&gt; &lt;NEW-DOMAIN&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-amend-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec amend &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-accept-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec accept &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-serve-platformdir---port-n---host-ip---token-t---allow-writes" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec serve [platformDir] [--port N] [--host IP] [--token T] [--allow-writes]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-mcp-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec mcp [platformDir]</span> </a> </li> </ul> </li> </ul></div></details></nav></mobile-starlight-toc><script type="module" src="/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.hwBsy0Mo.js"></script></div><div class="right-sidebar-panel sl-hidden lg:sl-block astro-l5m3cfel"><div class="sl-container astro-l5m3cfel"><starlight-toc data-min-h="2" data-max-h="3"><nav aria-labelledby="starlight__on-this-page"><h2 id="starlight__on-this-page">On this page</h2><ul class="astro-xz67lhkg" style="--depth: 0;"> <li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#_top" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Overview</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#exit-code-contract" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Exit code contract</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#common-flags" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Common flags</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#command-reference" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Command reference</span> </a> <ul class="astro-xz67lhkg" style="--depth: 1;"> <li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-index-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec index [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-check-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec check [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-guard-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec guard [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-map-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec map [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-get-key-nnn-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec get &lt;KEY-NNN&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-list-platformdir---domain-key---status-s" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec list [platformDir] [--domain KEY] [--status S]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-query-text-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec query &lt;text&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-relations-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec relations [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-provenance-issueid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec provenance [issueId] [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-resolve-files-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec resolve &lt;files...&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-propagation-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec propagation &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-gate-repo-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec gate &lt;repo&gt; &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-init-repo" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec init [repo]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-domain-new-name--spec-domain-list-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec domain new &lt;name&gt; / spec domain list [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-migrate-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec migrate [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-req-domain-prefix-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec req &lt;domain-prefix&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-term-" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec term …</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-glossary-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec glossary [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-supersede-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec supersede &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-move-reqid-new-domain-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec move &lt;reqId&gt; &lt;NEW-DOMAIN&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-amend-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec amend &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-accept-reqid-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec accept &lt;reqId&gt; [platformDir]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-serve-platformdir---port-n---host-ip---token-t---allow-writes" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec serve [platformDir] [--port N] [--host IP] [--token T] [--allow-writes]</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#spec-mcp-platformdir" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">spec mcp [platformDir]</span> </a> </li> </ul> </li> </ul></nav></starlight-toc><script type="module" src="/_astro/TableOfContents.astro_astro_type_script_index_0_lang.FuRcXuRY.js"></script></div></div> </div> </aside> <div class="main-pane astro-blhl7r6q"> <main data-pagefind-body class="astro-ioexmpvj" lang="en" dir="ltr"> <div class="content-panel astro-sfndtt72"> <div class="sl-container astro-sfndtt72"> <h1 id="_top" class="astro-4b4mtvou">Commands</h1> </div> </div> <div class="content-panel astro-sfndtt72"> <div class="sl-container astro-sfndtt72"> <div class="sl-markdown-content"> <p>Install the CLI from npm — <code dir="auto">bun add -g @spec-engine/spec-engine</code> puts <code dir="auto">spec</code> on your PATH, and every command below is written that way. Without a global install, run any of them as <code dir="auto">bunx @spec-engine/spec-engine &#x3C;command> [args]</code>; from a checkout, as <code dir="auto">bun packages/engine/src/cli.ts &#x3C;command> [args]</code>. See <a href="/getting-started/">Getting Started</a> for the details.</p>
74
74
  <div class="sl-heading-wrapper level-h2"><h2 id="exit-code-contract">Exit code contract</h2><a class="sl-anchor-link" href="#exit-code-contract"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Exit code contract”</span></a></div>
75
75
 
76
76
 
@@ -226,8 +226,8 @@
226
226
  <div class="sl-heading-wrapper level-h3"><h3 id="spec-accept-reqid-platformdir"><code dir="auto">spec accept &#x3C;reqId> [platformDir]</code></h3><a class="sl-anchor-link" href="#spec-accept-reqid-platformdir"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “spec accept &#x3C;reqId> [platformDir]”</span></a></div>
227
227
  <p>Promote a Draft requirement to Active — same ID, every other field untouched, no version change. A Draft is never an orphan, never unverified, and never counted in coverage; accepting it makes it an obligation.</p>
228
228
  <p><strong>Exit codes:</strong> 0 / 2</p>
229
- <div class="sl-heading-wrapper level-h3"><h3 id="spec-serve-platformdir---port-n"><code dir="auto">spec serve [platformDir] [--port N]</code></h3><a class="sl-anchor-link" href="#spec-serve-platformdir---port-n"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “spec serve [platformDir] [--port N]”</span></a></div>
230
- <p>Launch the local webapp over the derived index. Binds <code dir="auto">127.0.0.1</code> only. <code dir="auto">--probe</code> boots on an ephemeral port, smoke-tests, and exits.</p>
229
+ <div class="sl-heading-wrapper level-h3"><h3 id="spec-serve-platformdir---port-n---host-ip---token-t---allow-writes"><code dir="auto">spec serve [platformDir] [--port N] [--host IP] [--token T] [--allow-writes]</code></h3><a class="sl-anchor-link" href="#spec-serve-platformdir---port-n---host-ip---token-t---allow-writes"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “spec serve [platformDir] [--port N] [--host IP] [--token T] [--allow-writes]”</span></a></div>
230
+ <p>Launch the local webapp over the derived index on <code dir="auto">127.0.0.1</code>, or on a private <code dir="auto">--host</code> address that requires the printed token and is read-only unless <code dir="auto">--allow-writes</code>. <code dir="auto">--probe</code> smoke-tests and exits.</p>
231
231
  <p><strong>API routes (always on):</strong> <code dir="auto">/api/coverage</code>, <code dir="auto">/api/report</code> (per-domain rollup over Active reqs), <code dir="auto">/api/repos</code>, <code dir="auto">/api/platform</code> (the derived platform version), <code dir="auto">/api/requirements[/:id]</code>, <code dir="auto">/api/propagation/:id</code>, <code dir="auto">/api/resolve?files=...</code></p>
232
232
  <p><strong>Feature-flagged routes:</strong> <code dir="auto">/api/query?q=&#x26;limit=</code>, <code dir="auto">/api/relations[?format=mermaid]</code>, <code dir="auto">/api/provenance</code>, and the editor’s write routes answer <strong>404</strong> until their feature is enabled — the unshipped webapp features (query, relations, provenance, editor, glossary, logs) all default off. Opt in locally with a comma-separated <code dir="auto">SPEC_FLAGS</code> env var: <code dir="auto">SPEC_FLAGS=query,relations spec serve .</code> (unknown keys are ignored).</p>
233
233
  <p><strong>Exit codes:</strong> 0 / 1 / 2</p>
@@ -1 +1 @@
1
- {"version":"1.5.2","languages":{"en":{"hash":"en_beb6d752c1","wasm":"en","page_count":12}},"include_characters":["_","‿","⁀","⁔","︳","︴","﹍","﹎","﹏","_"]}
1
+ {"version":"1.5.2","languages":{"en":{"hash":"en_2ed26d23fb","wasm":"en","page_count":12}},"include_characters":["_","‿","⁀","⁔","︳","︴","﹍","﹎","﹏","_"]}
package/dist/impl.js CHANGED
@@ -20,7 +20,7 @@ var package_default;
20
20
  var init_package = __esm(() => {
21
21
  package_default = {
22
22
  name: "@spec-engine/spec-engine",
23
- version: "0.6.0",
23
+ version: "0.6.1",
24
24
  description: "Cross-repo spec engine \u2014 durable requirements with permanent IDs, bound to code via @spec tags, checked in CI. Ships the spec CLI, the local coverage webapp (spec serve), and offline docs (spec docs).",
25
25
  license: "MIT",
26
26
  type: "module",
@@ -373,10 +373,10 @@ var init_flags = __esm(() => {
373
373
  FEATURE_KEYS = Object.keys(FEATURES);
374
374
  });
375
375
  // ../shared/src/net.ts
376
- function isLoopbackHostname(hostname) {
377
- return LOOPBACK_HOSTNAMES.has(hostname);
376
+ function isServedHostname(hostname, bindHost = DEFAULT_BIND_HOST) {
377
+ return LOOPBACK_HOSTNAMES.has(hostname) || hostname === bindHost;
378
378
  }
379
- var LOOPBACK_HOSTNAMES;
379
+ var DEFAULT_BIND_HOST = "127.0.0.1", LOOPBACK_HOSTNAMES;
380
380
  var init_net = __esm(() => {
381
381
  LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
382
382
  });
@@ -1120,6 +1120,9 @@ function readPlatformMap(dir) {
1120
1120
  return null;
1121
1121
  return { mapped: map(abs), located };
1122
1122
  }
1123
+ function platformName(platformDir) {
1124
+ return readPlatformMap(platformDir)?.mapped.name ?? basename(resolve2(platformDir));
1125
+ }
1123
1126
  function platformMode(platformDir) {
1124
1127
  return readPlatformMap(platformDir)?.mapped.mode ?? "single-repo";
1125
1128
  }
@@ -7935,7 +7938,7 @@ function errorResult(message) {
7935
7938
  return { content: [{ type: "text", text: message }], isError: true };
7936
7939
  }
7937
7940
  function buildMcpServer(platformDir) {
7938
- const server = new McpServer({ name: "spec", version: "0.0.6" });
7941
+ const server = new McpServer({ name: "spec", version: package_default.version });
7939
7942
  const fresh = (fn) => withIndex({ platformDir, build: "fresh" }, (h) => fn(h.storage));
7940
7943
  server.registerTool("spec_query", {
7941
7944
  title: "Full-text requirement retrieval",
@@ -8118,6 +8121,7 @@ function buildMcpServer(platformDir) {
8118
8121
  }
8119
8122
  var init_mcp = __esm(() => {
8120
8123
  init_src();
8124
+ init_package();
8121
8125
  init_domains();
8122
8126
  init__index();
8123
8127
  init_accept();
@@ -8159,7 +8163,7 @@ var init_mcp2 = __esm(() => {
8159
8163
  handleNotAPlatform(e);
8160
8164
  }
8161
8165
  const server = buildMcpServer(platformDir);
8162
- console.error(`spec mcp: serving ${platformDir} over stdio`);
8166
+ console.error(`spec mcp: serving ${platformName(platformDir)} over stdio`);
8163
8167
  await server.connect(new StdioServerTransport);
8164
8168
  }
8165
8169
  });
@@ -9248,6 +9252,18 @@ code {
9248
9252
  display: block;
9249
9253
  }
9250
9254
 
9255
+ .sidebar-platform {
9256
+ margin-left: auto;
9257
+ min-width: 0;
9258
+ overflow: hidden;
9259
+ text-overflow: ellipsis;
9260
+ white-space: nowrap;
9261
+ font-family: var(--font-mono);
9262
+ font-weight: 500;
9263
+ font-size: 0.75rem;
9264
+ color: var(--fg-dim);
9265
+ }
9266
+
9251
9267
  .sidebar-scroll {
9252
9268
  flex: 1;
9253
9269
  overflow-y: auto;
@@ -10854,9 +10870,9 @@ var init_coverage = __esm(() => {
10854
10870
  // ../webapp/src/pages/editor.ts
10855
10871
  import { bodyLimit } from "hono/body-limit";
10856
10872
  import { html as html5, raw as raw4 } from "hono/html";
10857
- function crossOriginRejected(c) {
10873
+ function crossOriginRejected(c, bindHost) {
10858
10874
  const selfUrl = new URL(c.req.url);
10859
- if (!isLoopbackHostname(selfUrl.hostname))
10875
+ if (!isServedHostname(selfUrl.hostname, bindHost))
10860
10876
  return true;
10861
10877
  const origin = c.req.header("origin");
10862
10878
  if (origin === undefined)
@@ -10914,7 +10930,7 @@ async function renderResult(res) {
10914
10930
  <p><a href="/editor">Back to editor</a></p>
10915
10931
  `);
10916
10932
  }
10917
- function mountEditor(app) {
10933
+ function mountEditor(app, bindHost = DEFAULT_BIND_HOST) {
10918
10934
  app.get("/editor", (c) => {
10919
10935
  if (!featureEnabled("editor"))
10920
10936
  return c.html(comingSoonDoc("editor", "Editor"));
@@ -10974,7 +10990,7 @@ function mountEditor(app) {
10974
10990
  app.post("/editor/create", editorBodyLimit, async (c) => {
10975
10991
  if (!featureEnabled("editor"))
10976
10992
  return c.html(rejectPage(featureDisabledMessage("editor")), 404);
10977
- if (crossOriginRejected(c))
10993
+ if (crossOriginRejected(c, bindHost))
10978
10994
  return c.html(rejectPage("cross-origin request rejected"), 403);
10979
10995
  const form = await c.req.parseBody();
10980
10996
  const statement = field(form, "statement");
@@ -10998,7 +11014,7 @@ function mountEditor(app) {
10998
11014
  app.post("/editor/amend", editorBodyLimit, async (c) => {
10999
11015
  if (!featureEnabled("editor"))
11000
11016
  return c.html(rejectPage(featureDisabledMessage("editor")), 404);
11001
- if (crossOriginRejected(c))
11017
+ if (crossOriginRejected(c, bindHost))
11002
11018
  return c.html(rejectPage("cross-origin request rejected"), 403);
11003
11019
  const form = await c.req.parseBody();
11004
11020
  const id = field(form, "id");
@@ -11752,12 +11768,27 @@ var init_setup = __esm(() => {
11752
11768
 
11753
11769
  // ../webapp/src/server.ts
11754
11770
  import { Hono } from "hono";
11771
+ import { html as htmlTemplate } from "hono/html";
11755
11772
  function createApp() {
11756
11773
  const app = new Hono;
11757
11774
  app.get("/", (c) => c.html(index_html_default));
11758
11775
  return app;
11759
11776
  }
11760
- function mountWebapp(app) {
11777
+ function labelPlatform(app, platformName2) {
11778
+ const name = htmlTemplate`${platformName2}`.toString();
11779
+ app.use("*", async (c, next) => {
11780
+ await next();
11781
+ if (!c.res.headers.get("content-type")?.startsWith("text/html"))
11782
+ return;
11783
+ const body = (await c.res.text()).replace("<title>Spec Engine", `<title>${name} \xB7 Spec Engine`).replace("<span>Spec Engine</span></a>", `<span>Spec Engine</span><span class="sidebar-platform">${name}</span></a>`);
11784
+ const headers = new Headers(c.res.headers);
11785
+ headers.delete("content-length");
11786
+ c.res = new Response(body, { status: c.res.status, headers });
11787
+ });
11788
+ }
11789
+ function mountWebapp(app, bindHost = DEFAULT_BIND_HOST, platformName2) {
11790
+ if (platformName2 !== undefined)
11791
+ labelPlatform(app, platformName2);
11761
11792
  registerErrorBoundary(app);
11762
11793
  mountCoverage(app);
11763
11794
  mountRequirements(app);
@@ -11765,13 +11796,14 @@ function mountWebapp(app) {
11765
11796
  mountQuery(app);
11766
11797
  mountRelations(app);
11767
11798
  mountProvenance(app);
11768
- mountEditor(app);
11799
+ mountEditor(app, bindHost);
11769
11800
  mountSetup(app);
11770
11801
  mountGlossary(app);
11771
11802
  mountLogs(app);
11772
11803
  return app;
11773
11804
  }
11774
11805
  var init_server = __esm(() => {
11806
+ init_src();
11775
11807
  init_index_html();
11776
11808
  init_coverage();
11777
11809
  init_data();
@@ -11821,9 +11853,9 @@ function withWriteLock(fn) {
11821
11853
  writeChain = run.catch(() => {});
11822
11854
  return run;
11823
11855
  }
11824
- function rejectCrossOrigin(c) {
11856
+ function rejectCrossOrigin(c, bindHost) {
11825
11857
  const selfUrl = new URL(c.req.url);
11826
- if (!isLoopbackHostname(selfUrl.hostname)) {
11858
+ if (!isServedHostname(selfUrl.hostname, bindHost)) {
11827
11859
  return c.json({ error: "cross-origin request rejected" }, 403);
11828
11860
  }
11829
11861
  const origin = c.req.header("origin");
@@ -11926,7 +11958,7 @@ function parseQueryLimit2(c, rawLimit) {
11926
11958
  }
11927
11959
  return { ok: true, limit };
11928
11960
  }
11929
- function mountApi(app, storage2, platformDir = process.cwd()) {
11961
+ function mountApi(app, storage2, platformDir = process.cwd(), bindHost = DEFAULT_BIND_HOST) {
11930
11962
  app.use("/api/*", async (c, next) => {
11931
11963
  c.header("Cache-Control", "no-store");
11932
11964
  await next();
@@ -11935,7 +11967,12 @@ function mountApi(app, storage2, platformDir = process.cwd()) {
11935
11967
  app.get("/api/repos", guarded((c) => c.json(storage2.listRepos())));
11936
11968
  app.get("/api/platform", async (c) => {
11937
11969
  const version = await derivePlatformVersion(platformDir);
11938
- const info = { version, source: "derived", mode: platformMode(platformDir) };
11970
+ const info = {
11971
+ version,
11972
+ name: platformName(platformDir),
11973
+ source: "derived",
11974
+ mode: platformMode(platformDir)
11975
+ };
11939
11976
  return c.json(info);
11940
11977
  });
11941
11978
  app.get("/api/report", guarded((c) => c.json(coverageReport(storage2).rows)));
@@ -11957,7 +11994,7 @@ function mountApi(app, storage2, platformDir = process.cwd()) {
11957
11994
  if (!featureEnabled("editor")) {
11958
11995
  return c.json({ error: featureDisabledMessage("editor") }, 404);
11959
11996
  }
11960
- const originReject = rejectCrossOrigin(c);
11997
+ const originReject = rejectCrossOrigin(c, bindHost);
11961
11998
  if (originReject)
11962
11999
  return originReject;
11963
12000
  const headerReject = rejectBadWriteHeaders(c);
@@ -11983,7 +12020,7 @@ function mountApi(app, storage2, platformDir = process.cwd()) {
11983
12020
  if (!featureEnabled("editor")) {
11984
12021
  return c.json({ error: featureDisabledMessage("editor") }, 404);
11985
12022
  }
11986
- const originReject = rejectCrossOrigin(c);
12023
+ const originReject = rejectCrossOrigin(c, bindHost);
11987
12024
  if (originReject)
11988
12025
  return originReject;
11989
12026
  const headerReject = rejectBadWriteHeaders(c);
@@ -12013,7 +12050,7 @@ function mountApi(app, storage2, platformDir = process.cwd()) {
12013
12050
  if (!featureEnabled("editor")) {
12014
12051
  return c.json({ error: featureDisabledMessage("editor") }, 404);
12015
12052
  }
12016
- const originReject = rejectCrossOrigin(c);
12053
+ const originReject = rejectCrossOrigin(c, bindHost);
12017
12054
  if (originReject)
12018
12055
  return originReject;
12019
12056
  const headerReject = rejectBadWriteHeaders(c);
@@ -12057,7 +12094,7 @@ function mountApi(app, storage2, platformDir = process.cwd()) {
12057
12094
  if (!featureEnabled("editor")) {
12058
12095
  return c.json({ error: featureDisabledMessage("editor") }, 404);
12059
12096
  }
12060
- const originReject = rejectCrossOrigin(c);
12097
+ const originReject = rejectCrossOrigin(c, bindHost);
12061
12098
  if (originReject)
12062
12099
  return originReject;
12063
12100
  const headerReject = rejectBadWriteHeaders(c);
@@ -12087,7 +12124,7 @@ function mountApi(app, storage2, platformDir = process.cwd()) {
12087
12124
  if (!featureEnabled("editor")) {
12088
12125
  return c.json({ error: featureDisabledMessage("editor") }, 404);
12089
12126
  }
12090
- const originReject = rejectCrossOrigin(c);
12127
+ const originReject = rejectCrossOrigin(c, bindHost);
12091
12128
  if (originReject)
12092
12129
  return originReject;
12093
12130
  const headerReject = rejectBadWriteHeaders(c);
@@ -12201,22 +12238,162 @@ var init_api = __esm(() => {
12201
12238
  writeChain = Promise.resolve();
12202
12239
  });
12203
12240
 
12241
+ // src/server/lan.ts
12242
+ import { createHash as createHash2, randomBytes, timingSafeEqual } from "crypto";
12243
+ import { isIP } from "net";
12244
+ function ipv4Prefix(ip) {
12245
+ const parts = ip.split(".");
12246
+ if (parts.length !== 4 || parts.some((p) => String(Number(p)) !== p))
12247
+ return null;
12248
+ return [Number(parts[0]), Number(parts[1])];
12249
+ }
12250
+ function isLoopbackAddress(ip) {
12251
+ if (ip === "::1")
12252
+ return true;
12253
+ return ipv4Prefix(ip)?.[0] === 127;
12254
+ }
12255
+ function isPrivateIPv4([a, b]) {
12256
+ if (a === 127 || a === 10)
12257
+ return true;
12258
+ if (a === 172 && b >= 16 && b <= 31)
12259
+ return true;
12260
+ if (a === 192 && b === 168)
12261
+ return true;
12262
+ return a === 100 && b >= 64 && b <= 127;
12263
+ }
12264
+ function bindHostRefusal(raw11) {
12265
+ const kind = isIP(raw11);
12266
+ if (kind === 0)
12267
+ return `--host must be a literal IP address, not "${raw11}"`;
12268
+ if (kind === 6) {
12269
+ return raw11 === "::1" ? null : `--host ${raw11}: the only IPv6 address accepted is ::1`;
12270
+ }
12271
+ const prefix = ipv4Prefix(raw11);
12272
+ if (prefix === null || !isPrivateIPv4(prefix)) {
12273
+ return `--host ${raw11} is not a loopback, RFC 1918, or 100.64.0.0/10 address`;
12274
+ }
12275
+ return null;
12276
+ }
12277
+ function urlHost(ip, port) {
12278
+ return `${isIP(ip) === 6 ? `[${ip}]` : ip}:${port}`;
12279
+ }
12280
+ function generateToken() {
12281
+ return randomBytes(32).toString("hex");
12282
+ }
12283
+ function pinnedTokenRefusal(token) {
12284
+ if (!/^[A-Za-z0-9_-]+$/.test(token))
12285
+ return "the access token may only contain A-Z a-z 0-9 _ -";
12286
+ if (token.length < MIN_PINNED_TOKEN_LENGTH) {
12287
+ return `the access token must be at least ${MIN_PINNED_TOKEN_LENGTH} characters`;
12288
+ }
12289
+ return null;
12290
+ }
12291
+ function digest(value) {
12292
+ return createHash2("sha256").update(value).digest();
12293
+ }
12294
+ function tokenMatches(candidate, token) {
12295
+ if (!candidate)
12296
+ return false;
12297
+ return timingSafeEqual(digest(candidate), digest(token));
12298
+ }
12299
+ function cookieValue(header, name) {
12300
+ for (const pair of (header ?? "").split(";")) {
12301
+ const eq = pair.indexOf("=");
12302
+ if (eq !== -1 && pair.slice(0, eq).trim() === name)
12303
+ return pair.slice(eq + 1).trim();
12304
+ }
12305
+ return null;
12306
+ }
12307
+ function isWrite(req, url) {
12308
+ if (req.method !== "GET" && req.method !== "HEAD")
12309
+ return true;
12310
+ if (url.pathname === "/provenance")
12311
+ return true;
12312
+ return url.pathname === "/api/provenance" && url.searchParams.has("resolve");
12313
+ }
12314
+ function plain(status, body, headers = {}) {
12315
+ return new Response(`${body}
12316
+ `, {
12317
+ status,
12318
+ headers: {
12319
+ "content-type": "text/plain; charset=utf-8",
12320
+ "cache-control": "no-store",
12321
+ ...headers
12322
+ }
12323
+ });
12324
+ }
12325
+ function lanGate(fetch2, { bindHost, token, allowWrites }) {
12326
+ return (req) => {
12327
+ const url = new URL(req.url);
12328
+ if (!isServedHostname(url.hostname, bindHost))
12329
+ return plain(403, "host rejected");
12330
+ const queryToken = url.searchParams.get("token");
12331
+ if (queryToken !== null) {
12332
+ if (!tokenMatches(queryToken, token))
12333
+ return plain(401, "invalid access token");
12334
+ url.searchParams.delete("token");
12335
+ return plain(302, "authenticated", {
12336
+ location: `${url.pathname}${url.search}`,
12337
+ "set-cookie": `${TOKEN_COOKIE}=${token}; Path=/; HttpOnly; SameSite=Strict`
12338
+ });
12339
+ }
12340
+ if (!tokenMatches(cookieValue(req.headers.get("cookie"), TOKEN_COOKIE), token)) {
12341
+ return plain(401, "access token required: open the tokenized URL spec serve printed");
12342
+ }
12343
+ if (!allowWrites && isWrite(req, url)) {
12344
+ return plain(403, "read-only: restart spec serve with --allow-writes to permit changes");
12345
+ }
12346
+ return fetch2(req);
12347
+ };
12348
+ }
12349
+ var TOKEN_COOKIE = "spec_token", MIN_PINNED_TOKEN_LENGTH = 16;
12350
+ var init_lan = __esm(() => {
12351
+ init_src();
12352
+ });
12353
+
12204
12354
  // src/commands/serve.ts
12205
12355
  var exports_serve = {};
12206
12356
  __export(exports_serve, {
12207
12357
  serveCommand: () => serveCommand,
12358
+ resolveBind: () => resolveBind,
12208
12359
  composeServeApp: () => composeServeApp
12209
12360
  });
12210
12361
  import { existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
12211
12362
  import { dirname as dirname8 } from "path";
12212
12363
  import { defineCommand as defineCommand24 } from "citty";
12213
12364
  import { Hono as Hono2 } from "hono";
12214
- function composeServeApp(storage2, platformDir = process.cwd()) {
12365
+ function composeServeApp(storage2, platformDir = process.cwd(), bindHost = DEFAULT_BIND_HOST) {
12215
12366
  const app = new Hono2;
12216
- mountApi(app, storage2, platformDir);
12217
- mountWebapp(app);
12367
+ mountApi(app, storage2, platformDir, bindHost);
12368
+ mountWebapp(app, bindHost, platformName(platformDir));
12218
12369
  return app;
12219
12370
  }
12371
+ function usageError(message) {
12372
+ console.error(`spec serve: ${message}`);
12373
+ process.exit(EXIT.USAGE);
12374
+ }
12375
+ function resolveBind(args) {
12376
+ const host = args.host ?? DEFAULT_BIND_HOST;
12377
+ const hostRefusal = bindHostRefusal(host);
12378
+ if (hostRefusal !== null)
12379
+ usageError(hostRefusal);
12380
+ if (isLoopbackAddress(host))
12381
+ return { host, lan: null };
12382
+ const pinned = args.token ?? process.env.SPEC_SERVE_TOKEN;
12383
+ if (pinned !== undefined) {
12384
+ const tokenRefusal = pinnedTokenRefusal(pinned);
12385
+ if (tokenRefusal !== null)
12386
+ usageError(tokenRefusal);
12387
+ }
12388
+ return {
12389
+ host,
12390
+ lan: {
12391
+ bindHost: host,
12392
+ token: pinned ?? generateToken(),
12393
+ allowWrites: args.allowWrites === true
12394
+ }
12395
+ };
12396
+ }
12220
12397
  async function runProbe() {
12221
12398
  const app = createApp();
12222
12399
  const server = Bun.serve({
@@ -12249,26 +12426,41 @@ async function runProbe() {
12249
12426
  }
12250
12427
  var serveCommand;
12251
12428
  var init_serve = __esm(() => {
12429
+ init_src();
12252
12430
  init_server();
12253
12431
  init_constants();
12254
12432
  init_discover();
12255
12433
  init_pipeline();
12256
12434
  init_prompt();
12257
12435
  init_api();
12436
+ init_lan();
12258
12437
  init_sqlite();
12259
12438
  init__args();
12260
12439
  init__shared();
12261
12440
  serveCommand = defineCommand24({
12262
12441
  meta: {
12263
12442
  name: "serve",
12264
- description: "Run the local webapp. Binds 127.0.0.1 on --port (loopback only \u2014 no --host). --probe is the SERV-04 asset-embedding smoke."
12443
+ description: "Run the local webapp. Binds 127.0.0.1 on --port, or one private --host address behind an access token. --probe is the SERV-04 asset-embedding smoke."
12265
12444
  },
12266
12445
  args: {
12267
12446
  platformDir: platformDirArg,
12268
12447
  port: {
12269
12448
  type: "string",
12270
12449
  default: "4000",
12271
- description: "Port (default 4000; 0 = ephemeral). Hostname is hardcoded 127.0.0.1 (T-1-01); no --host accepted."
12450
+ description: "Port (default 4000; 0 = ephemeral)."
12451
+ },
12452
+ host: {
12453
+ type: "string",
12454
+ default: DEFAULT_BIND_HOST,
12455
+ description: "Address to bind (default 127.0.0.1). Only a literal loopback, RFC 1918, or 100.64.0.0/10 IP; a non-loopback address requires the printed access token and is read-only."
12456
+ },
12457
+ token: {
12458
+ type: "string",
12459
+ description: "Pin the access token for a non-loopback --host (else SPEC_SERVE_TOKEN, else random). At least 16 of A-Z a-z 0-9 _ -."
12460
+ },
12461
+ allowWrites: {
12462
+ type: "boolean",
12463
+ description: "On a non-loopback --host, permit editor writes and tracker resolution."
12272
12464
  },
12273
12465
  out: {
12274
12466
  type: "string",
@@ -12276,7 +12468,7 @@ var init_serve = __esm(() => {
12276
12468
  },
12277
12469
  probe: {
12278
12470
  type: "boolean",
12279
- description: "Smoke test: bind ephemeral port on 127.0.0.1, GET /, assert body contains placeholder, exit."
12471
+ description: "Smoke test: bind ephemeral port on 127.0.0.1 (ignores --host), GET /, assert body contains placeholder, exit."
12280
12472
  },
12281
12473
  noPrompt: {
12282
12474
  type: "boolean",
@@ -12300,6 +12492,7 @@ var init_serve = __esm(() => {
12300
12492
  process.exit(EXIT.USAGE);
12301
12493
  return;
12302
12494
  }
12495
+ const { host, lan } = resolveBind(args);
12303
12496
  const platformDir = resolvePlatformDir(args);
12304
12497
  const outArg2 = args.out;
12305
12498
  const dbPath = resolveDbPath(platformDir, outArg2);
@@ -12330,20 +12523,26 @@ var init_serve = __esm(() => {
12330
12523
  if (needsIndex || storage2.listRepos().length === 0) {
12331
12524
  await runIndex({ platformDir, storage: storage2 });
12332
12525
  }
12333
- const app = composeServeApp(storage2, platformDir);
12526
+ const app = composeServeApp(storage2, platformDir, host);
12334
12527
  server = Bun.serve({
12335
12528
  port,
12336
- hostname: "127.0.0.1",
12337
- fetch: app.fetch
12529
+ hostname: host,
12530
+ fetch: lan === null ? app.fetch : lanGate(app.fetch, lan)
12338
12531
  });
12339
12532
  } catch (err) {
12340
12533
  storage2.close();
12341
12534
  handleStorageUnavailable(err, dbPath);
12342
- console.error(`spec serve: failed to start on 127.0.0.1:${port}:`, err);
12535
+ console.error(`spec serve: failed to start on ${urlHost(host, port)}:`, err);
12343
12536
  process.exit(EXIT.FAILURE);
12344
12537
  return;
12345
12538
  }
12346
- console.log(`spec: serving on http://127.0.0.1:${server.port}`);
12539
+ const serving = `spec: serving ${platformName(platformDir)} on ${server.url.origin}`;
12540
+ if (lan === null) {
12541
+ console.log(serving);
12542
+ return;
12543
+ }
12544
+ console.log(`${serving}/?token=${lan.token}`);
12545
+ console.log(lan.allowWrites ? "spec: writes ALLOWED (--allow-writes)" : "spec: read-only");
12347
12546
  }
12348
12547
  });
12349
12548
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spec-engine/spec-engine",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Cross-repo spec engine — durable requirements with permanent IDs, bound to code via @spec tags, checked in CI. Ships the spec CLI, the local coverage webapp (spec serve), and offline docs (spec docs).",
5
5
  "license": "MIT",
6
6
  "type": "module",