@spec-engine/spec-engine 0.4.0 → 0.5.0

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.
@@ -77,7 +77,7 @@
77
77
  <li>Code binds with <code dir="auto">// @spec KEY-NNN</code>. A test tag may add <code dir="auto">unit</code> | <code dir="auto">integration</code> | <code dir="auto">e2e</code>.</li>
78
78
  <li>Tag kind is path-derived: implementation code implements, a test path verifies.</li>
79
79
  <li>The index at <code dir="auto">&#x3C;platformDir>/.spec-engine/index.sqlite</code> is derived and disposable.</li>
80
- <li>Coverage, drift, and propagation are SQL projections over tags. Drift compares a member’s pin (<code dir="auto">spec-engine@N</code>) against each referenced requirement’s <code dir="auto">changed_at_version</code>; the platform version is the max domain version.</li>
80
+ <li>Coverage, drift, and propagation are SQL projections over tags. Drift compares a member’s pin (<code dir="auto">spec-engine@N</code>) against each referenced requirement’s <code dir="auto">changed_at_version</code>; the platform version is the max domain version. Propagation rows come in dependency order, providers before consumers, from platform-map’s <code dir="auto">dependsOn</code>.</li>
81
81
  </ul>
82
82
  <p>The repo’s <code dir="auto">AGENTS.md</code> is the canonical copy of this page, with every command’s flags and <code dir="auto">--json</code> shape.</p>
83
83
  <div class="sl-heading-wrapper level-h2"><h2 id="invocation">Invocation</h2><a class="sl-anchor-link" href="#invocation"><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 “Invocation”</span></a></div>
@@ -182,7 +182,7 @@
182
182
  <p>Requirements tagged in the given files. Accepts multiple positionals, comma-split within a positional, and absolute paths under the platform tree. <code dir="auto">--req KEY-NNN</code> inverts the lookup: every tag site for that requirement across all repos.</p>
183
183
  <p><strong>Exit codes:</strong> 0 / 2</p>
184
184
  <div class="sl-heading-wrapper level-h3"><h3 id="spec-propagation-reqid-platformdir"><code dir="auto">spec propagation &#x3C;reqId> [platformDir]</code></h3><a class="sl-anchor-link" href="#spec-propagation-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 propagation &#x3C;reqId> [platformDir]”</span></a></div>
185
- <p>Classify each member repo’s relationship to a superseded requirement: <code dir="auto">MIGRATED_VERIFIED</code>, <code dir="auto">MIGRATED_UNVERIFIED</code>, <code dir="auto">ON_PREDECESSOR</code>, <code dir="auto">ON_OTHER_DOMAIN_REQ</code>, <code dir="auto">NO_DOMAIN_REFERENCE</code> — plus a <code dir="auto">drifted</code> flag per member.</p>
185
+ <p>Classify each member repo’s relationship to a superseded requirement: <code dir="auto">MIGRATED_VERIFIED</code>, <code dir="auto">MIGRATED_UNVERIFIED</code>, <code dir="auto">ON_PREDECESSOR</code>, <code dir="auto">ON_OTHER_DOMAIN_REQ</code>, <code dir="auto">NO_DOMAIN_REFERENCE</code> — plus a <code dir="auto">drifted</code> flag per member. Rows are in dependency order, providers before consumers, read from platform-map’s <code dir="auto">dependsOn</code>; ties by name. See <a href="/platform-mapping/">Platform Mapping</a>.</p>
186
186
  <p><strong>Exit codes:</strong> 0 / 2</p>
187
187
  <div class="sl-heading-wrapper level-h3"><h3 id="spec-gate-repo-reqid-platformdir"><code dir="auto">spec gate &#x3C;repo> &#x3C;reqId> [platformDir]</code></h3><a class="sl-anchor-link" href="#spec-gate-repo-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 gate &#x3C;repo> &#x3C;reqId> [platformDir]”</span></a></div>
188
188
  <p>Approval primitive. Passes iff <code dir="auto">&#x3C;reqId></code> is Active AND <code dir="auto">&#x3C;repo></code>’s pin covers the requirement’s <code dir="auto">changed_at_version</code>. <strong>Decision order:</strong> <code dir="auto">NOT_FOUND</code> → <code dir="auto">DRAFT</code> → <code dir="auto">SUPERSEDED</code> → <code dir="auto">VERSION_PIN</code> → <code dir="auto">PASS</code>; pin equality passes. An unknown repo is a usage error (exit 2), not a gate failure.</p>
@@ -1 +1 @@
1
- {"version":"1.5.2","languages":{"en":{"hash":"en_b7799d257e","wasm":"en","page_count":12}},"include_characters":["_","‿","⁀","⁔","︳","︴","﹍","﹎","﹏","_"]}
1
+ {"version":"1.5.2","languages":{"en":{"hash":"en_20f81448c5","wasm":"en","page_count":12}},"include_characters":["_","‿","⁀","⁔","︳","︴","﹍","﹎","﹏","_"]}
@@ -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="#two-files-one-convention" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Two files, one convention</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#the-platform-root" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">The platform root</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#members" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Members</span> </a> <ul class="isMobile astro-xz67lhkg" style="--depth: 1;"> <li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#two-ignore-lists" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">Two ignore lists</span> </a> </li> </ul> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#monorepo-members-and-their-packages" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Monorepo members and their packages</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#a-lone-repository" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">A lone repository</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#names" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Names</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#diagnostics" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Diagnostics</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#where-spec-init-may-write" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Where spec init may write</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#the-webapps-scan-mode-tile" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">The webapp’s scan-mode tile</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#determinism" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Determinism</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#declaring-a-platform" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Declaring a platform</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#a-future-input-dependson" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">A future input: dependsOn</span> </a> </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="#two-files-one-convention" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Two files, one convention</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#the-platform-root" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">The platform root</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#members" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Members</span> </a> <ul class="astro-xz67lhkg" style="--depth: 1;"> <li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#two-ignore-lists" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">Two ignore lists</span> </a> </li> </ul> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#monorepo-members-and-their-packages" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Monorepo members and their packages</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#a-lone-repository" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">A lone repository</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#names" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Names</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#diagnostics" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Diagnostics</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#where-spec-init-may-write" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Where spec init may write</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#the-webapps-scan-mode-tile" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">The webapp’s scan-mode tile</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#determinism" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Determinism</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#declaring-a-platform" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Declaring a platform</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#a-future-input-dependson" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">A future input: dependsOn</span> </a> </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">Platform Mapping</h1> </div> </div> <div class="content-panel astro-sfndtt72"> <div class="sl-container astro-sfndtt72"> <div class="sl-markdown-content"> <p>Spec Engine does not have its own idea of what a platform is. It takes membership and shape from <a href="https://www.npmjs.com/package/@spec-engine/platform-map"><code dir="auto">@spec-engine/platform-map</code></a>, the same package every tool built on Spec Engine uses, and adds exactly one fact of its own per member: the version pin. <!-- @spec INIT-037 --> platform-map is a dependency of the engine and is called as a library: <code dir="auto">spec init</code> declares members through it, <code dir="auto">spec check</code> runs its checks underneath, and no <code dir="auto">spec</code> workflow asks you to run a platform-map command yourself. This page states the model in platform-map’s vocabulary and names the requirement each rule carries.</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="#two-files-one-convention" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Two files, one convention</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#the-platform-root" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">The platform root</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#members" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Members</span> </a> <ul class="isMobile astro-xz67lhkg" style="--depth: 1;"> <li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#two-ignore-lists" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">Two ignore lists</span> </a> </li> </ul> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#monorepo-members-and-their-packages" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Monorepo members and their packages</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#a-lone-repository" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">A lone repository</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#names" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Names</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#diagnostics" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Diagnostics</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#where-spec-init-may-write" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Where spec init may write</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#the-webapps-scan-mode-tile" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">The webapp’s scan-mode tile</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#determinism" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Determinism</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#declaring-a-platform" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Declaring a platform</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#propagation-order-dependson" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Propagation order: dependsOn</span> </a> </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="#two-files-one-convention" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Two files, one convention</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#the-platform-root" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">The platform root</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#members" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Members</span> </a> <ul class="astro-xz67lhkg" style="--depth: 1;"> <li class="astro-xz67lhkg" style="--depth: 1;"> <a href="#two-ignore-lists" class="astro-xz67lhkg" style="--depth: 1;"> <span class="astro-xz67lhkg" style="--depth: 1;">Two ignore lists</span> </a> </li> </ul> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#monorepo-members-and-their-packages" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Monorepo members and their packages</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#a-lone-repository" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">A lone repository</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#names" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Names</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#diagnostics" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Diagnostics</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#where-spec-init-may-write" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Where spec init may write</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#the-webapps-scan-mode-tile" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">The webapp’s scan-mode tile</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#determinism" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Determinism</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#declaring-a-platform" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Declaring a platform</span> </a> </li><li class="astro-xz67lhkg" style="--depth: 0;"> <a href="#propagation-order-dependson" class="astro-xz67lhkg" style="--depth: 0;"> <span class="astro-xz67lhkg" style="--depth: 0;">Propagation order: dependsOn</span> </a> </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">Platform Mapping</h1> </div> </div> <div class="content-panel astro-sfndtt72"> <div class="sl-container astro-sfndtt72"> <div class="sl-markdown-content"> <p>Spec Engine does not have its own idea of what a platform is. It takes membership and shape from <a href="https://www.npmjs.com/package/@spec-engine/platform-map"><code dir="auto">@spec-engine/platform-map</code></a>, the same package every tool built on Spec Engine uses, and adds exactly one fact of its own per member: the version pin. <!-- @spec INIT-037 --> platform-map is a dependency of the engine and is called as a library: <code dir="auto">spec init</code> declares members through it, <code dir="auto">spec check</code> runs its checks underneath, and no <code dir="auto">spec</code> workflow asks you to run a platform-map command yourself. This page states the model in platform-map’s vocabulary and names the requirement each rule carries.</p>
74
74
  <div class="sl-heading-wrapper level-h2"><h2 id="two-files-one-convention">Two files, one convention</h2><a class="sl-anchor-link" href="#two-files-one-convention"><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 “Two files, one convention”</span></a></div>
75
75
  <p>platform-map works from two small committed files that share one name, <code dir="auto">platform-map.json</code>. Which one a file is depends on its keys.</p>
76
76
  <p><strong>The platform file</strong> lives at the platform root and declares the members:</p>
@@ -254,5 +254,8 @@
254
254
  <div class="sl-heading-wrapper level-h2"><h2 id="declaring-a-platform">Declaring a platform</h2><a class="sl-anchor-link" href="#declaring-a-platform"><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 “Declaring a platform”</span></a></div>
255
255
  <div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="bash"><code><div class="ec-line"><div class="code"><span style="--0:#C5E478;--1:#3B61B0">cd</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">~/clients/acme</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#919F9F;--1:#5F636F"># the folder holding the repos</span></div></div><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">spec</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">domain</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">new</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">BILLING</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#919F9F;--1:#5F636F"># the canonical tree makes it a Spec Engine platform</span></div></div><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">spec</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">init</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">api</span><span style="--0:#D6DEEB;--1:#403F53"> &#x26;&#x26; </span><span style="--0:#82AAFF;--1:#3B61B0">spec</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">init</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">webapp</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#919F9F;--1:#5F636F"># each: platform file entry, marker, pin</span></div></div><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">spec</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">index</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">.</span><span style="--0:#D6DEEB;--1:#403F53"> &#x26;&#x26; </span><span style="--0:#82AAFF;--1:#3B61B0">spec</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">check</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">.</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">--ci</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#919F9F;--1:#5F636F"># membership and pins are now one gate</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="cd ~/clients/acme # the folder holding the reposspec domain new BILLING # the canonical tree makes it a Spec Engine platformspec init api &#x26;&#x26; spec init webapp # each: platform file entry, marker, pinspec index . &#x26;&#x26; spec check . --ci # membership and pins are now one gate"><div></div></button></div></figure></div>
256
256
  <p><code dir="auto">spec check . --ci</code> runs platform-map’s own check underneath, so one gate fails when a requirement, a tag, a pin, or the platform declaration is broken.</p>
257
- <div class="sl-heading-wrapper level-h2"><h2 id="a-future-input-dependson">A future input: <code dir="auto">dependsOn</code></h2><a class="sl-anchor-link" href="#a-future-input-dependson"><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 “A future input: dependsOn”</span></a></div>
258
- <p>Every repo and package in the map carries <code dir="auto">dependsOn</code>, the platform’s own package names it depends on. Spec Engine does not read it yet. It is the natural ordering input for propagation across members and is recorded here so the migration leaves room for it.</p> </div> <footer class="sl-flex astro-5h22m7ac"> <div class="meta sl-flex astro-5h22m7ac"> <a href="https://github.com/spec-engine/spec-engine/edit/main/packages/site/src/content/docs/platform-mapping.md" class="sl-flex print:hidden astro-txs4wy42"><svg aria-hidden="true" class="astro-txs4wy42 astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.2em;"><path d="M22 7.24a1 1 0 0 0-.29-.71l-4.24-4.24a1 1 0 0 0-1.1-.22 1 1 0 0 0-.32.22l-2.83 2.83L2.29 16.05a1 1 0 0 0-.29.71V21a1 1 0 0 0 1 1h4.24a1 1 0 0 0 .76-.29l10.87-10.93L21.71 8c.1-.1.17-.2.22-.33a1 1 0 0 0 0-.24v-.14l.07-.05ZM6.83 20H4v-2.83l9.93-9.93 2.83 2.83L6.83 20ZM18.17 8.66l-2.83-2.83 1.42-1.41 2.82 2.82-1.41 1.42Z"/></svg>Edit page</a> </div> <div class="pagination-links print:hidden astro-7kn6nbmm" dir="ltr"> <a href="/architecture/" rel="prev" class="astro-7kn6nbmm"> <svg aria-hidden="true" class="astro-7kn6nbmm astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17 11H9.41l3.3-3.29a1.004 1.004 0 1 0-1.42-1.42l-5 5a1 1 0 0 0-.21.33 1 1 0 0 0 0 .76 1 1 0 0 0 .21.33l5 5a1.002 1.002 0 0 0 1.639-.325 1 1 0 0 0-.219-1.095L9.41 13H17a1 1 0 0 0 0-2Z"/></svg> <span class="astro-7kn6nbmm"> Previous <br class="astro-7kn6nbmm"> <span class="link-title astro-7kn6nbmm">Architecture</span> </span> </a> <a href="/commands/" rel="next" class="astro-7kn6nbmm"> <svg aria-hidden="true" class="astro-7kn6nbmm astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z"/></svg> <span class="astro-7kn6nbmm"> Next <br class="astro-7kn6nbmm"> <span class="link-title astro-7kn6nbmm">Commands</span> </span> </a> </div> </footer> </div> </div> </main> </div> </div> </div> </div> </body></html>
257
+ <div class="sl-heading-wrapper level-h2"><h2 id="propagation-order-dependson">Propagation order: <code dir="auto">dependsOn</code></h2><a class="sl-anchor-link" href="#propagation-order-dependson"><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 “Propagation order: dependsOn”</span></a></div>
258
+ <p>Every repo and package in the map carries <code dir="auto">dependsOn</code>, the platform’s own package names its manifest depends on, matched within one ecosystem. Spec Engine reads it as the order in which a superseded requirement propagates: a consumer can only move to the successor after the packages it depends on ship it. <code dir="auto">spec propagation</code>, <code dir="auto">/api/propagation/:id</code>, <code dir="auto">spec_propagation</code>, and the webapp’s propagation page list members in dependency order, a member after every member it depends on, and members at the same depth by name. <!-- @spec PROP-006 --></p>
259
+ <p>The depth is a coverage-column fact, computed at discovery and stored in the derived index. A single-repo member’s node is its map repo; a monorepo member’s package column, and a lone monorepo’s package column, is its map <code dir="auto">Package</code>. A column depends on another column when the other’s <code dir="auto">packageName</code> appears in its <code dir="auto">dependsOn</code>; a name that is no column (a monorepo root’s own package, an absent member) is no edge. A column with no dependency is depth 0; every other column is one deeper than its deepest dependency. A dependency cycle, and every column downstream of it, sits at one depth after every column outside it, by name.</p>
260
+ <div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="plaintext"><code><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">$ spec propagation BILLING-002 .</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">REPO STATE VIA DRIFT?</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">shared MIGRATED_VERIFIED — no</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">zed NO_DOMAIN_REFERENCE — no</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">api ON_PREDECESSOR BILLING-001 no</span></div></div><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">web MIGRATED_UNVERIFIED — no</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="$ spec propagation BILLING-002 .REPO STATE VIA DRIFT?shared MIGRATED_VERIFIED — nozed NO_DOMAIN_REFERENCE — noapi ON_PREDECESSOR BILLING-001 noweb MIGRATED_UNVERIFIED — no"><div></div></button></div></figure></div>
261
+ <p>Here <code dir="auto">api</code> depends on <code dir="auto">shared</code> and <code dir="auto">web</code> on <code dir="auto">api</code>, so <code dir="auto">api</code> is the member to migrate next. A platform with no in-platform dependencies lists members by name, as before.</p> </div> <footer class="sl-flex astro-5h22m7ac"> <div class="meta sl-flex astro-5h22m7ac"> <a href="https://github.com/spec-engine/spec-engine/edit/main/packages/site/src/content/docs/platform-mapping.md" class="sl-flex print:hidden astro-txs4wy42"><svg aria-hidden="true" class="astro-txs4wy42 astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.2em;"><path d="M22 7.24a1 1 0 0 0-.29-.71l-4.24-4.24a1 1 0 0 0-1.1-.22 1 1 0 0 0-.32.22l-2.83 2.83L2.29 16.05a1 1 0 0 0-.29.71V21a1 1 0 0 0 1 1h4.24a1 1 0 0 0 .76-.29l10.87-10.93L21.71 8c.1-.1.17-.2.22-.33a1 1 0 0 0 0-.24v-.14l.07-.05ZM6.83 20H4v-2.83l9.93-9.93 2.83 2.83L6.83 20ZM18.17 8.66l-2.83-2.83 1.42-1.41 2.82 2.82-1.41 1.42Z"/></svg>Edit page</a> </div> <div class="pagination-links print:hidden astro-7kn6nbmm" dir="ltr"> <a href="/architecture/" rel="prev" class="astro-7kn6nbmm"> <svg aria-hidden="true" class="astro-7kn6nbmm astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17 11H9.41l3.3-3.29a1.004 1.004 0 1 0-1.42-1.42l-5 5a1 1 0 0 0-.21.33 1 1 0 0 0 0 .76 1 1 0 0 0 .21.33l5 5a1.002 1.002 0 0 0 1.639-.325 1 1 0 0 0-.219-1.095L9.41 13H17a1 1 0 0 0 0-2Z"/></svg> <span class="astro-7kn6nbmm"> Previous <br class="astro-7kn6nbmm"> <span class="link-title astro-7kn6nbmm">Architecture</span> </span> </a> <a href="/commands/" rel="next" class="astro-7kn6nbmm"> <svg aria-hidden="true" class="astro-7kn6nbmm astro-lm5mbbcm" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z"/></svg> <span class="astro-7kn6nbmm"> Next <br class="astro-7kn6nbmm"> <span class="link-title astro-7kn6nbmm">Commands</span> </span> </a> </div> </footer> </div> </div> </main> </div> </div> </div> </div> </body></html>
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.4.0",
23
+ version: "0.5.0",
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",
@@ -432,19 +432,20 @@ function buildCoverageReport(rows) {
432
432
  }
433
433
 
434
434
  // ../shared/src/schema.ts
435
- var SCHEMA_VERSION = 10, FILES_MAX = 1000, DEFAULT_QUERY_LIMIT = 10, LIMIT_MAX = 1000, TABLES_DDL = `
435
+ var SCHEMA_VERSION = 11, FILES_MAX = 1000, DEFAULT_QUERY_LIMIT = 10, LIMIT_MAX = 1000, TABLES_DDL = `
436
436
  -- _schema_version: single-row table holding the integer schema_version on disk.
437
437
  -- Engine reads this on open; if it doesn't match SCHEMA_VERSION, the DB is wiped
438
- -- and rebuilt from scratch (D-12).
438
+ -- and rebuilt from scratch.
439
439
  CREATE TABLE IF NOT EXISTS _schema_version (
440
440
  version INTEGER NOT NULL
441
441
  );
442
442
 
443
- -- Per D-03 / SCHM-07: NO CHECK / FK / UNIQUE on domain fields. PRIMARY KEY only.
443
+ -- No CHECK, FK, or UNIQUE constraint on any domain field. PRIMARY KEY only.
444
444
  CREATE TABLE IF NOT EXISTS repos (
445
445
  name TEXT PRIMARY KEY,
446
446
  path TEXT NOT NULL,
447
- pinned_spec_version INTEGER NOT NULL
447
+ pinned_spec_version INTEGER NOT NULL,
448
+ dependency_depth INTEGER NOT NULL
448
449
  );
449
450
 
450
451
  CREATE TABLE IF NOT EXISTS domains (
@@ -483,12 +484,10 @@ CREATE INDEX IF NOT EXISTS idx_tags_by_req ON tags(req_id);
483
484
  CREATE INDEX IF NOT EXISTS idx_tags_by_repo ON tags(repo);
484
485
  CREATE INDEX IF NOT EXISTS idx_tags_by_file ON tags(file);
485
486
 
486
- -- relations (RED-16): one row per **Relates:** link in a SPEC.md entry.
487
- -- from_id carries the field; to_id is the linked requirement. Per
488
- -- Invariant #4 there is NO FK \u2014 a to_id pointing at a missing requirement
487
+ -- relations: one row per relates link. from_id carries the field; to_id is
488
+ -- the linked requirement. No FK: a to_id pointing at a missing requirement
489
489
  -- lands verbatim so spec check can surface BROKEN_RELATES; a to_id whose
490
- -- requirement was superseded surfaces RELATES_SUPERSEDED. source_file +
491
- -- line locate the authored Relates field line.
490
+ -- requirement was superseded surfaces RELATES_SUPERSEDED.
492
491
  CREATE TABLE IF NOT EXISTS relations (
493
492
  id INTEGER PRIMARY KEY AUTOINCREMENT,
494
493
  from_id TEXT NOT NULL,
@@ -499,11 +498,8 @@ CREATE TABLE IF NOT EXISTS relations (
499
498
  CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_id);
500
499
  CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_id);
501
500
 
502
- -- term_aliases (TERM-01, Phase 6): one row per glossary-term alias. Cloned 1:1
503
- -- from the relations table \u2014 id AUTOINCREMENT (excluded from build_id), no
504
- -- FK/CHECK/UNIQUE (SCHM-07 / Invariant #4, or the arch-fence trips). term_id is
505
- -- the owning TERM's id; name the synonym string. Present-and-empty until Wave C
506
- -- flattens the requirement aliases field into rows.
501
+ -- term_aliases: one row per glossary-term alias. id AUTOINCREMENT is excluded
502
+ -- from build_id. term_id is the owning TERM's id; name the synonym string.
507
503
  CREATE TABLE IF NOT EXISTS term_aliases (
508
504
  id INTEGER PRIMARY KEY AUTOINCREMENT,
509
505
  term_id TEXT NOT NULL,
@@ -512,15 +508,12 @@ CREATE TABLE IF NOT EXISTS term_aliases (
512
508
  CREATE INDEX IF NOT EXISTS idx_term_aliases_term ON term_aliases(term_id);
513
509
  CREATE INDEX IF NOT EXISTS idx_term_aliases_name ON term_aliases(name);
514
510
 
515
- -- term_citations (TERM-01, Phase 6): one row per pinned cites reference from a
516
- -- requirement to a glossary TERM. Cloned from the relations table: id
517
- -- AUTOINCREMENT (excluded from build_id), NO FK/CHECK/UNIQUE (SCHM-07 /
518
- -- Invariant #4) \u2014 a term_id pointing at a missing/superseded TERM lands verbatim
519
- -- so spec check can surface UNDEFINED_TERM / TERM_DRIFT (Wave D). req_id carries
520
- -- the citing requirement; term_id the cited TERM (nullable \u2014 an unresolved cite
521
- -- still lands); cited_as the authored surface form; pinned_version the term
522
- -- spec_version the citation was pinned to; source_file + line locate the cite.
523
- -- Present-and-empty until Wave C wires the flatten.
511
+ -- term_citations: one row per pinned cites reference from a requirement to a
512
+ -- glossary TERM. id AUTOINCREMENT is excluded from build_id. No FK: a term_id
513
+ -- pointing at a missing or superseded TERM lands verbatim so spec check can
514
+ -- surface UNDEFINED_TERM / TERM_DRIFT. term_id is nullable (an unresolved cite
515
+ -- still lands); cited_as is the authored surface form; pinned_version the term
516
+ -- spec_version the citation was pinned to.
524
517
  CREATE TABLE IF NOT EXISTS term_citations (
525
518
  id INTEGER PRIMARY KEY AUTOINCREMENT,
526
519
  req_id TEXT NOT NULL,
@@ -533,30 +526,25 @@ CREATE TABLE IF NOT EXISTS term_citations (
533
526
  CREATE INDEX IF NOT EXISTS idx_term_citations_req ON term_citations(req_id);
534
527
  CREATE INDEX IF NOT EXISTS idx_term_citations_term ON term_citations(term_id);
535
528
 
536
- -- provenance (PROV-02 / PROV-04): one row per **Issues:** role:ID link in a
537
- -- SPEC.md entry. req_id carries the field; issue_id is the OPAQUE external
538
- -- tracker payload; role is a closed-allow-list label (created | supersedes-via
539
- -- | amends-via), validated at PARSE time, never as a DB constraint (SCHM-07 /
540
- -- Invariant #4). source_file + line locate the authored **Issues:** field line.
541
- -- Exactly ONE index, on req_id (SC3) \u2014 never on issue_id, which would read as
542
- -- treating the opaque payload as a lookup key (PROV-02, grep-fenced in Plan 04).
529
+ -- provenance: one row per issues role:ID link. issue_id is the opaque
530
+ -- external tracker payload; role is a closed-allow-list label (created |
531
+ -- supersedes-via | amends-via), validated at parse time, never as a DB
532
+ -- constraint. Exactly one index, on req_id; never on issue_id, which would
533
+ -- treat the opaque payload as a lookup key.
543
534
  -- @spec PROV-004
544
535
  CREATE TABLE IF NOT EXISTS provenance (
545
536
  id INTEGER PRIMARY KEY AUTOINCREMENT,
546
537
  req_id TEXT NOT NULL,
547
- issue_id TEXT NOT NULL, -- OPAQUE payload string \u2014 never PK/FK/UNIQUE/JOIN key (PROV-02/SC3)
538
+ issue_id TEXT NOT NULL, -- opaque payload string, never a PK/FK/UNIQUE/JOIN key
548
539
  role TEXT NOT NULL, -- created | supersedes-via | amends-via
549
540
  source_file TEXT NOT NULL,
550
541
  line INTEGER NOT NULL
551
542
  );
552
- CREATE INDEX IF NOT EXISTS idx_provenance_by_req ON provenance(req_id); -- ONLY index, on req_id (SC3)
553
-
554
- -- parse_diagnostics: structural diagnostics carried forward from Phase 2's
555
- -- validate-on-parse pass (DUP_ID, BROKEN_SUPERSEDE, BAD_STATUS). Stored so
556
- -- spec check can render them with file+line; not used by Phase 1's smoke.
557
- -- req_id is nullable per WR-02 (03-REVIEW): the parser knows the implicated
558
- -- id for BROKEN_SUPERSEDE and BAD_STATUS, and for the SECOND DUP_ID
559
- -- occurrence. NULL when no specific id is implicated.
543
+ CREATE INDEX IF NOT EXISTS idx_provenance_by_req ON provenance(req_id); -- the only index
544
+
545
+ -- parse_diagnostics: structural diagnostics from the validate-on-parse pass
546
+ -- (DUP_ID, BROKEN_SUPERSEDE, BAD_STATUS), stored so spec check can render them
547
+ -- with file+line. req_id is NULL when no specific id is implicated.
560
548
  CREATE TABLE IF NOT EXISTS parse_diagnostics (
561
549
  id INTEGER PRIMARY KEY AUTOINCREMENT,
562
550
  code TEXT NOT NULL,
@@ -591,13 +579,9 @@ FROM requirements r
591
579
  CROSS JOIN repos
592
580
  LEFT JOIN tags t
593
581
  ON t.req_id = r.id AND t.repo = repos.name
594
- -- TERM-01 (Phase 6): a glossary TERM is a requirement row but NEVER a
595
- -- code-coverage obligation \u2014 it carries no @spec tag. Excluding key='TERM' here
596
- -- drops terms from spec map / spec report (and provenance_matrix, which joins
597
- -- coverage) in ONE edit, so a migrated term never renders an empty coverage
598
- -- cell. The Q4 ORPHAN_REQ / Q5 UNVERIFIED_REQ queries (sqlite.ts) carry the
599
- -- SAME literal exclusion so a term never fires a coverage ERROR. The match is
600
- -- the exact literal 'TERM' \u2014 never a real domain key.
582
+ -- A glossary TERM is a requirement row but never a code-coverage obligation.
583
+ -- The ORPHAN_REQ / UNVERIFIED_REQ queries in sqlite.ts carry the same literal
584
+ -- exclusion. The match is the exact literal 'TERM', never a real domain key.
601
585
  -- @spec SCHM-017
602
586
  WHERE r.key != 'TERM'
603
587
  GROUP BY r.id, repos.name;
@@ -616,15 +600,9 @@ JOIN requirements r ON r.id = t.req_id
616
600
  JOIN repos ON repos.name = t.repo
617
601
  WHERE r.changed_at_version > repos.pinned_spec_version;
618
602
 
619
- -- term_drift (TERM-01 / Phase 6): the citation-drift VIEW, a 1:1 shape-clone of
620
- -- the member-pin drift VIEW but keyed on the CITATION's pin instead of a repo's.
621
- -- One row per cites reference whose pinned_version is behind the cited TERM's
622
- -- changed_at_version \u2014 i.e. the term's definition moved on since the citation
623
- -- was pinned. INNER JOIN (not LEFT): an unresolved term_id (no matching
624
- -- requirement) is an UNDEFINED_TERM concern, NOT drift \u2014 scoped distinctly by
625
- -- the Wave-D diagnostics. term.key='TERM' keeps the VIEW scoped to the reserved
626
- -- glossary domain. Present-and-empty until Wave C populates term_citations; the
627
- -- diagnostic (TERM_DRIFT) reads it in Wave E.
603
+ -- term_drift: one row per cites reference whose pinned_version is behind the
604
+ -- cited TERM's changed_at_version. INNER JOIN: an unresolved term_id is an
605
+ -- UNDEFINED_TERM concern, not drift.
628
606
  -- @spec CHCK-018
629
607
  CREATE VIEW IF NOT EXISTS term_drift AS
630
608
  SELECT
@@ -638,38 +616,21 @@ FROM term_citations tc
638
616
  JOIN requirements term ON term.id = tc.term_id
639
617
  WHERE term.key = 'TERM' AND term.changed_at_version > tc.pinned_version;
640
618
 
641
- -- provenance_matrix (PMAT-04 / data half of PMAT-01): a WIDENED projection
642
- -- over provenance \xD7 requirements \xD7 coverage, never materialized (mirrors the
643
- -- coverage VIEW pattern). The widening joins are ON req_id ONLY \u2014 NEVER on
644
- -- issue_id, which is OPAQUE (PROV-02/SC3). JOIN requirements r ON p.req_id =
645
- -- r.id carries req_status (the requirement's lifecycle status); LEFT JOIN
646
- -- coverage c ON c.req_id = p.req_id carries the backing-test columns. Because
647
- -- coverage is a per-(req,repo) matrix, the join fans out per repo \u2014 so we
648
- -- aggregate to ONE row per provenance link.
649
- --
650
- -- WR-01 (13-REVIEW review-fix): test_levels is re-aggregated at the
651
- -- INDIVIDUAL-level granularity via a correlated subquery over tags, NOT by
652
- -- GROUP_CONCAT(DISTINCT c.test_levels). The coverage VIEW already concatenates
653
- -- levels per (req,repo) into a single string (api -> "unit,integration",
654
- -- mobile -> "unit"); GROUP_CONCAT(DISTINCT ...) over those whole strings
655
- -- deduplicates STRINGS, not levels, so a level present in two repos emitted
656
- -- twice ("unit,integration,unit"). The subquery selects DISTINCT t2.level for
657
- -- this req across ALL repos (ORDER BY level for determinism), so each level
658
- -- appears exactly once. The correlation is ON req_id ONLY (t2.req_id =
659
- -- p.req_id) \u2014 the issue_id-opacity fence stays green (no issue_id key usage).
660
- -- CRITICAL FENCE CONSTRAINT
661
- -- (PROV-02/SC3): the CI issue_id-opacity fence forbids grouping by issue_id,
662
- -- so we GROUP BY the provenance PK p.id (which uniquely determines the link)
663
- -- and let issue_id ride as a bare projected column under SQLite's
664
- -- bare-column rule. issue_id is therefore PURELY projected \u2014 never a
665
- -- JOIN/GROUP-BY/PK/FK/UNIQUE/INDEX key.
619
+ -- provenance_matrix: provenance \xD7 requirements \xD7 coverage, never
620
+ -- materialized. Every join is on req_id, never on issue_id. coverage is a
621
+ -- per-(req, repo) matrix, so the join fans out per repo and is aggregated to
622
+ -- one row per provenance link, grouped by the provenance PK p.id; issue_id
623
+ -- rides as a bare projected column and is never a JOIN, GROUP BY, PK, FK,
624
+ -- UNIQUE, or INDEX key. test_levels is aggregated by a correlated subquery
625
+ -- over tags (DISTINCT level, ORDER BY level) so a level present in two repos
626
+ -- appears once.
666
627
  CREATE VIEW IF NOT EXISTS provenance_matrix AS
667
628
  SELECT
668
629
  p.req_id AS req_id,
669
630
  p.role AS role,
670
- p.issue_id AS issue_id, -- OPAQUE payload, projected only (PROV-02/SC3)
631
+ p.issue_id AS issue_id, -- opaque payload, projected only
671
632
  p.source_file AS source_file,
672
- p.line AS line, -- git pointer per PMAT-01
633
+ p.line AS line,
673
634
  r.status AS req_status,
674
635
  MAX(c.implemented) AS implemented,
675
636
  MAX(c.verified) AS verified,
@@ -1204,19 +1165,86 @@ function packageColumnName(root, dir) {
1204
1165
  function byName(a, b) {
1205
1166
  return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
1206
1167
  }
1207
- function column(name, path, pin, ignore) {
1168
+ function column(name, path, pin, node, ignore) {
1208
1169
  return {
1209
- name,
1210
- path,
1211
- pinned_spec_version: pin,
1212
- ...ignore && ignore.length > 0 ? { ignore } : {}
1170
+ repo: {
1171
+ name,
1172
+ path,
1173
+ pinned_spec_version: pin,
1174
+ ...ignore && ignore.length > 0 ? { ignore } : {}
1175
+ },
1176
+ node
1213
1177
  };
1214
1178
  }
1179
+ function packageKey(ecosystem, packageName) {
1180
+ return `${ecosystem ?? ""}\x00${packageName}`;
1181
+ }
1182
+ function providersOf(columns) {
1183
+ const providers = new Map;
1184
+ for (const c of columns) {
1185
+ if (c.node.packageName === undefined)
1186
+ continue;
1187
+ const key = packageKey(c.node.ecosystem, c.node.packageName);
1188
+ if (!providers.has(key))
1189
+ providers.set(key, c.repo.name);
1190
+ }
1191
+ return providers;
1192
+ }
1193
+ function dependenciesOf(c, providers) {
1194
+ const deps = new Set;
1195
+ for (const name of c.node.dependsOn) {
1196
+ const provider = providers.get(packageKey(c.node.ecosystem, name));
1197
+ if (provider !== undefined && provider !== c.repo.name)
1198
+ deps.add(provider);
1199
+ }
1200
+ return deps;
1201
+ }
1202
+ function dependencyGraph(columns) {
1203
+ const providers = providersOf(columns);
1204
+ const dependents = new Map;
1205
+ const waitingOn = new Map;
1206
+ for (const c of columns) {
1207
+ const deps = dependenciesOf(c, providers);
1208
+ waitingOn.set(c.repo.name, deps.size);
1209
+ for (const provider of deps) {
1210
+ dependents.set(provider, [...dependents.get(provider) ?? [], c.repo.name]);
1211
+ }
1212
+ }
1213
+ return { names: columns.map((c) => c.repo.name).sort(), dependents, waitingOn };
1214
+ }
1215
+ function dependencyDepths(columns) {
1216
+ const { names, dependents, waitingOn } = dependencyGraph(columns);
1217
+ const depth = new Map;
1218
+ const ready = names.filter((n) => waitingOn.get(n) === 0);
1219
+ for (const n of ready)
1220
+ depth.set(n, 0);
1221
+ for (let i = 0;i < ready.length; i++) {
1222
+ const provider = ready[i];
1223
+ const providerDepth = depth.get(provider) ?? 0;
1224
+ for (const consumer of dependents.get(provider) ?? []) {
1225
+ depth.set(consumer, Math.max(depth.get(consumer) ?? 0, providerDepth + 1));
1226
+ const left = (waitingOn.get(consumer) ?? 0) - 1;
1227
+ waitingOn.set(consumer, left);
1228
+ if (left === 0)
1229
+ ready.push(consumer);
1230
+ }
1231
+ }
1232
+ const cyclic = Math.max(-1, ...depth.values()) + 1;
1233
+ for (const n of names)
1234
+ if (!depth.has(n))
1235
+ depth.set(n, cyclic);
1236
+ return depth;
1237
+ }
1238
+ function toRepos(columns) {
1239
+ const depth = dependencyDepths(columns);
1240
+ return columns.map((c) => ({ ...c.repo, dependency_depth: depth.get(c.repo.name) ?? 0 })).sort(byName);
1241
+ }
1215
1242
  function selfMember(absPlatform, platformVersion) {
1216
1243
  return {
1217
1244
  name: basename(absPlatform),
1218
1245
  path: absPlatform,
1219
1246
  pinned_spec_version: platformVersion,
1247
+ dependency_depth: 0,
1220
1248
  selfMember: true
1221
1249
  };
1222
1250
  }
@@ -1238,11 +1266,11 @@ async function packageColumns(repoPath, prefix, repo, parentPin) {
1238
1266
  const path = join3(repoPath, pkg.path);
1239
1267
  const configPath = join3(path, MEMBER_CONFIG_FILENAME);
1240
1268
  if (!existsSync(configPath)) {
1241
- out.push(column(name, path, parentPin));
1269
+ out.push(column(name, path, parentPin, pkg));
1242
1270
  continue;
1243
1271
  }
1244
1272
  const cfg = await readRepoConfig(configPath);
1245
- out.push(column(name, path, extractPin(cfg.specs), cfg.ignore));
1273
+ out.push(column(name, path, extractPin(cfg.specs), pkg, cfg.ignore));
1246
1274
  }
1247
1275
  return out;
1248
1276
  }
@@ -1279,14 +1307,14 @@ async function membersOf(reading, absPlatform, platformVersion) {
1279
1307
  const diagnostics2 = surfaced(mapped);
1280
1308
  if (mapped.mode !== "multi-repo") {
1281
1309
  const repo = mapped.repos[0];
1282
- const members2 = repo !== undefined && repo.mode === "monorepo" && repo.packages.length > 0 ? await packageColumns(absPlatform, "", repo, platformVersion) : [selfMember(absPlatform, platformVersion)];
1283
- return { mode: mapped.mode, members: members2, unpinned: [], undeclared: [], diagnostics: diagnostics2 };
1310
+ const members = repo !== undefined && repo.mode === "monorepo" && repo.packages.length > 0 ? toRepos(await packageColumns(absPlatform, "", repo, platformVersion)) : [selfMember(absPlatform, platformVersion)];
1311
+ return { mode: mapped.mode, members, unpinned: [], undeclared: [], diagnostics: diagnostics2 };
1284
1312
  }
1285
1313
  if (!mapped.declared) {
1286
1314
  const undeclared2 = mapped.repos.map((r) => ({ name: r.name, path: join3(absPlatform, r.name) }));
1287
1315
  return { mode: "multi-repo", members: [], unpinned: [], undeclared: undeclared2, diagnostics: diagnostics2 };
1288
1316
  }
1289
- const members = [];
1317
+ const columns = [];
1290
1318
  const unpinned = [];
1291
1319
  for (const repo of mapped.repos) {
1292
1320
  const path = located.repos[repo.name];
@@ -1300,14 +1328,13 @@ async function membersOf(reading, absPlatform, platformVersion) {
1300
1328
  const cfg = await readRepoConfig(configPath);
1301
1329
  const pin = extractPin(cfg.specs);
1302
1330
  if (repo.mode === "monorepo" && repo.packages.length > 0) {
1303
- members.push(...await packageColumns(path, `${repo.name}/`, repo, pin));
1331
+ columns.push(...await packageColumns(path, `${repo.name}/`, repo, pin));
1304
1332
  } else {
1305
- members.push(column(repo.name, path, pin, cfg.ignore));
1333
+ columns.push(column(repo.name, path, pin, repo, cfg.ignore));
1306
1334
  }
1307
1335
  }
1308
- members.sort(byName);
1309
1336
  const undeclared = mapped.diagnostics.filter((d) => d.code === "UNLISTED_REPO").map((d) => ({ name: d.subject, path: join3(absPlatform, d.subject) }));
1310
- return { mode: "multi-repo", members, unpinned, undeclared, diagnostics: diagnostics2 };
1337
+ return { mode: "multi-repo", members: toRepos(columns), unpinned, undeclared, diagnostics: diagnostics2 };
1311
1338
  }
1312
1339
  async function discoverRepos(platformDir) {
1313
1340
  const absPlatform = resolve2(platformDir);
@@ -1320,7 +1347,8 @@ async function discoverRepos(platformDir) {
1320
1347
  const canonical = {
1321
1348
  name: CANONICAL_SPECS_DIR,
1322
1349
  path: canonicalPath,
1323
- pinned_spec_version: platformVersion
1350
+ pinned_spec_version: platformVersion,
1351
+ dependency_depth: 0
1324
1352
  };
1325
1353
  const reading = readPlatformMap(absPlatform);
1326
1354
  const shape = reading === null ? loneSingleRepo(absPlatform, platformVersion) : await membersOf(reading, absPlatform, platformVersion);
@@ -1706,11 +1734,11 @@ SELECT
1706
1734
  ) AS via_other
1707
1735
  FROM repos
1708
1736
  WHERE repos.name != 'spec-engine'
1709
- ORDER BY repos.name
1737
+ ORDER BY repos.dependency_depth, repos.name
1710
1738
  `;
1711
1739
 
1712
1740
  // src/storage/sql/reads.ts
1713
- var LIST_RELATIONS_SQL, LIST_TERM_ALIASES_SQL = "SELECT term_id, name FROM term_aliases ORDER BY term_id, name", LIST_TERM_CITATIONS_SQL, LIST_PROVENANCE_SQL, LIST_PROVENANCE_MATRIX_SQL, PROVENANCE_BY_ISSUE_SQL, LIST_REPOS_SQL = "SELECT name, path, pinned_spec_version FROM repos ORDER BY name", GET_REPO_SQL = "SELECT name, path, pinned_spec_version FROM repos WHERE name = $name", LIST_DOMAINS_SQL = "SELECT key, owner, schema, spec_version, source_repo FROM domains ORDER BY key", GET_DOMAIN_SQL = "SELECT key, owner, schema, spec_version, source_repo FROM domains WHERE key = $key", LIST_TAGS_SQL = `SELECT id, req_id, repo, file, line, kind, level FROM tags
1741
+ var LIST_RELATIONS_SQL, LIST_TERM_ALIASES_SQL = "SELECT term_id, name FROM term_aliases ORDER BY term_id, name", LIST_TERM_CITATIONS_SQL, LIST_PROVENANCE_SQL, LIST_PROVENANCE_MATRIX_SQL, PROVENANCE_BY_ISSUE_SQL, LIST_REPOS_SQL = "SELECT name, path, pinned_spec_version, dependency_depth FROM repos ORDER BY name", GET_REPO_SQL = "SELECT name, path, pinned_spec_version, dependency_depth FROM repos WHERE name = $name", LIST_DOMAINS_SQL = "SELECT key, owner, schema, spec_version, source_repo FROM domains ORDER BY key", GET_DOMAIN_SQL = "SELECT key, owner, schema, spec_version, source_repo FROM domains WHERE key = $key", LIST_TAGS_SQL = `SELECT id, req_id, repo, file, line, kind, level FROM tags
1714
1742
  WHERE ($repo IS NULL OR repo = $repo)
1715
1743
  AND ($req_id IS NULL OR req_id = $req_id)
1716
1744
  AND ($file IS NULL OR file = $file)
@@ -1983,7 +2011,7 @@ class SqliteStorage {
1983
2011
  }
1984
2012
  #makeWriteHandle() {
1985
2013
  const db = this.#db;
1986
- const insRepo = db.prepare("INSERT OR REPLACE INTO repos (name, path, pinned_spec_version) VALUES ($name, $path, $pin)");
2014
+ const insRepo = db.prepare("INSERT OR REPLACE INTO repos (name, path, pinned_spec_version, dependency_depth) VALUES ($name, $path, $pin, $depth)");
1987
2015
  const insDomain = db.prepare("INSERT OR REPLACE INTO domains (key, owner, schema, spec_version, source_repo) " + "VALUES ($key, $owner, $schema, $ver, $source)");
1988
2016
  const insReq = db.prepare("INSERT OR REPLACE INTO requirements " + "(id, key, seq, status, superseded_by, text, why, source_file, line, spec_version, changed_at_version, superseded_at_version) " + "VALUES ($id, $key, $seq, $status, $sup, $text, $why, $file, $line, $sver, $cver, $saver)");
1989
2017
  const insTag = db.prepare("INSERT INTO tags (req_id, repo, file, line, kind, level) " + "VALUES ($req, $repo, $file, $line, $kind, $level)");
@@ -2013,7 +2041,8 @@ class SqliteStorage {
2013
2041
  insRepo.run({
2014
2042
  name: r.name,
2015
2043
  path: r.path,
2016
- pin: r.pinned_spec_version
2044
+ pin: r.pinned_spec_version,
2045
+ depth: r.dependency_depth
2017
2046
  });
2018
2047
  },
2019
2048
  upsertDomain(d) {
@@ -2136,11 +2165,7 @@ function poisonSchemaVersion(path, badVersion) {
2136
2165
  function poisonRepoRow(path, name) {
2137
2166
  const db = new Database(path);
2138
2167
  try {
2139
- db.run("INSERT INTO repos (name, path, pinned_spec_version) VALUES (?, ?, ?)", [
2140
- name,
2141
- "/dev/null",
2142
- 999
2143
- ]);
2168
+ db.run("INSERT INTO repos (name, path, pinned_spec_version, dependency_depth) VALUES (?, ?, ?, ?)", [name, "/dev/null", 999, 0]);
2144
2169
  } finally {
2145
2170
  db.close();
2146
2171
  }
@@ -2165,7 +2190,7 @@ function computeBuildId(storage2) {
2165
2190
  },
2166
2191
  {
2167
2192
  label: "repos",
2168
- sql: "SELECT name, path, pinned_spec_version FROM repos ORDER BY name"
2193
+ sql: "SELECT name, path, pinned_spec_version, dependency_depth FROM repos ORDER BY name"
2169
2194
  },
2170
2195
  {
2171
2196
  label: "domains",
@@ -2511,7 +2536,8 @@ function buildRows(input) {
2511
2536
  {
2512
2537
  name: canonical.name,
2513
2538
  path: canonical.path,
2514
- pinned_spec_version: platformVersion
2539
+ pinned_spec_version: platformVersion,
2540
+ dependency_depth: 0
2515
2541
  },
2516
2542
  ...members
2517
2543
  ], (r) => r.name);
@@ -5654,19 +5680,15 @@ var init_list = __esm(() => {
5654
5680
  });
5655
5681
 
5656
5682
  // src/propagation/format.ts
5657
- function sortPropagation(rows) {
5658
- return [...rows].sort((a, b) => a.repo.localeCompare(b.repo));
5659
- }
5660
5683
  function renderPropagation(rows, mode) {
5661
- const sorted = sortPropagation(rows);
5662
5684
  if (mode === "json") {
5663
- return JSON.stringify(sorted);
5685
+ return JSON.stringify(rows);
5664
5686
  }
5665
- if (sorted.length === 0)
5687
+ if (rows.length === 0)
5666
5688
  return "";
5667
5689
  const header = ["REPO", "STATE", "VIA", "DRIFT?"];
5668
5690
  const lines = [header];
5669
- for (const row of sorted) {
5691
+ for (const row of rows) {
5670
5692
  lines.push([row.repo, row.state, row.via_req_id ?? EMPTY_CELL2, row.drifted ? "yes" : "no"]);
5671
5693
  }
5672
5694
  const widths = header.map((_, col) => Math.max(...lines.map((row) => row[col]?.length ?? 0)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spec-engine/spec-engine",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
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",