@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.
- package/dist/docs/architecture/index.html +1 -1
- package/dist/docs/commands/index.html +3 -3
- package/dist/docs/pagefind/fragment/en_dfaa776.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_edd950c.pf_fragment +0 -0
- package/dist/docs/pagefind/index/en_a63e8e2.pf_index +0 -0
- package/dist/docs/pagefind/pagefind-entry.json +1 -1
- package/dist/docs/pagefind/pagefind.en_2ed26d23fb.pf_meta +0 -0
- package/dist/impl.js +232 -33
- package/package.json +1 -1
- package/dist/docs/pagefind/fragment/en_34cad94.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_94271d5.pf_fragment +0 -0
- package/dist/docs/pagefind/index/en_9af49f2.pf_index +0 -0
- package/dist/docs/pagefind/pagefind.en_beb6d752c1.pf_meta +0 -0
|
@@ -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
|
|
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 <KEY-NNN> [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 <text> [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 <files...> [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 <reqId> [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 <repo> <reqId> [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 <name> / 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 <domain-prefix> [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 <reqId> [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 <reqId> <NEW-DOMAIN> [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 <reqId> [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 <reqId> [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 <KEY-NNN> [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 <text> [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 <files...> [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 <reqId> [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 <repo> <reqId> [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 <name> / 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 <domain-prefix> [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 <reqId> [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 <reqId> <NEW-DOMAIN> [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 <reqId> [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 <reqId> [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 <command> [args]</code>; from a checkout, as <code dir="auto">bun packages/engine/src/cli.ts <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 <KEY-NNN> [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 <text> [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 <files...> [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 <reqId> [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 <repo> <reqId> [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 <name> / 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 <domain-prefix> [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 <reqId> [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 <reqId> <NEW-DOMAIN> [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 <reqId> [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 <reqId> [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 <KEY-NNN> [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 <text> [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 <files...> [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 <reqId> [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 <repo> <reqId> [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 <name> / 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 <domain-prefix> [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 <reqId> [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 <reqId> <NEW-DOMAIN> [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 <reqId> [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 <reqId> [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 <command> [args]</code>; from a checkout, as <code dir="auto">bun packages/engine/src/cli.ts <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 <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 <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
|
|
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=&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>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.5.2","languages":{"en":{"hash":"
|
|
1
|
+
{"version":"1.5.2","languages":{"en":{"hash":"en_2ed26d23fb","wasm":"en","page_count":12}},"include_characters":["_","‿","⁀","⁔","︳","︴","﹍","﹎","﹏","_"]}
|
|
Binary file
|
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.
|
|
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
|
|
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:
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 = {
|
|
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
|
|
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).
|
|
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:
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|