@spec-engine/spec-engine 0.2.0 → 0.3.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.
- package/dist/docs/checks/index.html +1 -1
- package/dist/docs/commands/index.html +3 -3
- package/dist/docs/pagefind/fragment/en_10bec64.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_adb4528.pf_fragment +0 -0
- package/dist/docs/pagefind/index/en_90d86c1.pf_index +0 -0
- package/dist/docs/pagefind/pagefind-entry.json +1 -1
- package/dist/docs/pagefind/pagefind.en_4cf42761c7.pf_meta +0 -0
- package/dist/impl.js +352 -213
- package/package.json +1 -1
- package/dist/docs/pagefind/fragment/en_523ebff.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_649983a.pf_fragment +0 -0
- package/dist/docs/pagefind/index/en_d1196c8.pf_index +0 -0
- package/dist/docs/pagefind/pagefind.en_f3d65f37ce.pf_meta +0 -0
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
<table><thead><tr><th>Code</th><th>Severity</th><th>Condition</th></tr></thead><tbody><tr><td><code dir="auto">INVALID_DOMAIN_FILE</code></td><td>error</td><td>A <code dir="auto">SPEC.json</code> fails the structural schema (bad JSON, malformed <code dir="auto">key</code>/<code dir="auto">id</code>, missing <code dir="auto">statement</code>, unknown envelope key) — loud, never a silent zero</td></tr><tr><td><code dir="auto">DUP_ID</code></td><td>error</td><td>The same requirement ID appears twice</td></tr><tr><td><code dir="auto">BROKEN_SUPERSEDE</code></td><td>error</td><td><code dir="auto">supersededBy</code> points at a missing ID</td></tr><tr><td><code dir="auto">CYCLIC_SUPERSEDE</code></td><td>error</td><td>A circular supersession chain (<code dir="auto">A → B → A</code>) — the chain is the change history, so a cycle is corrupt history</td></tr><tr><td><code dir="auto">BAD_STATUS</code></td><td>error</td><td>Entry status outside <code dir="auto">active</code> / <code dir="auto">draft</code> / <code dir="auto">superseded</code> / <code dir="auto">deprecated</code></td></tr><tr><td><code dir="auto">DANGLING_TAG</code></td><td>error</td><td>A <code dir="auto">@spec</code> tag references no requirement</td></tr><tr><td><code dir="auto">SUPERSEDED_REFERENCED</code></td><td>error</td><td>Code still tags a superseded requirement (retag to the successor)</td></tr><tr><td><code dir="auto">ORPHAN_REQ</code></td><td>error</td><td>An active requirement has no implementing <code dir="auto">@spec</code> tag</td></tr><tr><td><code dir="auto">UNVERIFIED_REQ</code></td><td>error</td><td>An active requirement is implemented but has no verifying test tag</td></tr><tr><td><code dir="auto">DRIFT</code></td><td>error</td><td>A member is pinned behind the version at which a requirement it references changed</td></tr><tr><td><code dir="auto">
|
|
165
|
+
<table><thead><tr><th>Code</th><th>Severity</th><th>Condition</th></tr></thead><tbody><tr><td><code dir="auto">INVALID_DOMAIN_FILE</code></td><td>error</td><td>A <code dir="auto">SPEC.json</code> fails the structural schema (bad JSON, malformed <code dir="auto">key</code>/<code dir="auto">id</code>, missing <code dir="auto">statement</code>, unknown envelope key) — loud, never a silent zero</td></tr><tr><td><code dir="auto">DUP_ID</code></td><td>error</td><td>The same requirement ID appears twice</td></tr><tr><td><code dir="auto">BROKEN_SUPERSEDE</code></td><td>error</td><td><code dir="auto">supersededBy</code> points at a missing ID</td></tr><tr><td><code dir="auto">CYCLIC_SUPERSEDE</code></td><td>error</td><td>A circular supersession chain (<code dir="auto">A → B → A</code>) — the chain is the change history, so a cycle is corrupt history</td></tr><tr><td><code dir="auto">BAD_STATUS</code></td><td>error</td><td>Entry status outside <code dir="auto">active</code> / <code dir="auto">draft</code> / <code dir="auto">superseded</code> / <code dir="auto">deprecated</code></td></tr><tr><td><code dir="auto">DANGLING_TAG</code></td><td>error</td><td>A <code dir="auto">@spec</code> tag references no requirement</td></tr><tr><td><code dir="auto">SUPERSEDED_REFERENCED</code></td><td>error</td><td>Code still tags a superseded requirement (retag to the successor)</td></tr><tr><td><code dir="auto">ORPHAN_REQ</code></td><td>error</td><td>An active requirement has no implementing <code dir="auto">@spec</code> tag</td></tr><tr><td><code dir="auto">UNVERIFIED_REQ</code></td><td>error</td><td>An active requirement is implemented but has no verifying test tag</td></tr><tr><td><code dir="auto">DRIFT</code></td><td>error</td><td>A member is pinned behind the version at which a requirement it references changed</td></tr><tr><td><code dir="auto">BROKEN_FILE_REF</code></td><td>error</td><td>An active or draft requirement’s <code dir="auto">livesIn</code> entry does not resolve to a file under the platform root (a terminal-status entry is exempt)</td></tr><tr><td><code dir="auto">NO_SPEC_CONFIG</code></td><td>warning</td><td>A sibling repo-root under the platform has no <code dir="auto">spec-engine.member.json</code></td></tr><tr><td><code dir="auto">BROKEN_RELATES</code></td><td>warning</td><td>A <code dir="auto">relates</code> entry points at a nonexistent requirement ID</td></tr><tr><td><code dir="auto">RELATES_SUPERSEDED</code></td><td>warning</td><td>A <code dir="auto">relates</code> entry points at a superseded requirement</td></tr><tr><td><code dir="auto">SELF_RELATES</code></td><td>warning</td><td>A <code dir="auto">relates</code> entry names its own requirement (dropped, but surfaced)</td></tr><tr><td><code dir="auto">UNKNOWN_ROLE</code></td><td>warning</td><td>An <code dir="auto">issues</code> entry uses a role outside <code dir="auto">created</code> / <code dir="auto">supersedes-via</code> / <code dir="auto">amends-via</code> — surfaced and dropped, never stored</td></tr></tbody></table>
|
|
166
166
|
<div class="sl-heading-wrapper level-h2"><h2 id="glossary-term-diagnostics">Glossary (TERM) diagnostics</h2><a class="sl-anchor-link" href="#glossary-term-diagnostics"><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 “Glossary (TERM) diagnostics”</span></a></div>
|
|
167
167
|
|
|
168
168
|
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
<p>One-time hard cutover: convert every legacy canonical <code dir="auto">SPEC.md</code> to a schema-validated sibling <code dir="auto">SPEC.json</code>, then delete the <code dir="auto">SPEC.md</code>. Idempotent — skips already-migrated dirs.</p>
|
|
194
194
|
<p><strong>Exit codes:</strong> 0 / 2</p>
|
|
195
195
|
<div class="sl-heading-wrapper level-h3"><h3 id="spec-req-domain-prefix-platformdir"><code dir="auto">spec req <domain-prefix> [platformDir]</code></h3><a class="sl-anchor-link" href="#spec-req-domain-prefix-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 req <domain-prefix> [platformDir]”</span></a></div>
|
|
196
|
-
<p><strong>Piped:</strong> prints the next unused requirement ID (e.g. <code dir="auto">BILLING-010</code>) — zero prompts, zero writes. <strong>TTY:</strong> interactive authoring. <strong><code dir="auto">--text</code> (+ <code dir="auto">--why</code> / <code dir="auto">--
|
|
196
|
+
<p><strong>Piped:</strong> prints the next unused requirement ID (e.g. <code dir="auto">BILLING-010</code>) — zero prompts, zero writes. <strong>TTY:</strong> interactive authoring. <strong><code dir="auto">--text</code> (+ <code dir="auto">--why</code> / <code dir="auto">--lives</code>):</strong> appends a born-active entry non-interactively through the validated write seam. Prefix is case-insensitive (<code dir="auto">bil</code> → <code dir="auto">BILLING</code>; ambiguous → exit 2). <code dir="auto">spec req <domain></code> alone prints the domain’s charter before authoring.</p>
|
|
197
197
|
<p><strong>Exit codes:</strong> 0 / 2</p>
|
|
198
198
|
<div class="sl-heading-wrapper level-h3"><h3 id="spec-term-"><code dir="auto">spec term …</code></h3><a class="sl-anchor-link" href="#spec-term-"><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 term …”</span></a></div>
|
|
199
199
|
<p>The glossary authoring surface — a term is a requirement row in the reserved <code dir="auto">TERM</code> domain (definition in the statement, headword in <code dir="auto">term</code>, synonyms in <code dir="auto">aliases</code>):</p>
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
<p>The GLOSSARY.md round-trip. Default: <strong>generate</strong> GLOSSARY.md from the TERM store, byte-stable and LLM-free. <code dir="auto">--migrate</code>: one-time parse of an existing GLOSSARY.md into <code dir="auto">TERM-001..N</code> (idempotent-skip). <code dir="auto">--check</code>: regenerate into a buffer and fail (exit 1) if the committed file drifts — a CI fence.</p>
|
|
210
210
|
<p><strong>Exit codes:</strong> 0 / 1 / 2</p>
|
|
211
211
|
<div class="sl-heading-wrapper level-h3"><h3 id="spec-supersede-reqid-platformdir"><code dir="auto">spec supersede <reqId> [platformDir]</code></h3><a class="sl-anchor-link" href="#spec-supersede-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 supersede <reqId> [platformDir]”</span></a></div>
|
|
212
|
-
<p>The post-ship lifecycle move, mechanized. Flips the entry to superseded, mints the successor (<code dir="auto">--text</code> required non-TTY; <code dir="auto">--why</code>/<code dir="auto">--
|
|
212
|
+
<p>The post-ship lifecycle move, mechanized. Flips the entry to superseded, mints the successor (<code dir="auto">--text</code> required non-TTY; <code dir="auto">--why</code>/<code dir="auto">--lives</code> default to copies), reindexes, and prints the <strong>retag worklist</strong> — every code site still on the old ID. The target must be Active.</p>
|
|
213
213
|
<p>On a requirement domain the reported <code dir="auto">spec_version</code> is the <strong>derived</strong> domain version after the supersession — no authored counter is written, and <code dir="auto">--no-bump</code> is a no-op (there is no counter to hold back). On the reserved TERM domain the authored <code dir="auto">specVersion</code> still bumps (it is the term-drift pin) and <code dir="auto">--no-bump</code> opts out.</p>
|
|
214
214
|
<p><code dir="auto">--json</code>: <code dir="auto">{ old_id, new_id, file, spec_version, retag: [...] }</code></p>
|
|
215
215
|
<p><strong>Exit codes:</strong> 0 / 2</p>
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
<p>Cross-domain supersede for taxonomy reorganization: mints the successor as the next id in <code dir="auto"><NEW-DOMAIN></code> (which must exist), carrying the source’s fields (<code dir="auto">--text</code>/<code dir="auto">--why</code>/<code dir="auto">--lives</code> override), flips the source to superseded with a cross-domain <code dir="auto">supersededBy</code>, and emits the retag worklist. Both envelopes validate before either is written — a reject never half-applies. Only Active requirements move; moving into the source’s own domain is a usage error (use <code dir="auto">supersede</code>).</p>
|
|
218
218
|
<p><strong>Exit codes:</strong> 0 / 2</p>
|
|
219
219
|
<div class="sl-heading-wrapper level-h3"><h3 id="spec-amend-reqid-platformdir"><code dir="auto">spec amend <reqId> [platformDir]</code></h3><a class="sl-anchor-link" href="#spec-amend-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 amend <reqId> [platformDir]”</span></a></div>
|
|
220
|
-
<p>Revise an unshipped requirement’s fields in place — same ID, no version change (the domain version only derives from supersessions). Field flags name what changes (<code dir="auto">--text</code>/<code dir="auto">--why</code>/<code dir="auto">--
|
|
220
|
+
<p>Revise an unshipped requirement’s fields in place — same ID, no version change (the domain version only derives from supersessions). Field flags name what changes (<code dir="auto">--text</code>/<code dir="auto">--why</code>/<code dir="auto">--lives</code>, at least one); untouched fields stay byte-identical. Only Active and Draft entries amend.</p>
|
|
221
221
|
<p><strong>Exit codes:</strong> 0 / 2</p>
|
|
222
222
|
<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>
|
|
223
223
|
<p>Launch the local webapp over the derived index. Binds <code dir="auto">127.0.0.1</code> only. <code dir="auto">--probe</code> boots on an ephemeral port, smoke-tests, and exits.</p>
|
|
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_4cf42761c7","wasm":"en","page_count":11}},"include_characters":["_","‿","⁀","⁔","︳","︴","﹍","﹎","﹏","_"]}
|
|
Binary file
|
package/dist/impl.js
CHANGED
|
@@ -661,6 +661,23 @@ var init_constants = __esm(() => {
|
|
|
661
661
|
OUT_HELP = `DB path override (default: <platformDir>/${INDEX_DIR}/${INDEX_DB_FILENAME})`;
|
|
662
662
|
});
|
|
663
663
|
|
|
664
|
+
// src/parser/requirementLine.ts
|
|
665
|
+
function findRequirementIdLine(rawLines, id, from = 0) {
|
|
666
|
+
const needle = `"id": "${id}"`;
|
|
667
|
+
const exact = `${SEAM_INDENT}${needle}`;
|
|
668
|
+
const start = from > 0 ? from : 0;
|
|
669
|
+
let loose = -1;
|
|
670
|
+
for (let i = start;i < rawLines.length; i++) {
|
|
671
|
+
const line = rawLines[i];
|
|
672
|
+
if (line === exact || line === `${exact},`)
|
|
673
|
+
return i;
|
|
674
|
+
if (loose < 0 && line.includes(needle))
|
|
675
|
+
loose = i;
|
|
676
|
+
}
|
|
677
|
+
return loose;
|
|
678
|
+
}
|
|
679
|
+
var SEAM_INDENT = " ";
|
|
680
|
+
|
|
664
681
|
// src/parser/domainJson.ts
|
|
665
682
|
function parseDomainJsonFile(opts) {
|
|
666
683
|
let parsed;
|
|
@@ -698,6 +715,7 @@ function parseDomainJson(opts) {
|
|
|
698
715
|
const updated = data.updated ?? null;
|
|
699
716
|
const rawLines = text.split(`
|
|
700
717
|
`);
|
|
718
|
+
let lineCursor = 0;
|
|
701
719
|
const working = [];
|
|
702
720
|
const relatesAcc = {
|
|
703
721
|
relations: [],
|
|
@@ -718,7 +736,10 @@ function parseDomainJson(opts) {
|
|
|
718
736
|
};
|
|
719
737
|
for (let idx = 0;idx < data.requirements.length; idx++) {
|
|
720
738
|
const r = data.requirements[idx];
|
|
721
|
-
const
|
|
739
|
+
const found = findRequirementIdLine(rawLines, r.id, lineCursor);
|
|
740
|
+
const line = found >= 0 ? found + 1 : idx + 1;
|
|
741
|
+
if (found >= 0)
|
|
742
|
+
lineCursor = found + 1;
|
|
722
743
|
working.push(mapRequirementRow(r, key, sourceFile, spec_version, line));
|
|
723
744
|
flattenRelates(r, sourceFile, line, relatesAcc);
|
|
724
745
|
flattenIssues(r, sourceFile, line, issuesAcc);
|
|
@@ -742,11 +763,6 @@ function parseDomainJson(opts) {
|
|
|
742
763
|
term_citations: termAcc.citations
|
|
743
764
|
};
|
|
744
765
|
}
|
|
745
|
-
function deriveRequirementLine(rawLines, id, idx) {
|
|
746
|
-
const needle = `"id": "${id}"`;
|
|
747
|
-
const found = rawLines.findIndex((l) => l.includes(needle));
|
|
748
|
-
return found >= 0 ? found + 1 : idx + 1;
|
|
749
|
-
}
|
|
750
766
|
function mapRequirementRow(r, key, sourceFile, spec_version, line) {
|
|
751
767
|
const id = r.id;
|
|
752
768
|
const seq = Number(id.split("-")[1]);
|
|
@@ -1363,10 +1379,11 @@ class SqliteStorage {
|
|
|
1363
1379
|
return row ?? null;
|
|
1364
1380
|
}
|
|
1365
1381
|
listDomains() {
|
|
1366
|
-
return
|
|
1382
|
+
return this.#db.query(LIST_DOMAINS_SQL).all();
|
|
1367
1383
|
}
|
|
1368
|
-
getDomain(
|
|
1369
|
-
|
|
1384
|
+
getDomain(key) {
|
|
1385
|
+
const row = this.#db.query(GET_DOMAIN_SQL).get({ key });
|
|
1386
|
+
return row ?? null;
|
|
1370
1387
|
}
|
|
1371
1388
|
listRequirements(opts) {
|
|
1372
1389
|
const key = opts?.key;
|
|
@@ -1996,7 +2013,7 @@ SELECT
|
|
|
1996
2013
|
FROM repos
|
|
1997
2014
|
WHERE repos.name != 'spec-engine'
|
|
1998
2015
|
ORDER BY repos.name
|
|
1999
|
-
`, FTS_SEARCH_SQL = `SELECT r.id AS req_id, r.key AS key, r.text AS text, r.why AS why, r.source_file AS source_file, r.line AS line, bm25(requirements_fts, 1.0, 0.5) AS rank FROM requirements_fts JOIN requirements r ON r.rowid = requirements_fts.rowid WHERE requirements_fts MATCH $query AND r.status NOT IN ('Superseded', 'Deprecated') ORDER BY rank ASC LIMIT $limit`, RESOLVE_BY_FILES_SQL_PREFIX = "SELECT DISTINCT r.id, r.key, r.seq, r.status, r.superseded_by, r.text, r.why, r.source_file, r.line, r.spec_version, r.changed_at_version, r.superseded_at_version FROM tags t JOIN requirements r ON r.id = t.req_id WHERE t.file IN (", RESOLVE_BY_FILES_SQL_SUFFIX = ") ORDER BY r.key, r.seq", 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_TAGS_SQL = `SELECT id, req_id, repo, file, line, kind, level FROM tags
|
|
2016
|
+
`, FTS_SEARCH_SQL = `SELECT r.id AS req_id, r.key AS key, r.text AS text, r.why AS why, r.source_file AS source_file, r.line AS line, bm25(requirements_fts, 1.0, 0.5) AS rank FROM requirements_fts JOIN requirements r ON r.rowid = requirements_fts.rowid WHERE requirements_fts MATCH $query AND r.status NOT IN ('Superseded', 'Deprecated') ORDER BY rank ASC LIMIT $limit`, RESOLVE_BY_FILES_SQL_PREFIX = "SELECT DISTINCT r.id, r.key, r.seq, r.status, r.superseded_by, r.text, r.why, r.source_file, r.line, r.spec_version, r.changed_at_version, r.superseded_at_version FROM tags t JOIN requirements r ON r.id = t.req_id WHERE t.file IN (", RESOLVE_BY_FILES_SQL_SUFFIX = ") ORDER BY r.key, r.seq", 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
|
|
2000
2017
|
WHERE ($repo IS NULL OR repo = $repo)
|
|
2001
2018
|
AND ($req_id IS NULL OR req_id = $req_id)
|
|
2002
2019
|
AND ($file IS NULL OR file = $file)
|
|
@@ -3106,49 +3123,6 @@ function matchesSegment(pat, seg) {
|
|
|
3106
3123
|
return p === pat.length;
|
|
3107
3124
|
}
|
|
3108
3125
|
|
|
3109
|
-
// src/check/format.ts
|
|
3110
|
-
function compareRepoNullsLast(a, b) {
|
|
3111
|
-
if (a === b)
|
|
3112
|
-
return 0;
|
|
3113
|
-
if (a === null)
|
|
3114
|
-
return 1;
|
|
3115
|
-
if (b === null)
|
|
3116
|
-
return -1;
|
|
3117
|
-
return a.localeCompare(b);
|
|
3118
|
-
}
|
|
3119
|
-
function compareDiagnostics(a, b) {
|
|
3120
|
-
const codeCmp = a.code.localeCompare(b.code);
|
|
3121
|
-
if (codeCmp !== 0)
|
|
3122
|
-
return codeCmp;
|
|
3123
|
-
const repoCmp = compareRepoNullsLast(a.repo, b.repo);
|
|
3124
|
-
if (repoCmp !== 0)
|
|
3125
|
-
return repoCmp;
|
|
3126
|
-
const fileCmp = (a.source_file ?? "").localeCompare(b.source_file ?? "");
|
|
3127
|
-
if (fileCmp !== 0)
|
|
3128
|
-
return fileCmp;
|
|
3129
|
-
const lineCmp = (a.line ?? 0) - (b.line ?? 0);
|
|
3130
|
-
if (lineCmp !== 0)
|
|
3131
|
-
return lineCmp;
|
|
3132
|
-
return (a.req_id ?? "").localeCompare(b.req_id ?? "");
|
|
3133
|
-
}
|
|
3134
|
-
function sortDiagnostics(rows) {
|
|
3135
|
-
return [...rows].sort(compareDiagnostics);
|
|
3136
|
-
}
|
|
3137
|
-
function renderDiagnostics(rows, mode) {
|
|
3138
|
-
const sorted = sortDiagnostics(rows);
|
|
3139
|
-
if (mode === "json") {
|
|
3140
|
-
return JSON.stringify(sorted);
|
|
3141
|
-
}
|
|
3142
|
-
return sorted.map((d) => {
|
|
3143
|
-
const repo = d.repo ?? "";
|
|
3144
|
-
const file = d.source_file ?? "";
|
|
3145
|
-
const line = d.line ?? "";
|
|
3146
|
-
const reqId = d.req_id ?? "";
|
|
3147
|
-
return `${d.code} ${repo} ${file}:${line} ${reqId} ${d.detail}`;
|
|
3148
|
-
}).join(`
|
|
3149
|
-
`);
|
|
3150
|
-
}
|
|
3151
|
-
|
|
3152
3126
|
// src/authoring/domains.ts
|
|
3153
3127
|
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
3154
3128
|
import { join as join8 } from "path";
|
|
@@ -3220,21 +3194,150 @@ var init_domains = __esm(() => {
|
|
|
3220
3194
|
init_src();
|
|
3221
3195
|
});
|
|
3222
3196
|
|
|
3223
|
-
// src/
|
|
3197
|
+
// src/authoring/filerefs.ts
|
|
3198
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3199
|
+
import { resolve as resolve7 } from "path";
|
|
3200
|
+
function extractRefsFromText(s) {
|
|
3201
|
+
const out = [];
|
|
3202
|
+
for (const m of s.matchAll(FILE_REF_RE)) {
|
|
3203
|
+
const candidate = m[1].replace(/\.+$/, "");
|
|
3204
|
+
if (candidate.includes("/"))
|
|
3205
|
+
out.push(candidate);
|
|
3206
|
+
}
|
|
3207
|
+
return out;
|
|
3208
|
+
}
|
|
3209
|
+
function resolveFileRef(platformDir, ref) {
|
|
3210
|
+
const root = resolve7(platformDir);
|
|
3211
|
+
const abs = resolve7(root, ref);
|
|
3212
|
+
if (!(abs === root || abs.startsWith(`${root}/`)))
|
|
3213
|
+
return false;
|
|
3214
|
+
return existsSync6(abs);
|
|
3215
|
+
}
|
|
3216
|
+
var FILE_REF_RE;
|
|
3217
|
+
var init_filerefs = __esm(() => {
|
|
3218
|
+
FILE_REF_RE = /(?<![^\s])@([A-Za-z0-9_][A-Za-z0-9_\-./]*)/g;
|
|
3219
|
+
});
|
|
3220
|
+
|
|
3221
|
+
// src/check/filerefs.ts
|
|
3224
3222
|
import { join as join9 } from "path";
|
|
3225
|
-
function
|
|
3226
|
-
|
|
3223
|
+
async function readDomain(platformDir, key) {
|
|
3224
|
+
try {
|
|
3225
|
+
const raw = await Bun.file(join9(platformDir, "spec-engine", key, "SPEC.json")).text();
|
|
3226
|
+
const doc = JSON.parse(raw);
|
|
3227
|
+
return {
|
|
3228
|
+
raw,
|
|
3229
|
+
requirements: Array.isArray(doc.requirements) ? doc.requirements : []
|
|
3230
|
+
};
|
|
3231
|
+
} catch {
|
|
3232
|
+
return null;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
function rowsForRequirement(platformDir, key, reqId, livesIn, line) {
|
|
3236
|
+
if (!Array.isArray(livesIn))
|
|
3237
|
+
return [];
|
|
3238
|
+
const rows = [];
|
|
3239
|
+
for (const entry of livesIn) {
|
|
3240
|
+
if (typeof entry !== "string")
|
|
3241
|
+
continue;
|
|
3242
|
+
const ref = entry.startsWith("@") ? entry.slice(1) : entry;
|
|
3243
|
+
if (ref.length === 0 || resolveFileRef(platformDir, ref))
|
|
3244
|
+
continue;
|
|
3245
|
+
rows.push({
|
|
3246
|
+
code: DiagnosticCode.BROKEN_FILE_REF,
|
|
3247
|
+
severity: "error",
|
|
3248
|
+
repo: null,
|
|
3249
|
+
source_file: `spec-engine/${key}/SPEC.json`,
|
|
3250
|
+
line,
|
|
3251
|
+
req_id: reqId,
|
|
3252
|
+
detail: `livesIn entry ${entry} does not resolve to a file under the platform root`
|
|
3253
|
+
});
|
|
3254
|
+
}
|
|
3255
|
+
return rows;
|
|
3256
|
+
}
|
|
3257
|
+
function rowsForDomain(platformDir, key, domain2) {
|
|
3258
|
+
const rows = [];
|
|
3259
|
+
const rawLines = domain2.raw.split(`
|
|
3227
3260
|
`);
|
|
3228
|
-
|
|
3229
|
-
for (
|
|
3230
|
-
if (
|
|
3231
|
-
|
|
3261
|
+
let lineCursor = 0;
|
|
3262
|
+
for (const req of domain2.requirements) {
|
|
3263
|
+
if (typeof req?.id !== "string")
|
|
3264
|
+
continue;
|
|
3265
|
+
const found = findRequirementIdLine(rawLines, req.id, lineCursor);
|
|
3266
|
+
if (found >= 0)
|
|
3267
|
+
lineCursor = found + 1;
|
|
3268
|
+
const status = typeof req.status === "string" ? req.status.toLowerCase() : "";
|
|
3269
|
+
if (!CHECKED_STATUSES.has(status))
|
|
3270
|
+
continue;
|
|
3271
|
+
rows.push(...rowsForRequirement(platformDir, key, req.id, req.livesIn, found >= 0 ? found + 1 : 0));
|
|
3272
|
+
}
|
|
3273
|
+
return rows;
|
|
3274
|
+
}
|
|
3275
|
+
async function brokenFileRefDiagnostics(platformDir) {
|
|
3276
|
+
const rows = [];
|
|
3277
|
+
for (const key of listDomainKeys(platformDir)) {
|
|
3278
|
+
const domain2 = await readDomain(platformDir, key);
|
|
3279
|
+
if (domain2 !== null)
|
|
3280
|
+
rows.push(...rowsForDomain(platformDir, key, domain2));
|
|
3281
|
+
}
|
|
3282
|
+
rows.sort((a, b) => (a.source_file ?? "").localeCompare(b.source_file ?? "") || (a.line ?? 0) - (b.line ?? 0) || (a.req_id ?? "").localeCompare(b.req_id ?? "") || a.detail.localeCompare(b.detail));
|
|
3283
|
+
return rows;
|
|
3284
|
+
}
|
|
3285
|
+
var CHECKED_STATUSES;
|
|
3286
|
+
var init_filerefs2 = __esm(() => {
|
|
3287
|
+
init_src();
|
|
3288
|
+
init_domains();
|
|
3289
|
+
init_filerefs();
|
|
3290
|
+
CHECKED_STATUSES = new Set(["active", "draft"]);
|
|
3291
|
+
});
|
|
3292
|
+
|
|
3293
|
+
// src/check/format.ts
|
|
3294
|
+
function compareRepoNullsLast(a, b) {
|
|
3295
|
+
if (a === b)
|
|
3296
|
+
return 0;
|
|
3297
|
+
if (a === null)
|
|
3298
|
+
return 1;
|
|
3299
|
+
if (b === null)
|
|
3300
|
+
return -1;
|
|
3301
|
+
return a.localeCompare(b);
|
|
3302
|
+
}
|
|
3303
|
+
function compareDiagnostics(a, b) {
|
|
3304
|
+
const codeCmp = a.code.localeCompare(b.code);
|
|
3305
|
+
if (codeCmp !== 0)
|
|
3306
|
+
return codeCmp;
|
|
3307
|
+
const repoCmp = compareRepoNullsLast(a.repo, b.repo);
|
|
3308
|
+
if (repoCmp !== 0)
|
|
3309
|
+
return repoCmp;
|
|
3310
|
+
const fileCmp = (a.source_file ?? "").localeCompare(b.source_file ?? "");
|
|
3311
|
+
if (fileCmp !== 0)
|
|
3312
|
+
return fileCmp;
|
|
3313
|
+
const lineCmp = (a.line ?? 0) - (b.line ?? 0);
|
|
3314
|
+
if (lineCmp !== 0)
|
|
3315
|
+
return lineCmp;
|
|
3316
|
+
return (a.req_id ?? "").localeCompare(b.req_id ?? "");
|
|
3317
|
+
}
|
|
3318
|
+
function sortDiagnostics(rows) {
|
|
3319
|
+
return [...rows].sort(compareDiagnostics);
|
|
3320
|
+
}
|
|
3321
|
+
function renderDiagnostics(rows, mode) {
|
|
3322
|
+
const sorted = sortDiagnostics(rows);
|
|
3323
|
+
if (mode === "json") {
|
|
3324
|
+
return JSON.stringify(sorted);
|
|
3232
3325
|
}
|
|
3233
|
-
return
|
|
3326
|
+
return sorted.map((d) => {
|
|
3327
|
+
const repo = d.repo ?? "";
|
|
3328
|
+
const file = d.source_file ?? "";
|
|
3329
|
+
const line = d.line ?? "";
|
|
3330
|
+
const reqId = d.req_id ?? "";
|
|
3331
|
+
return `${d.code} ${repo} ${file}:${line} ${reqId} ${d.detail}`;
|
|
3332
|
+
}).join(`
|
|
3333
|
+
`);
|
|
3234
3334
|
}
|
|
3335
|
+
|
|
3336
|
+
// src/check/grammar.ts
|
|
3337
|
+
import { join as join10 } from "path";
|
|
3235
3338
|
async function readDeclaredDomain(platformDir, key) {
|
|
3236
3339
|
try {
|
|
3237
|
-
const raw = await Bun.file(
|
|
3340
|
+
const raw = await Bun.file(join10(platformDir, "spec-engine", key, "SPEC.json")).text();
|
|
3238
3341
|
const doc = JSON.parse(raw);
|
|
3239
3342
|
if (doc.grammar !== "ears")
|
|
3240
3343
|
return null;
|
|
@@ -3247,13 +3350,21 @@ async function readDeclaredDomain(platformDir, key) {
|
|
|
3247
3350
|
return null;
|
|
3248
3351
|
}
|
|
3249
3352
|
}
|
|
3250
|
-
function
|
|
3353
|
+
function rowsForDomain2(key, domain2) {
|
|
3251
3354
|
const rows = [];
|
|
3355
|
+
const rawLines = domain2.raw.split(`
|
|
3356
|
+
`);
|
|
3357
|
+
let lineCursor = 0;
|
|
3252
3358
|
for (const req of domain2.requirements) {
|
|
3253
|
-
if (typeof req?.id !== "string"
|
|
3359
|
+
if (typeof req?.id !== "string")
|
|
3360
|
+
continue;
|
|
3361
|
+
const found = findRequirementIdLine(rawLines, req.id, lineCursor);
|
|
3362
|
+
if (found >= 0)
|
|
3363
|
+
lineCursor = found + 1;
|
|
3364
|
+
if (typeof req.statement !== "string")
|
|
3254
3365
|
continue;
|
|
3255
3366
|
const status = typeof req.status === "string" ? req.status.toLowerCase() : "";
|
|
3256
|
-
if (!
|
|
3367
|
+
if (!CHECKED_STATUSES2.has(status))
|
|
3257
3368
|
continue;
|
|
3258
3369
|
const result = parseEarsStatement(req.statement);
|
|
3259
3370
|
if (result.ok)
|
|
@@ -3263,7 +3374,7 @@ function rowsForDomain(key, domain2) {
|
|
|
3263
3374
|
severity: domain2.severity,
|
|
3264
3375
|
repo: null,
|
|
3265
3376
|
source_file: `spec-engine/${key}/SPEC.json`,
|
|
3266
|
-
line:
|
|
3377
|
+
line: found >= 0 ? found + 1 : 0,
|
|
3267
3378
|
req_id: req.id,
|
|
3268
3379
|
detail: `${result.problem}. Expected one of:
|
|
3269
3380
|
${result.expected}`
|
|
@@ -3278,16 +3389,16 @@ async function statementGrammarDiagnostics(platformDir) {
|
|
|
3278
3389
|
continue;
|
|
3279
3390
|
const domain2 = await readDeclaredDomain(platformDir, key);
|
|
3280
3391
|
if (domain2 !== null)
|
|
3281
|
-
rows.push(...
|
|
3392
|
+
rows.push(...rowsForDomain2(key, domain2));
|
|
3282
3393
|
}
|
|
3283
3394
|
rows.sort((a, b) => (a.source_file ?? "").localeCompare(b.source_file ?? "") || (a.line ?? 0) - (b.line ?? 0) || (a.req_id ?? "").localeCompare(b.req_id ?? ""));
|
|
3284
3395
|
return rows;
|
|
3285
3396
|
}
|
|
3286
|
-
var
|
|
3397
|
+
var CHECKED_STATUSES2;
|
|
3287
3398
|
var init_grammar = __esm(() => {
|
|
3288
3399
|
init_src();
|
|
3289
3400
|
init_domains();
|
|
3290
|
-
|
|
3401
|
+
CHECKED_STATUSES2 = new Set(["active", "draft"]);
|
|
3291
3402
|
});
|
|
3292
3403
|
|
|
3293
3404
|
// src/check/proven.ts
|
|
@@ -3536,6 +3647,72 @@ var init_statusflip = __esm(() => {
|
|
|
3536
3647
|
TERMINAL_STATUSES = new Set(["superseded", "deprecated", "retired"]);
|
|
3537
3648
|
});
|
|
3538
3649
|
|
|
3650
|
+
// src/check/supersedes.ts
|
|
3651
|
+
import { join as join11 } from "path";
|
|
3652
|
+
async function entriesForDomain(platformDir, key) {
|
|
3653
|
+
let raw;
|
|
3654
|
+
let reqs;
|
|
3655
|
+
try {
|
|
3656
|
+
raw = await Bun.file(join11(platformDir, "spec-engine", key, "SPEC.json")).text();
|
|
3657
|
+
const doc = JSON.parse(raw);
|
|
3658
|
+
reqs = Array.isArray(doc.requirements) ? doc.requirements : [];
|
|
3659
|
+
} catch {
|
|
3660
|
+
return [];
|
|
3661
|
+
}
|
|
3662
|
+
const out = [];
|
|
3663
|
+
const rawLines = raw.split(`
|
|
3664
|
+
`);
|
|
3665
|
+
let lineCursor = 0;
|
|
3666
|
+
for (const r of reqs) {
|
|
3667
|
+
if (typeof r?.id !== "string")
|
|
3668
|
+
continue;
|
|
3669
|
+
const found = findRequirementIdLine(rawLines, r.id, lineCursor);
|
|
3670
|
+
if (found >= 0)
|
|
3671
|
+
lineCursor = found + 1;
|
|
3672
|
+
out.push({
|
|
3673
|
+
id: r.id,
|
|
3674
|
+
supersedes: typeof r.supersedes === "string" ? r.supersedes : null,
|
|
3675
|
+
line: found >= 0 ? found + 1 : 0,
|
|
3676
|
+
sourceFile: `spec-engine/${key}/SPEC.json`
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3679
|
+
return out;
|
|
3680
|
+
}
|
|
3681
|
+
async function collectEntries(platformDir) {
|
|
3682
|
+
const out = [];
|
|
3683
|
+
for (const key of listDomainKeys(platformDir)) {
|
|
3684
|
+
out.push(...await entriesForDomain(platformDir, key));
|
|
3685
|
+
}
|
|
3686
|
+
return out;
|
|
3687
|
+
}
|
|
3688
|
+
async function supersedesPointerDiagnostics(platformDir) {
|
|
3689
|
+
const entries = await collectEntries(platformDir);
|
|
3690
|
+
const ids = new Set(entries.map((e) => e.id));
|
|
3691
|
+
const rows = [];
|
|
3692
|
+
for (const e of entries) {
|
|
3693
|
+
if (e.supersedes === null)
|
|
3694
|
+
continue;
|
|
3695
|
+
const detail = e.supersedes === e.id ? `${e.id} declares supersedes on itself` : ids.has(e.supersedes) ? null : `${e.id} supersedes ${e.supersedes} which does not exist`;
|
|
3696
|
+
if (detail === null)
|
|
3697
|
+
continue;
|
|
3698
|
+
rows.push({
|
|
3699
|
+
code: DiagnosticCode.BROKEN_SUPERSEDE,
|
|
3700
|
+
severity: "error",
|
|
3701
|
+
repo: null,
|
|
3702
|
+
source_file: e.sourceFile,
|
|
3703
|
+
line: e.line,
|
|
3704
|
+
req_id: e.id,
|
|
3705
|
+
detail
|
|
3706
|
+
});
|
|
3707
|
+
}
|
|
3708
|
+
rows.sort((a, b) => (a.source_file ?? "").localeCompare(b.source_file ?? "") || (a.line ?? 0) - (b.line ?? 0) || (a.req_id ?? "").localeCompare(b.req_id ?? ""));
|
|
3709
|
+
return rows;
|
|
3710
|
+
}
|
|
3711
|
+
var init_supersedes = __esm(() => {
|
|
3712
|
+
init_src();
|
|
3713
|
+
init_domains();
|
|
3714
|
+
});
|
|
3715
|
+
|
|
3539
3716
|
// src/check/unsourced.ts
|
|
3540
3717
|
function unsourcedChanges(requirements, provenance) {
|
|
3541
3718
|
const sourced = new Set;
|
|
@@ -3841,8 +4018,8 @@ __export(exports_glossary, {
|
|
|
3841
4018
|
glossaryCommand: () => glossaryCommand,
|
|
3842
4019
|
generateGlossary: () => generateGlossary
|
|
3843
4020
|
});
|
|
3844
|
-
import { existsSync as
|
|
3845
|
-
import { join as
|
|
4021
|
+
import { existsSync as existsSync7, readFileSync } from "fs";
|
|
4022
|
+
import { join as join12, resolve as resolve8 } from "path";
|
|
3846
4023
|
import { defineCommand as defineCommand3 } from "citty";
|
|
3847
4024
|
function parseGlossary(md) {
|
|
3848
4025
|
const terms = [];
|
|
@@ -3894,7 +4071,7 @@ function generateGlossary(terms) {
|
|
|
3894
4071
|
`;
|
|
3895
4072
|
}
|
|
3896
4073
|
function resolvePlatform(platformDirRaw) {
|
|
3897
|
-
const platformDir =
|
|
4074
|
+
const platformDir = resolve8(platformDirRaw ?? process.cwd());
|
|
3898
4075
|
try {
|
|
3899
4076
|
assertSpecPlatform(platformDir);
|
|
3900
4077
|
} catch (e) {
|
|
@@ -3903,8 +4080,8 @@ function resolvePlatform(platformDirRaw) {
|
|
|
3903
4080
|
return platformDir;
|
|
3904
4081
|
}
|
|
3905
4082
|
function readStoreTerms(platformDir) {
|
|
3906
|
-
const specPath =
|
|
3907
|
-
if (!
|
|
4083
|
+
const specPath = join12(platformDir, "spec-engine", TERM_KEY, "SPEC.json");
|
|
4084
|
+
if (!existsSync7(specPath))
|
|
3908
4085
|
return [];
|
|
3909
4086
|
const domain2 = JSON.parse(readFileSync(specPath, "utf8"));
|
|
3910
4087
|
const reqs = Array.isArray(domain2.requirements) ? domain2.requirements : [];
|
|
@@ -3915,12 +4092,12 @@ function readStoreTerms(platformDir) {
|
|
|
3915
4092
|
}));
|
|
3916
4093
|
}
|
|
3917
4094
|
function glossaryPath(platformDir) {
|
|
3918
|
-
return
|
|
4095
|
+
return join12(platformDir, "GLOSSARY.md");
|
|
3919
4096
|
}
|
|
3920
4097
|
async function migrateGlossary(platformDir, json) {
|
|
3921
4098
|
const relFile = `spec-engine/${TERM_KEY}/SPEC.json`;
|
|
3922
|
-
const specPath =
|
|
3923
|
-
if (!
|
|
4099
|
+
const specPath = join12(platformDir, "spec-engine", TERM_KEY, "SPEC.json");
|
|
4100
|
+
if (!existsSync7(specPath)) {
|
|
3924
4101
|
console.error(`spec glossary: no TERM domain (expected ${relFile} under ${platformDir})`);
|
|
3925
4102
|
process.exit(EXIT.USAGE);
|
|
3926
4103
|
}
|
|
@@ -3935,7 +4112,7 @@ async function migrateGlossary(platformDir, json) {
|
|
|
3935
4112
|
return;
|
|
3936
4113
|
}
|
|
3937
4114
|
const gPath = glossaryPath(platformDir);
|
|
3938
|
-
if (!
|
|
4115
|
+
if (!existsSync7(gPath)) {
|
|
3939
4116
|
console.error(`spec glossary: no GLOSSARY.md to migrate (expected ${gPath})`);
|
|
3940
4117
|
process.exit(EXIT.USAGE);
|
|
3941
4118
|
}
|
|
@@ -3982,7 +4159,7 @@ function glossaryDriftDiagnostic(platformDir) {
|
|
|
3982
4159
|
if (terms.length === 0)
|
|
3983
4160
|
return null;
|
|
3984
4161
|
const gPath = glossaryPath(platformDir);
|
|
3985
|
-
if (!
|
|
4162
|
+
if (!existsSync7(gPath))
|
|
3986
4163
|
return null;
|
|
3987
4164
|
const generated = generateGlossary(terms);
|
|
3988
4165
|
const committed = readFileSync(gPath, "utf8");
|
|
@@ -4001,7 +4178,7 @@ function glossaryDriftDiagnostic(platformDir) {
|
|
|
4001
4178
|
function checkGlossary(platformDir, json) {
|
|
4002
4179
|
const generated = generateGlossary(readStoreTerms(platformDir));
|
|
4003
4180
|
const gPath = glossaryPath(platformDir);
|
|
4004
|
-
const committed =
|
|
4181
|
+
const committed = existsSync7(gPath) ? readFileSync(gPath, "utf8") : "";
|
|
4005
4182
|
if (generated === committed) {
|
|
4006
4183
|
if (json)
|
|
4007
4184
|
console.log(JSON.stringify({ ok: true }));
|
|
@@ -4063,12 +4240,12 @@ var exports_check = {};
|
|
|
4063
4240
|
__export(exports_check, {
|
|
4064
4241
|
checkCommand: () => checkCommand
|
|
4065
4242
|
});
|
|
4066
|
-
import { existsSync as
|
|
4067
|
-
import { dirname as dirname7, join as
|
|
4243
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync2 } from "fs";
|
|
4244
|
+
import { dirname as dirname7, join as join13, resolve as resolve9 } from "path";
|
|
4068
4245
|
import { defineCommand as defineCommand4 } from "citty";
|
|
4069
4246
|
function readCodeownersText(platformDir) {
|
|
4070
|
-
const path =
|
|
4071
|
-
if (!
|
|
4247
|
+
const path = join13(platformDir, ".github", "CODEOWNERS");
|
|
4248
|
+
if (!existsSync8(path))
|
|
4072
4249
|
return null;
|
|
4073
4250
|
return readFileSync2(path, "utf-8");
|
|
4074
4251
|
}
|
|
@@ -4107,7 +4284,7 @@ async function ingestResults(storage2, platformDir, resultsArg, jsonMode, diagno
|
|
|
4107
4284
|
}
|
|
4108
4285
|
return { resultsParsed: undefined, verifyingTags: undefined };
|
|
4109
4286
|
}
|
|
4110
|
-
const resultsPath =
|
|
4287
|
+
const resultsPath = resolve9(platformDir, resultsArg);
|
|
4111
4288
|
if (!isContainedPath(resultsPath, platformDir)) {
|
|
4112
4289
|
console.error(`spec check: --results path must be inside platformDir (resolved to ${resultsPath})`);
|
|
4113
4290
|
try {
|
|
@@ -4127,7 +4304,7 @@ async function collectChangeReqs(canonicalDir) {
|
|
|
4127
4304
|
const relPathById = new Map;
|
|
4128
4305
|
for (const rel of await findDomainJsonFiles(canonicalDir)) {
|
|
4129
4306
|
const platformRel = `spec-engine/${rel}`;
|
|
4130
|
-
const text = await Bun.file(
|
|
4307
|
+
const text = await Bun.file(join13(canonicalDir, rel)).text();
|
|
4131
4308
|
const parsed = domainReqsFromText(text, platformRel);
|
|
4132
4309
|
for (const r of parsed.reqs) {
|
|
4133
4310
|
reqs.push(r);
|
|
@@ -4167,7 +4344,7 @@ async function runGovernanceGate(storage2, platformDir, args, diagnostics2, resu
|
|
|
4167
4344
|
} catch {}
|
|
4168
4345
|
process.exit(EXIT.USAGE);
|
|
4169
4346
|
}
|
|
4170
|
-
const canonicalDir =
|
|
4347
|
+
const canonicalDir = join13(platformDir, "spec-engine");
|
|
4171
4348
|
const change = await collectChangeReqs(canonicalDir);
|
|
4172
4349
|
const base = collectBaseReqs(platformDir, ref, diagnostics2);
|
|
4173
4350
|
diagnostics2.push(...requirementRemoved(base.reqs, change.reqs, (id) => base.relPathById.get(id) ?? null));
|
|
@@ -4193,11 +4370,13 @@ function exitAfterCrash(e, storage2) {
|
|
|
4193
4370
|
var checkCommand;
|
|
4194
4371
|
var init_check = __esm(() => {
|
|
4195
4372
|
init_src();
|
|
4373
|
+
init_filerefs2();
|
|
4196
4374
|
init_grammar();
|
|
4197
4375
|
init_propagation_teeth();
|
|
4198
4376
|
init_proven();
|
|
4199
4377
|
init_removed();
|
|
4200
4378
|
init_statusflip();
|
|
4379
|
+
init_supersedes();
|
|
4201
4380
|
init_unsourced();
|
|
4202
4381
|
init_constants();
|
|
4203
4382
|
init_discover();
|
|
@@ -4257,7 +4436,7 @@ var init_check = __esm(() => {
|
|
|
4257
4436
|
}
|
|
4258
4437
|
},
|
|
4259
4438
|
async run({ args }) {
|
|
4260
|
-
const platformDir =
|
|
4439
|
+
const platformDir = resolve9(args.platformDir ?? process.cwd());
|
|
4261
4440
|
const outArg = args.out;
|
|
4262
4441
|
const dbPath = resolveDbPath(platformDir, outArg);
|
|
4263
4442
|
if (outArg)
|
|
@@ -4287,6 +4466,8 @@ var init_check = __esm(() => {
|
|
|
4287
4466
|
diagnostics2.push(...unsourcedChanges(superseded, provenance));
|
|
4288
4467
|
}
|
|
4289
4468
|
diagnostics2.push(...await statementGrammarDiagnostics(platformDir));
|
|
4469
|
+
diagnostics2.push(...await brokenFileRefDiagnostics(platformDir));
|
|
4470
|
+
diagnostics2.push(...await supersedesPointerDiagnostics(platformDir));
|
|
4290
4471
|
const glossaryDrift = glossaryDriftDiagnostic(platformDir);
|
|
4291
4472
|
if (glossaryDrift !== null)
|
|
4292
4473
|
diagnostics2.push(glossaryDrift);
|
|
@@ -4297,7 +4478,7 @@ var init_check = __esm(() => {
|
|
|
4297
4478
|
requireOwnerApproval: args.requireOwnerApproval
|
|
4298
4479
|
}, diagnostics2, results);
|
|
4299
4480
|
if (!args.base && gitRefResolves(platformDir, "HEAD")) {
|
|
4300
|
-
const change = await collectChangeReqs(
|
|
4481
|
+
const change = await collectChangeReqs(join13(platformDir, "spec-engine"));
|
|
4301
4482
|
const baseParseSink = [];
|
|
4302
4483
|
const base = collectBaseReqs(platformDir, "HEAD", baseParseSink);
|
|
4303
4484
|
diagnostics2.push(...requirementRemoved(base.reqs, change.reqs, (id) => base.relPathById.get(id) ?? null));
|
|
@@ -4324,8 +4505,8 @@ __export(exports_domain, {
|
|
|
4324
4505
|
domainListCommand: () => domainListCommand,
|
|
4325
4506
|
domainCommand: () => domainCommand
|
|
4326
4507
|
});
|
|
4327
|
-
import { existsSync as
|
|
4328
|
-
import { dirname as dirname8, join as
|
|
4508
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
4509
|
+
import { dirname as dirname8, join as join14, resolve as resolve10 } from "path";
|
|
4329
4510
|
import { defineCommand as defineCommand5 } from "citty";
|
|
4330
4511
|
var domainNewCommand, domainListCommand, domainCommand;
|
|
4331
4512
|
var init_domain2 = __esm(() => {
|
|
@@ -4353,7 +4534,7 @@ var init_domain2 = __esm(() => {
|
|
|
4353
4534
|
},
|
|
4354
4535
|
async run({ args }) {
|
|
4355
4536
|
const raw = args.name;
|
|
4356
|
-
const platformDir =
|
|
4537
|
+
const platformDir = resolve10(args.platformDir ?? process.cwd());
|
|
4357
4538
|
const key = normalizeDomainKey(raw);
|
|
4358
4539
|
if (key !== raw) {
|
|
4359
4540
|
console.log(`spec domain: normalized "${raw}" \u2192 ${key}`);
|
|
@@ -4363,21 +4544,21 @@ var init_domain2 = __esm(() => {
|
|
|
4363
4544
|
process.exit(EXIT.USAGE);
|
|
4364
4545
|
return;
|
|
4365
4546
|
}
|
|
4366
|
-
const dest =
|
|
4367
|
-
const resolvedDest =
|
|
4368
|
-
const resolvedRoot =
|
|
4547
|
+
const dest = join14(platformDir, "spec-engine", key, "SPEC.json");
|
|
4548
|
+
const resolvedDest = resolve10(dest);
|
|
4549
|
+
const resolvedRoot = resolve10(platformDir);
|
|
4369
4550
|
if (!(resolvedDest === resolvedRoot || resolvedDest.startsWith(`${resolvedRoot}/`))) {
|
|
4370
4551
|
console.error(`spec domain new: refusing to write outside platformDir (${resolvedDest})`);
|
|
4371
4552
|
process.exit(EXIT.USAGE);
|
|
4372
4553
|
return;
|
|
4373
4554
|
}
|
|
4374
|
-
if (
|
|
4555
|
+
if (existsSync9(dest)) {
|
|
4375
4556
|
console.error(`refusing to overwrite ${dest}`);
|
|
4376
4557
|
process.exit(EXIT.USAGE);
|
|
4377
4558
|
return;
|
|
4378
4559
|
}
|
|
4379
|
-
const mdSibling =
|
|
4380
|
-
if (
|
|
4560
|
+
const mdSibling = join14(dirname8(dest), "SPEC.md");
|
|
4561
|
+
if (existsSync9(mdSibling)) {
|
|
4381
4562
|
console.error(`refusing to create ${dest}: domain ${key} already exists as spec-engine/${key}/SPEC.md ` + "\u2014 migrate the SPEC.md first (a fresh empty SPEC.json would shadow it on the next index)");
|
|
4382
4563
|
process.exit(EXIT.USAGE);
|
|
4383
4564
|
return;
|
|
@@ -4414,7 +4595,7 @@ var init_domain2 = __esm(() => {
|
|
|
4414
4595
|
}
|
|
4415
4596
|
},
|
|
4416
4597
|
async run({ args }) {
|
|
4417
|
-
const platformDir =
|
|
4598
|
+
const platformDir = resolve10(args.platformDir ?? process.cwd());
|
|
4418
4599
|
try {
|
|
4419
4600
|
assertSpecPlatform(platformDir);
|
|
4420
4601
|
} catch (e) {
|
|
@@ -4508,7 +4689,7 @@ var exports_map = {};
|
|
|
4508
4689
|
__export(exports_map, {
|
|
4509
4690
|
mapCommand: () => mapCommand
|
|
4510
4691
|
});
|
|
4511
|
-
import { resolve as
|
|
4692
|
+
import { resolve as resolve11 } from "path";
|
|
4512
4693
|
import { defineCommand as defineCommand6 } from "citty";
|
|
4513
4694
|
var mapCommand;
|
|
4514
4695
|
var init_map = __esm(() => {
|
|
@@ -4545,7 +4726,7 @@ var init_map = __esm(() => {
|
|
|
4545
4726
|
}
|
|
4546
4727
|
},
|
|
4547
4728
|
async run({ args }) {
|
|
4548
|
-
const platformDir =
|
|
4729
|
+
const platformDir = resolve11(args.platformDir ?? process.cwd());
|
|
4549
4730
|
const outArg = args.out;
|
|
4550
4731
|
const dbPath = resolveDbPath(platformDir, outArg);
|
|
4551
4732
|
if (outArg)
|
|
@@ -4595,7 +4776,7 @@ var exports_propagation = {};
|
|
|
4595
4776
|
__export(exports_propagation, {
|
|
4596
4777
|
propagationCommand: () => propagationCommand
|
|
4597
4778
|
});
|
|
4598
|
-
import { resolve as
|
|
4779
|
+
import { resolve as resolve12 } from "path";
|
|
4599
4780
|
import { defineCommand as defineCommand7 } from "citty";
|
|
4600
4781
|
var propagationCommand;
|
|
4601
4782
|
var init_propagation2 = __esm(() => {
|
|
@@ -4643,7 +4824,7 @@ var init_propagation2 = __esm(() => {
|
|
|
4643
4824
|
process.exit(EXIT.USAGE);
|
|
4644
4825
|
return;
|
|
4645
4826
|
}
|
|
4646
|
-
const platformDir =
|
|
4827
|
+
const platformDir = resolve12(args.platformDir ?? process.cwd());
|
|
4647
4828
|
const outArg = args.out;
|
|
4648
4829
|
const dbPath = resolveDbPath(platformDir, outArg);
|
|
4649
4830
|
if (outArg)
|
|
@@ -4717,7 +4898,7 @@ var exports_query = {};
|
|
|
4717
4898
|
__export(exports_query, {
|
|
4718
4899
|
queryCommand: () => queryCommand
|
|
4719
4900
|
});
|
|
4720
|
-
import { resolve as
|
|
4901
|
+
import { resolve as resolve13 } from "path";
|
|
4721
4902
|
import { defineCommand as defineCommand8 } from "citty";
|
|
4722
4903
|
var queryCommand;
|
|
4723
4904
|
var init_query = __esm(() => {
|
|
@@ -4783,7 +4964,7 @@ var init_query = __esm(() => {
|
|
|
4783
4964
|
process.exit(EXIT.USAGE);
|
|
4784
4965
|
return;
|
|
4785
4966
|
}
|
|
4786
|
-
const platformDir =
|
|
4967
|
+
const platformDir = resolve13(args.platformDir ?? process.cwd());
|
|
4787
4968
|
const outArg = args.out;
|
|
4788
4969
|
const dbPath = resolveDbPath(platformDir, outArg);
|
|
4789
4970
|
if (outArg)
|
|
@@ -4872,7 +5053,7 @@ var exports_relations = {};
|
|
|
4872
5053
|
__export(exports_relations, {
|
|
4873
5054
|
relationsCommand: () => relationsCommand
|
|
4874
5055
|
});
|
|
4875
|
-
import { resolve as
|
|
5056
|
+
import { resolve as resolve14 } from "path";
|
|
4876
5057
|
import { defineCommand as defineCommand9 } from "citty";
|
|
4877
5058
|
function formatNoRelations(platformDir) {
|
|
4878
5059
|
return [
|
|
@@ -4915,7 +5096,7 @@ var init_relations = __esm(() => {
|
|
|
4915
5096
|
}
|
|
4916
5097
|
},
|
|
4917
5098
|
async run({ args }) {
|
|
4918
|
-
const platformDir =
|
|
5099
|
+
const platformDir = resolve14(args.platformDir ?? process.cwd());
|
|
4919
5100
|
const outArg = args.out;
|
|
4920
5101
|
const dbPath = resolveDbPath(platformDir, outArg);
|
|
4921
5102
|
if (outArg)
|
|
@@ -5051,7 +5232,7 @@ var init_adapter = () => {};
|
|
|
5051
5232
|
|
|
5052
5233
|
// ../tracker/src/cache.ts
|
|
5053
5234
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
5054
|
-
import { dirname as dirname9, join as
|
|
5235
|
+
import { dirname as dirname9, join as join15 } from "path";
|
|
5055
5236
|
function isFresh(entry, now, ttlMs = CACHE_TTL_MS) {
|
|
5056
5237
|
const t = Date.parse(entry.fetched_at);
|
|
5057
5238
|
if (Number.isNaN(t))
|
|
@@ -5065,7 +5246,7 @@ function isValidEntry(e) {
|
|
|
5065
5246
|
return typeof r.title === "string" && typeof r.status === "string" && typeof r.url === "string" && typeof r.fetched_at === "string";
|
|
5066
5247
|
}
|
|
5067
5248
|
function sidecarPath(platformDir) {
|
|
5068
|
-
return
|
|
5249
|
+
return join15(platformDir, ".spec-engine", SIDECAR_FILE);
|
|
5069
5250
|
}
|
|
5070
5251
|
async function readCache(platformDir) {
|
|
5071
5252
|
try {
|
|
@@ -5253,7 +5434,7 @@ __export(exports_provenance, {
|
|
|
5253
5434
|
provenanceCommand: () => provenanceCommand
|
|
5254
5435
|
});
|
|
5255
5436
|
import { statSync as statSync5 } from "fs";
|
|
5256
|
-
import { resolve as
|
|
5437
|
+
import { resolve as resolve15 } from "path";
|
|
5257
5438
|
import { defineCommand as defineCommand10 } from "citty";
|
|
5258
5439
|
function formatNoProvenance(platformDir) {
|
|
5259
5440
|
return [
|
|
@@ -5314,7 +5495,7 @@ var init_provenance = __esm(() => {
|
|
|
5314
5495
|
if (issueId && platformArg === undefined) {
|
|
5315
5496
|
let isDir = false;
|
|
5316
5497
|
try {
|
|
5317
|
-
isDir = statSync5(
|
|
5498
|
+
isDir = statSync5(resolve15(issueId)).isDirectory();
|
|
5318
5499
|
} catch {
|
|
5319
5500
|
isDir = false;
|
|
5320
5501
|
}
|
|
@@ -5323,7 +5504,7 @@ var init_provenance = __esm(() => {
|
|
|
5323
5504
|
issueId = "";
|
|
5324
5505
|
}
|
|
5325
5506
|
}
|
|
5326
|
-
const platformDir =
|
|
5507
|
+
const platformDir = resolve15(platformArg ?? process.cwd());
|
|
5327
5508
|
const outArg = args.out;
|
|
5328
5509
|
const dbPath = resolveDbPath(platformDir, outArg);
|
|
5329
5510
|
if (outArg)
|
|
@@ -5348,35 +5529,11 @@ var init_provenance = __esm(() => {
|
|
|
5348
5529
|
});
|
|
5349
5530
|
});
|
|
5350
5531
|
|
|
5351
|
-
// src/authoring/filerefs.ts
|
|
5352
|
-
import { existsSync as existsSync9 } from "fs";
|
|
5353
|
-
import { resolve as resolve15 } from "path";
|
|
5354
|
-
function extractRefsFromText(s) {
|
|
5355
|
-
const out = [];
|
|
5356
|
-
for (const m of s.matchAll(FILE_REF_RE)) {
|
|
5357
|
-
const candidate = m[1].replace(/\.+$/, "");
|
|
5358
|
-
if (candidate.includes("/"))
|
|
5359
|
-
out.push(candidate);
|
|
5360
|
-
}
|
|
5361
|
-
return out;
|
|
5362
|
-
}
|
|
5363
|
-
function resolveFileRef(platformDir, ref) {
|
|
5364
|
-
const root = resolve15(platformDir);
|
|
5365
|
-
const abs = resolve15(root, ref);
|
|
5366
|
-
if (!(abs === root || abs.startsWith(`${root}/`)))
|
|
5367
|
-
return false;
|
|
5368
|
-
return existsSync9(abs);
|
|
5369
|
-
}
|
|
5370
|
-
var FILE_REF_RE;
|
|
5371
|
-
var init_filerefs = __esm(() => {
|
|
5372
|
-
FILE_REF_RE = /(?<![^\s])@([A-Za-z0-9_][A-Za-z0-9_\-./]*)/g;
|
|
5373
|
-
});
|
|
5374
|
-
|
|
5375
5532
|
// src/authoring/grammar.ts
|
|
5376
|
-
import { join as
|
|
5533
|
+
import { join as join16 } from "path";
|
|
5377
5534
|
async function grammarConfig(platformDir, key) {
|
|
5378
5535
|
try {
|
|
5379
|
-
const doc = JSON.parse(await Bun.file(
|
|
5536
|
+
const doc = JSON.parse(await Bun.file(join16(platformDir, "spec-engine", key, "SPEC.json")).text());
|
|
5380
5537
|
return {
|
|
5381
5538
|
grammar: doc.grammar === "ears" ? "ears" : "freeform",
|
|
5382
5539
|
severity: doc.grammarSeverity === "error" ? "error" : "warning"
|
|
@@ -5419,7 +5576,7 @@ __export(exports_req, {
|
|
|
5419
5576
|
appendEntry: () => appendEntry
|
|
5420
5577
|
});
|
|
5421
5578
|
import { existsSync as existsSync10 } from "fs";
|
|
5422
|
-
import { join as
|
|
5579
|
+
import { join as join17, resolve as resolve16 } from "path";
|
|
5423
5580
|
import { createInterface as createInterface2 } from "readline";
|
|
5424
5581
|
import { defineCommand as defineCommand11 } from "citty";
|
|
5425
5582
|
function resolveDomainKey(input, platformDir) {
|
|
@@ -5448,13 +5605,11 @@ async function authorFromFieldFlags(platformDir, key, nextId, textFlag, opts) {
|
|
|
5448
5605
|
}
|
|
5449
5606
|
await printResolvedCharter(platformDir, key);
|
|
5450
5607
|
const why = (opts.why ?? "").trim();
|
|
5451
|
-
const binds = (opts.binds ?? "").trim();
|
|
5452
5608
|
const lives = (opts.lives ?? "").trim();
|
|
5453
|
-
warnUnresolvableRefs(platformDir, [requirement, why,
|
|
5609
|
+
warnUnresolvableRefs(platformDir, [requirement, why, lives]);
|
|
5454
5610
|
const relFile = await appendEntry(platformDir, key, nextId, {
|
|
5455
5611
|
requirement,
|
|
5456
5612
|
why,
|
|
5457
|
-
binds,
|
|
5458
5613
|
lives,
|
|
5459
5614
|
issue: (opts.issue ?? "").trim() || undefined
|
|
5460
5615
|
});
|
|
@@ -5484,7 +5639,7 @@ function warnUnresolvableRefs(platformDir, fieldValues) {
|
|
|
5484
5639
|
}
|
|
5485
5640
|
async function appendEntry(platformDir, key, id, fields) {
|
|
5486
5641
|
const relFile = `spec-engine/${key}/SPEC.json`;
|
|
5487
|
-
const specPath =
|
|
5642
|
+
const specPath = join17(platformDir, "spec-engine", key, "SPEC.json");
|
|
5488
5643
|
await enforceStatementGrammar(platformDir, key, fields.requirement, "spec req");
|
|
5489
5644
|
const domain2 = existsSync10(specPath) ? JSON.parse(await Bun.file(specPath).text()) : null;
|
|
5490
5645
|
if (domain2 === null) {
|
|
@@ -5559,10 +5714,6 @@ var init_req = __esm(() => {
|
|
|
5559
5714
|
type: "string",
|
|
5560
5715
|
description: "Why it matters field (only with --text)"
|
|
5561
5716
|
},
|
|
5562
|
-
binds: {
|
|
5563
|
-
type: "string",
|
|
5564
|
-
description: "Binds field (only with --text)"
|
|
5565
|
-
},
|
|
5566
5717
|
lives: {
|
|
5567
5718
|
type: "string",
|
|
5568
5719
|
description: "Lives in field (only with --text)"
|
|
@@ -5583,16 +5734,15 @@ var init_req = __esm(() => {
|
|
|
5583
5734
|
const key = resolveDomainKey(input, platformDir);
|
|
5584
5735
|
const nextId = await nextRequirementId(platformDir, key);
|
|
5585
5736
|
const textFlag = args.text;
|
|
5586
|
-
const hasFieldFlags = typeof args.why === "string" || typeof args.
|
|
5737
|
+
const hasFieldFlags = typeof args.why === "string" || typeof args.lives === "string" || typeof args.issue === "string";
|
|
5587
5738
|
if (textFlag === undefined && hasFieldFlags) {
|
|
5588
|
-
console.error("spec req: --why/--
|
|
5739
|
+
console.error("spec req: --why/--lives/--issue require --text (the Requirement field is mandatory)");
|
|
5589
5740
|
process.exit(EXIT.USAGE);
|
|
5590
5741
|
return;
|
|
5591
5742
|
}
|
|
5592
5743
|
if (textFlag !== undefined) {
|
|
5593
5744
|
await authorFromFieldFlags(platformDir, key, nextId, textFlag, {
|
|
5594
5745
|
why: args.why,
|
|
5595
|
-
binds: args.binds,
|
|
5596
5746
|
lives: args.lives,
|
|
5597
5747
|
issue: args.issue,
|
|
5598
5748
|
json: Boolean(args.json)
|
|
@@ -5616,13 +5766,11 @@ var init_req = __esm(() => {
|
|
|
5616
5766
|
return;
|
|
5617
5767
|
}
|
|
5618
5768
|
const why = (await askLine("Why it matters: ")).trim();
|
|
5619
|
-
const binds = (await askLine("Binds: ")).trim();
|
|
5620
5769
|
const lives = (await askLine("Lives in: ")).trim();
|
|
5621
|
-
warnUnresolvableRefs(platformDir, [requirement, why,
|
|
5770
|
+
warnUnresolvableRefs(platformDir, [requirement, why, lives]);
|
|
5622
5771
|
const relFile = await appendEntry(platformDir, key, nextId, {
|
|
5623
5772
|
requirement,
|
|
5624
5773
|
why,
|
|
5625
|
-
binds,
|
|
5626
5774
|
lives
|
|
5627
5775
|
});
|
|
5628
5776
|
console.log(`appended ${nextId} to ${relFile}`);
|
|
@@ -5644,7 +5792,7 @@ __export(exports_term, {
|
|
|
5644
5792
|
termCommand: () => termCommand
|
|
5645
5793
|
});
|
|
5646
5794
|
import { existsSync as existsSync11 } from "fs";
|
|
5647
|
-
import { join as
|
|
5795
|
+
import { join as join18, resolve as resolve17 } from "path";
|
|
5648
5796
|
import { defineCommand as defineCommand12 } from "citty";
|
|
5649
5797
|
function parseAliases(raw) {
|
|
5650
5798
|
if (typeof raw !== "string" || raw.trim() === "")
|
|
@@ -5661,7 +5809,7 @@ function resolvePlatform2(platformDirRaw) {
|
|
|
5661
5809
|
return platformDir;
|
|
5662
5810
|
}
|
|
5663
5811
|
async function readTermEnvelope(platformDir, cmd) {
|
|
5664
|
-
const specPath =
|
|
5812
|
+
const specPath = join18(platformDir, "spec-engine", TERM_KEY2, "SPEC.json");
|
|
5665
5813
|
if (!existsSync11(specPath)) {
|
|
5666
5814
|
console.error(`spec ${cmd}: no TERM domain (expected spec-engine/TERM/SPEC.json under ${platformDir})`);
|
|
5667
5815
|
process.exit(EXIT.USAGE);
|
|
@@ -5670,7 +5818,7 @@ async function readTermEnvelope(platformDir, cmd) {
|
|
|
5670
5818
|
}
|
|
5671
5819
|
async function appendTermEntry(platformDir, id, fields) {
|
|
5672
5820
|
const relFile = `spec-engine/${TERM_KEY2}/SPEC.json`;
|
|
5673
|
-
const specPath =
|
|
5821
|
+
const specPath = join18(platformDir, "spec-engine", TERM_KEY2, "SPEC.json");
|
|
5674
5822
|
const domain2 = await readTermEnvelope(platformDir, "term");
|
|
5675
5823
|
const requirements = Array.isArray(domain2.requirements) ? domain2.requirements : [];
|
|
5676
5824
|
const entry = {
|
|
@@ -5734,7 +5882,7 @@ async function authorOrQuery(opts) {
|
|
|
5734
5882
|
}
|
|
5735
5883
|
async function listTerms(platformDirRaw, json) {
|
|
5736
5884
|
const platformDir = resolvePlatform2(platformDirRaw);
|
|
5737
|
-
const specPath =
|
|
5885
|
+
const specPath = join18(platformDir, "spec-engine", TERM_KEY2, "SPEC.json");
|
|
5738
5886
|
const domain2 = existsSync11(specPath) ? JSON.parse(await Bun.file(specPath).text()) : { requirements: [] };
|
|
5739
5887
|
const requirements = Array.isArray(domain2.requirements) ? domain2.requirements : [];
|
|
5740
5888
|
const rows = requirements.map((r) => ({
|
|
@@ -5766,7 +5914,7 @@ async function resolveReviseTarget(id, platformDirRaw, defRaw) {
|
|
|
5766
5914
|
process.exit(EXIT.USAGE);
|
|
5767
5915
|
}
|
|
5768
5916
|
const relFile = `spec-engine/${TERM_KEY2}/SPEC.json`;
|
|
5769
|
-
const specPath =
|
|
5917
|
+
const specPath = join18(platformDir, "spec-engine", TERM_KEY2, "SPEC.json");
|
|
5770
5918
|
const domain2 = await readTermEnvelope(platformDir, "term revise");
|
|
5771
5919
|
const requirements = Array.isArray(domain2.requirements) ? domain2.requirements : [];
|
|
5772
5920
|
const req = requirements.find((r) => r?.id === id);
|
|
@@ -5842,7 +5990,7 @@ async function resolveConfirmTarget(platformDir, termId) {
|
|
|
5842
5990
|
async function locateCitation(platformDir, reqId, termId) {
|
|
5843
5991
|
const citKey = reqId.slice(0, reqId.indexOf("-"));
|
|
5844
5992
|
const relFile = `spec-engine/${citKey}/SPEC.json`;
|
|
5845
|
-
const specPath =
|
|
5993
|
+
const specPath = join18(platformDir, "spec-engine", citKey, "SPEC.json");
|
|
5846
5994
|
if (!existsSync11(specPath)) {
|
|
5847
5995
|
console.error(`spec term confirm: no domain ${citKey} (expected ${relFile} under ${platformDir})`);
|
|
5848
5996
|
process.exit(EXIT.USAGE);
|
|
@@ -6123,7 +6271,7 @@ __export(exports_supersede, {
|
|
|
6123
6271
|
supersedeCommand: () => supersedeCommand
|
|
6124
6272
|
});
|
|
6125
6273
|
import { existsSync as existsSync12 } from "fs";
|
|
6126
|
-
import { join as
|
|
6274
|
+
import { join as join19, resolve as resolve18 } from "path";
|
|
6127
6275
|
import { defineCommand as defineCommand13 } from "citty";
|
|
6128
6276
|
async function resolveSupersedeTarget(id, platformDir) {
|
|
6129
6277
|
if (!ID_RE.test(id)) {
|
|
@@ -6137,7 +6285,7 @@ async function resolveSupersedeTarget(id, platformDir) {
|
|
|
6137
6285
|
}
|
|
6138
6286
|
const key = id.slice(0, id.indexOf("-"));
|
|
6139
6287
|
const relFile = `spec-engine/${key}/SPEC.json`;
|
|
6140
|
-
const specPath =
|
|
6288
|
+
const specPath = join19(platformDir, "spec-engine", key, "SPEC.json");
|
|
6141
6289
|
if (!existsSync12(specPath)) {
|
|
6142
6290
|
console.error(`spec supersede: no domain ${key} (expected ${relFile} under ${platformDir})`);
|
|
6143
6291
|
process.exit(EXIT.USAGE);
|
|
@@ -6186,8 +6334,7 @@ function resolveSuccessorFields(args, req) {
|
|
|
6186
6334
|
const predLives = Array.isArray(req.livesIn) && req.livesIn.length > 0 ? String(req.livesIn[0]) : "";
|
|
6187
6335
|
const why = (args.why ?? predWhy).trim();
|
|
6188
6336
|
const lives = (args.lives ?? predLives).trim();
|
|
6189
|
-
|
|
6190
|
-
return { why, lives, binds };
|
|
6337
|
+
return { why, lives };
|
|
6191
6338
|
}
|
|
6192
6339
|
function resolveSuccessorTermFields(args, req) {
|
|
6193
6340
|
const predTerm = typeof req.term === "string" ? req.term : undefined;
|
|
@@ -6217,8 +6364,7 @@ function applySupersedeEdit(target, newId, requirement, why, lives, noBump, term
|
|
|
6217
6364
|
supersededBy: null,
|
|
6218
6365
|
relates: [],
|
|
6219
6366
|
livesIn: lives === "" ? [] : [lives],
|
|
6220
|
-
issues: issue ? [{ role: "created", id: issue }] : []
|
|
6221
|
-
changedAtVersion: 1
|
|
6367
|
+
issues: issue ? [{ role: "created", id: issue }] : []
|
|
6222
6368
|
};
|
|
6223
6369
|
if (termFields !== null) {
|
|
6224
6370
|
if (termFields.term !== undefined)
|
|
@@ -6227,20 +6373,25 @@ function applySupersedeEdit(target, newId, requirement, why, lives, noBump, term
|
|
|
6227
6373
|
}
|
|
6228
6374
|
requirements.push(successor);
|
|
6229
6375
|
domain2.requirements = requirements;
|
|
6230
|
-
|
|
6231
|
-
if (domain2.key === "TERM") {
|
|
6232
|
-
const currentVersion = typeof domain2.specVersion === "number" ? domain2.specVersion : 1;
|
|
6233
|
-
reportedVersion = noBump ? null : currentVersion + 1;
|
|
6234
|
-
if (reportedVersion !== null)
|
|
6235
|
-
domain2.specVersion = reportedVersion;
|
|
6236
|
-
req.supersededAtVersion = reportedVersion ?? currentVersion;
|
|
6237
|
-
} else {
|
|
6238
|
-
reportedVersion = deriveDomainVersion(requirements);
|
|
6239
|
-
req.supersededAtVersion = reportedVersion;
|
|
6240
|
-
}
|
|
6376
|
+
const reportedVersion = applyVersionStage(target, successor, noBump);
|
|
6241
6377
|
domain2.updated = localToday();
|
|
6242
6378
|
return reportedVersion;
|
|
6243
6379
|
}
|
|
6380
|
+
function applyVersionStage(target, successor, noBump) {
|
|
6381
|
+
const { domain: domain2, requirements, req } = target;
|
|
6382
|
+
if (domain2.key !== "TERM") {
|
|
6383
|
+
const derived = deriveDomainVersion(requirements);
|
|
6384
|
+
req.supersededAtVersion = derived;
|
|
6385
|
+
return derived;
|
|
6386
|
+
}
|
|
6387
|
+
const currentVersion = typeof domain2.specVersion === "number" ? domain2.specVersion : 1;
|
|
6388
|
+
const reported = noBump ? null : currentVersion + 1;
|
|
6389
|
+
if (reported !== null)
|
|
6390
|
+
domain2.specVersion = reported;
|
|
6391
|
+
req.supersededAtVersion = reported ?? currentVersion;
|
|
6392
|
+
successor.changedAtVersion = reported ?? currentVersion;
|
|
6393
|
+
return reported;
|
|
6394
|
+
}
|
|
6244
6395
|
async function reindexAndCollectRetag(platformDir, id) {
|
|
6245
6396
|
const tags = await reindexAndListTags(platformDir, id);
|
|
6246
6397
|
return tags.map(({ req_id, repo, file, line, kind, level }) => ({
|
|
@@ -6287,10 +6438,6 @@ var init_supersede = __esm(() => {
|
|
|
6287
6438
|
type: "string",
|
|
6288
6439
|
description: "Successor's Why it matters (default: copied from the old entry)"
|
|
6289
6440
|
},
|
|
6290
|
-
binds: {
|
|
6291
|
-
type: "string",
|
|
6292
|
-
description: "Binds value (validated for @-refs; not persisted in JSON \u2014 STOR-01)"
|
|
6293
|
-
},
|
|
6294
6441
|
lives: {
|
|
6295
6442
|
type: "string",
|
|
6296
6443
|
description: "Successor's Lives in (default: copied from the old entry)"
|
|
@@ -6322,8 +6469,8 @@ var init_supersede = __esm(() => {
|
|
|
6322
6469
|
const target = await resolveSupersedeTarget(id, platformDir);
|
|
6323
6470
|
const requirement = await resolveSuccessorText(args, id);
|
|
6324
6471
|
await enforceStatementGrammar(platformDir, target.key, requirement, "spec supersede");
|
|
6325
|
-
const { why, lives
|
|
6326
|
-
warnUnresolvableRefs(platformDir, [requirement, why,
|
|
6472
|
+
const { why, lives } = resolveSuccessorFields(args, target.req);
|
|
6473
|
+
warnUnresolvableRefs(platformDir, [requirement, why, lives]);
|
|
6327
6474
|
const newId = await nextRequirementId(platformDir, target.key);
|
|
6328
6475
|
const termFields = target.key === "TERM" ? resolveSuccessorTermFields(args, target.req) : null;
|
|
6329
6476
|
const newVersion = applySupersedeEdit(target, newId, requirement, why, lives, Boolean(args.noBump), termFields, (args.issue ?? "").trim() || undefined);
|
|
@@ -6366,12 +6513,12 @@ __export(exports_deprecate, {
|
|
|
6366
6513
|
deprecateCommand: () => deprecateCommand
|
|
6367
6514
|
});
|
|
6368
6515
|
import { existsSync as existsSync13 } from "fs";
|
|
6369
|
-
import { join as
|
|
6516
|
+
import { join as join20, resolve as resolve19 } from "path";
|
|
6370
6517
|
import { defineCommand as defineCommand14 } from "citty";
|
|
6371
6518
|
async function resolveDeprecateTarget(platformDir, id) {
|
|
6372
6519
|
const key = id.slice(0, id.indexOf("-"));
|
|
6373
6520
|
const relFile = `spec-engine/${key}/SPEC.json`;
|
|
6374
|
-
const specPath =
|
|
6521
|
+
const specPath = join20(platformDir, "spec-engine", key, "SPEC.json");
|
|
6375
6522
|
if (!existsSync13(specPath)) {
|
|
6376
6523
|
console.error(`spec deprecate: no domain ${key} (expected ${relFile} under ${platformDir})`);
|
|
6377
6524
|
process.exit(EXIT.USAGE);
|
|
@@ -6481,11 +6628,11 @@ __export(exports_move, {
|
|
|
6481
6628
|
moveCommand: () => moveCommand
|
|
6482
6629
|
});
|
|
6483
6630
|
import { existsSync as existsSync14 } from "fs";
|
|
6484
|
-
import { join as
|
|
6631
|
+
import { join as join21, resolve as resolve20 } from "path";
|
|
6485
6632
|
import { defineCommand as defineCommand15 } from "citty";
|
|
6486
6633
|
async function loadDomainSide(platformDir, key) {
|
|
6487
6634
|
const relFile = `spec-engine/${key}/SPEC.json`;
|
|
6488
|
-
const specPath =
|
|
6635
|
+
const specPath = join21(platformDir, "spec-engine", key, "SPEC.json");
|
|
6489
6636
|
if (!existsSync14(specPath))
|
|
6490
6637
|
return null;
|
|
6491
6638
|
const domain2 = JSON.parse(await Bun.file(specPath).text());
|
|
@@ -6719,14 +6866,13 @@ __export(exports_amend, {
|
|
|
6719
6866
|
amendCommand: () => amendCommand
|
|
6720
6867
|
});
|
|
6721
6868
|
import { existsSync as existsSync15 } from "fs";
|
|
6722
|
-
import { join as
|
|
6869
|
+
import { join as join22, resolve as resolve21 } from "path";
|
|
6723
6870
|
import { defineCommand as defineCommand16 } from "citty";
|
|
6724
6871
|
function validateAmendFields(args) {
|
|
6725
6872
|
const hasText = typeof args.text === "string";
|
|
6726
6873
|
const hasIssue = typeof args.issue === "string" && args.issue.trim() !== "";
|
|
6727
6874
|
const hasWhy = typeof args.why === "string";
|
|
6728
6875
|
const hasLives = typeof args.lives === "string";
|
|
6729
|
-
const hasBinds = typeof args.binds === "string";
|
|
6730
6876
|
const hasTerm = typeof args.term === "string";
|
|
6731
6877
|
const hasAliases = typeof args.aliases === "string";
|
|
6732
6878
|
if (!hasText && !hasWhy && !hasLives && !hasTerm && !hasAliases && !hasIssue) {
|
|
@@ -6741,12 +6887,12 @@ function validateAmendFields(args) {
|
|
|
6741
6887
|
console.error("spec amend: --term must be a non-empty headword");
|
|
6742
6888
|
process.exit(EXIT.USAGE);
|
|
6743
6889
|
}
|
|
6744
|
-
return { hasText, hasWhy, hasLives,
|
|
6890
|
+
return { hasText, hasWhy, hasLives, hasTerm, hasAliases, hasIssue };
|
|
6745
6891
|
}
|
|
6746
6892
|
async function locateAmendEntry(platformDir, id) {
|
|
6747
6893
|
const key = id.slice(0, id.indexOf("-"));
|
|
6748
6894
|
const relFile = `spec-engine/${key}/SPEC.json`;
|
|
6749
|
-
const specPath =
|
|
6895
|
+
const specPath = join22(platformDir, "spec-engine", key, "SPEC.json");
|
|
6750
6896
|
if (!existsSync15(specPath)) {
|
|
6751
6897
|
console.error(`spec amend: no domain ${key} (expected ${relFile} under ${platformDir})`);
|
|
6752
6898
|
process.exit(EXIT.USAGE);
|
|
@@ -6800,9 +6946,6 @@ function applyAmendMutations(req, args, fields) {
|
|
|
6800
6946
|
fieldsChanged.push("lives");
|
|
6801
6947
|
refValues.push(v);
|
|
6802
6948
|
}
|
|
6803
|
-
if (fields.hasBinds) {
|
|
6804
|
-
refValues.push(args.binds.trim());
|
|
6805
|
-
}
|
|
6806
6949
|
if (fields.hasTerm) {
|
|
6807
6950
|
const v = args.term.trim();
|
|
6808
6951
|
req.term = v;
|
|
@@ -6849,10 +6992,6 @@ var init_amend = __esm(() => {
|
|
|
6849
6992
|
},
|
|
6850
6993
|
text: { type: "string", description: "New Requirement (statement) field value" },
|
|
6851
6994
|
why: { type: "string", description: "New Why it matters field value" },
|
|
6852
|
-
binds: {
|
|
6853
|
-
type: "string",
|
|
6854
|
-
description: "Binds value (validated for @-refs; not persisted in JSON \u2014 STOR-01)"
|
|
6855
|
-
},
|
|
6856
6995
|
lives: { type: "string", description: "New Lives in (livesIn) field value" },
|
|
6857
6996
|
issue: {
|
|
6858
6997
|
type: "string",
|
|
@@ -7121,7 +7260,7 @@ __export(exports_resolve, {
|
|
|
7121
7260
|
resolveCommand: () => resolveCommand
|
|
7122
7261
|
});
|
|
7123
7262
|
import { statSync as statSync6 } from "fs";
|
|
7124
|
-
import { join as
|
|
7263
|
+
import { join as join23, relative, resolve as resolve23, sep as sep3 } from "path";
|
|
7125
7264
|
import { defineCommand as defineCommand18 } from "citty";
|
|
7126
7265
|
function classifyArg(a) {
|
|
7127
7266
|
if (a.startsWith("--")) {
|
|
@@ -7160,7 +7299,7 @@ function splitFilesAndPlatformDir(positionals, fallbackPlatformDir) {
|
|
|
7160
7299
|
const resolved = resolve23(last);
|
|
7161
7300
|
if (statSync6(resolved).isDirectory()) {
|
|
7162
7301
|
try {
|
|
7163
|
-
if (statSync6(
|
|
7302
|
+
if (statSync6(join23(resolved, "spec-engine")).isDirectory()) {
|
|
7164
7303
|
lastIsPlatformDir = true;
|
|
7165
7304
|
} else {
|
|
7166
7305
|
lastIsDirWithoutSpecEngine = true;
|
|
@@ -7456,7 +7595,7 @@ __export(exports_gate, {
|
|
|
7456
7595
|
gateCommand: () => gateCommand
|
|
7457
7596
|
});
|
|
7458
7597
|
import { existsSync as existsSync16, mkdirSync as mkdirSync7 } from "fs";
|
|
7459
|
-
import { dirname as dirname10, join as
|
|
7598
|
+
import { dirname as dirname10, join as join24, resolve as resolve24 } from "path";
|
|
7460
7599
|
import { defineCommand as defineCommand19 } from "citty";
|
|
7461
7600
|
function isEpipe(e) {
|
|
7462
7601
|
if (e === null || typeof e !== "object")
|
|
@@ -7472,7 +7611,7 @@ function resolveGateArgs(args) {
|
|
|
7472
7611
|
process.exit(EXIT.USAGE);
|
|
7473
7612
|
}
|
|
7474
7613
|
const platformDir = resolve24(args.platformDir ?? process.cwd());
|
|
7475
|
-
const hasSpecEngineDir = existsSync16(
|
|
7614
|
+
const hasSpecEngineDir = existsSync16(join24(platformDir, "spec-engine"));
|
|
7476
7615
|
if (!hasSpecEngineDir) {
|
|
7477
7616
|
console.error(formatNotASpecPlatform(platformDir));
|
|
7478
7617
|
process.exit(EXIT.USAGE);
|
|
@@ -7598,7 +7737,7 @@ var init_gate = __esm(() => {
|
|
|
7598
7737
|
});
|
|
7599
7738
|
|
|
7600
7739
|
// src/guard/collect.ts
|
|
7601
|
-
import { join as
|
|
7740
|
+
import { join as join25 } from "path";
|
|
7602
7741
|
function isIgnored(path) {
|
|
7603
7742
|
return isPathIgnored(path);
|
|
7604
7743
|
}
|
|
@@ -7655,7 +7794,7 @@ async function collectWorktreeReqs(canonicalDir) {
|
|
|
7655
7794
|
const activeIds = new Set;
|
|
7656
7795
|
const supersedesTargets = new Set;
|
|
7657
7796
|
for (const rel of await findDomainJsonFiles(canonicalDir)) {
|
|
7658
|
-
const text = await Bun.file(
|
|
7797
|
+
const text = await Bun.file(join25(canonicalDir, rel)).text();
|
|
7659
7798
|
for (const r of parseReqs(text)) {
|
|
7660
7799
|
ids.add(r.id);
|
|
7661
7800
|
if (r.status.toLowerCase() === "active")
|
|
@@ -7683,7 +7822,7 @@ async function collectFacts(platformDir, ref, storage2) {
|
|
|
7683
7822
|
const deletedSpecFiles = changed.filter((e) => e.status === "D" && isSpecFile(e.path)).map((e) => e.path);
|
|
7684
7823
|
const base = collectBaseReqs2(platformDir, ref, specPaths);
|
|
7685
7824
|
const baseTags = collectBaseTags(platformDir, ref, codePaths);
|
|
7686
|
-
const worktree = await collectWorktreeReqs(
|
|
7825
|
+
const worktree = await collectWorktreeReqs(join25(platformDir, CANONICAL_SPECS_DIR));
|
|
7687
7826
|
const counts = tallyWorktreeTags(storage2);
|
|
7688
7827
|
return {
|
|
7689
7828
|
baseReqs: base.reqs,
|
|
@@ -10749,7 +10888,7 @@ var init_server = __esm(() => {
|
|
|
10749
10888
|
|
|
10750
10889
|
// src/server/api.ts
|
|
10751
10890
|
import { existsSync as existsSync17 } from "fs";
|
|
10752
|
-
import { join as
|
|
10891
|
+
import { join as join26 } from "path";
|
|
10753
10892
|
function hasTraversalSegment(p) {
|
|
10754
10893
|
return p.split(/[/\\]/).some((seg) => seg === "..");
|
|
10755
10894
|
}
|
|
@@ -10856,7 +10995,7 @@ function resolveCreateTarget(c, platformDir, body) {
|
|
|
10856
10995
|
return { ok: false, res: c.json({ error: "unknown domain key" }, 400) };
|
|
10857
10996
|
}
|
|
10858
10997
|
const relFile = `spec-engine/${key}/SPEC.json`;
|
|
10859
|
-
const specPath =
|
|
10998
|
+
const specPath = join26(platformDir, "spec-engine", key, "SPEC.json");
|
|
10860
10999
|
if (!existsSync17(specPath))
|
|
10861
11000
|
return { ok: false, res: c.json({ error: "not found" }, 404) };
|
|
10862
11001
|
return { ok: true, key, relFile, specPath };
|
|
@@ -10876,7 +11015,7 @@ function buildRequirement(body, id) {
|
|
|
10876
11015
|
}
|
|
10877
11016
|
async function locateRequirement(platformDir, id) {
|
|
10878
11017
|
for (const key of listDomainKeys(platformDir)) {
|
|
10879
|
-
const specPath =
|
|
11018
|
+
const specPath = join26(platformDir, "spec-engine", key, "SPEC.json");
|
|
10880
11019
|
const relFile = `spec-engine/${key}/SPEC.json`;
|
|
10881
11020
|
let domain2;
|
|
10882
11021
|
try {
|
|
@@ -11304,12 +11443,12 @@ __export(exports_docs, {
|
|
|
11304
11443
|
createDocsFetchHandler: () => createDocsFetchHandler
|
|
11305
11444
|
});
|
|
11306
11445
|
import { existsSync as existsSync19, statSync as statSync7 } from "fs";
|
|
11307
|
-
import { join as
|
|
11446
|
+
import { join as join27, normalize, sep as sep4 } from "path";
|
|
11308
11447
|
import { fileURLToPath } from "url";
|
|
11309
11448
|
import { defineCommand as defineCommand22 } from "citty";
|
|
11310
11449
|
function resolveDocsRootFrom(candidates) {
|
|
11311
11450
|
for (const candidate of candidates) {
|
|
11312
|
-
if (existsSync19(
|
|
11451
|
+
if (existsSync19(join27(candidate, "index.html")))
|
|
11313
11452
|
return candidate;
|
|
11314
11453
|
}
|
|
11315
11454
|
return null;
|
|
@@ -11333,16 +11472,16 @@ function createDocsFetchHandler(root) {
|
|
|
11333
11472
|
if (pathname.includes("\x00")) {
|
|
11334
11473
|
return new Response("Bad request", { status: 400 });
|
|
11335
11474
|
}
|
|
11336
|
-
const target = normalize(
|
|
11475
|
+
const target = normalize(join27(normalRoot, pathname));
|
|
11337
11476
|
if (target !== normalRoot && !target.startsWith(rootPrefix)) {
|
|
11338
11477
|
return new Response("Not found", { status: 404 });
|
|
11339
11478
|
}
|
|
11340
|
-
for (const candidate of [target,
|
|
11479
|
+
for (const candidate of [target, join27(target, "index.html"), `${target}.html`]) {
|
|
11341
11480
|
if (existsSync19(candidate) && statSync7(candidate).isFile()) {
|
|
11342
11481
|
return new Response(Bun.file(candidate));
|
|
11343
11482
|
}
|
|
11344
11483
|
}
|
|
11345
|
-
const notFoundPage =
|
|
11484
|
+
const notFoundPage = join27(normalRoot, "404.html");
|
|
11346
11485
|
if (existsSync19(notFoundPage)) {
|
|
11347
11486
|
return new Response(Bun.file(notFoundPage), { status: 404 });
|
|
11348
11487
|
}
|
|
@@ -11520,7 +11659,7 @@ import { defineCommand as defineCommand24, runMain, showUsage } from "citty";
|
|
|
11520
11659
|
// package.json
|
|
11521
11660
|
var package_default = {
|
|
11522
11661
|
name: "@spec-engine/spec-engine",
|
|
11523
|
-
version: "0.
|
|
11662
|
+
version: "0.3.0",
|
|
11524
11663
|
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).",
|
|
11525
11664
|
license: "MIT",
|
|
11526
11665
|
type: "module",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spec-engine/spec-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|