@spec-engine/spec-engine 0.7.0 → 0.7.2
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/404.html +4 -4
- package/dist/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js +1 -0
- package/dist/docs/_astro/Search.astro_astro_type_script_index_0_lang.Djc9ZKGJ.js +1 -0
- package/dist/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js +1 -0
- package/dist/docs/_astro/common.BPl3nku0.css +3 -0
- package/dist/docs/_astro/components.BeLFs6cp.css +1 -0
- package/dist/docs/_astro/page.cNngjUgL.js +1 -0
- package/dist/docs/_astro/print.ehPL0gv-.css +1 -0
- package/dist/docs/_astro/starlight-toc.CTCQXEge.js +1 -0
- package/dist/docs/_astro/ui-core.B94qsovR.js +2 -0
- package/dist/docs/agent-reference/index.html +23 -22
- package/dist/docs/architecture/index.html +423 -422
- package/dist/docs/checks/index.html +247 -246
- package/dist/docs/commands/index.html +127 -123
- package/dist/docs/format/index.html +92 -91
- package/dist/docs/getting-started/index.html +21 -20
- package/dist/docs/guides/adoption/index.html +13 -12
- package/dist/docs/guides/platform-setup/index.html +15 -14
- package/dist/docs/guides/single-repo/index.html +12 -11
- package/dist/docs/index.html +2 -152
- package/dist/docs/overview/index.html +8 -8
- package/dist/docs/pagefind/fragment/en_70e8a6f.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_907a67d.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_b81dc52.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_c8cc99c.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_de9d958.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_e616a50.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_f77a622.pf_fragment +0 -0
- package/dist/docs/pagefind/index/en_19cbfc6.pf_index +0 -0
- package/dist/docs/pagefind/index/en_4be71c4.pf_index +0 -0
- package/dist/docs/pagefind/pagefind-entry.json +1 -1
- package/dist/docs/pagefind/pagefind.en_f697eb692e.pf_meta +0 -0
- package/dist/docs/platform-mapping/index.html +162 -161
- package/dist/docs/tags/index.html +74 -73
- package/dist/impl.js +283 -32
- package/package.json +2 -2
- package/dist/docs/_astro/Code.CAimF0uL.css +0 -1
- package/dist/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.hwBsy0Mo.js +0 -1
- package/dist/docs/_astro/Search.astro_astro_type_script_index_0_lang.lq3t8uE2.js +0 -1
- package/dist/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.FuRcXuRY.js +0 -1
- package/dist/docs/_astro/common.DslAiFsT.css +0 -1
- package/dist/docs/_astro/page.B_tncCx8.js +0 -1
- package/dist/docs/_astro/print.DNXP8c50.css +0 -1
- package/dist/docs/_astro/ui-core.C8327L_n.js +0 -2
- package/dist/docs/pagefind/fragment/en_435dca1.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_4bdd17f.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_6ce6fae.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_775cc8c.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_a3cd8bf.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_b44542a.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_d652b3c.pf_fragment +0 -0
- package/dist/docs/pagefind/index/en_6b9dc89.pf_index +0 -0
- package/dist/docs/pagefind/index/en_c5bf3e1.pf_index +0 -0
- package/dist/docs/pagefind/pagefind.en_c6c8e941d5.pf_meta +0 -0
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.7.
|
|
23
|
+
version: "0.7.2",
|
|
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",
|
|
@@ -62,7 +62,7 @@ var init_package = __esm(() => {
|
|
|
62
62
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
63
63
|
"@spec-engine/platform-map": "0.5.0",
|
|
64
64
|
citty: "0.2.2",
|
|
65
|
-
hono: "4.
|
|
65
|
+
hono: "4.13.11",
|
|
66
66
|
zod: "3.25.76"
|
|
67
67
|
},
|
|
68
68
|
devDependencies: {
|
|
@@ -414,8 +414,25 @@ var init_config = __esm(() => {
|
|
|
414
414
|
});
|
|
415
415
|
|
|
416
416
|
// ../shared/src/ears.ts
|
|
417
|
-
function reject(problem) {
|
|
418
|
-
return { ok: false, problem, expected
|
|
417
|
+
function reject(problem, expected = SHAPE_EXPECTED) {
|
|
418
|
+
return { ok: false, problem, expected };
|
|
419
|
+
}
|
|
420
|
+
function onePromiseProblem(statement, response) {
|
|
421
|
+
if (/;\s/.test(response)) {
|
|
422
|
+
return 'joins a second clause with "; " after "shall" \u2014 split it into its own requirement';
|
|
423
|
+
}
|
|
424
|
+
if (/,\s+and\s+(?!shall\b)\S[^,;]*?\s+shall\b/i.test(response)) {
|
|
425
|
+
return 'joins a second clause with its own subject (", and <subject> shall") \u2014 split it into its own requirement';
|
|
426
|
+
}
|
|
427
|
+
const rationale = statement.match(RATIONALE_RE);
|
|
428
|
+
if (rationale) {
|
|
429
|
+
return `carries a rationale ("${rationale[1]}") \u2014 the reason belongs in the why field`;
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
function lint(clauses, statement) {
|
|
434
|
+
const problem = onePromiseProblem(statement, clauses.response);
|
|
435
|
+
return problem === null ? { ok: true, clauses } : reject(problem, ONE_PROMISE_EXPECTED);
|
|
419
436
|
}
|
|
420
437
|
function parseEarsStatement(raw) {
|
|
421
438
|
const statement = raw.trim();
|
|
@@ -430,15 +447,12 @@ function parseEarsStatement(raw) {
|
|
|
430
447
|
const m2 = statement.match(re);
|
|
431
448
|
if (m2) {
|
|
432
449
|
const [, condition = "", system = "", response = ""] = m2;
|
|
433
|
-
return {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
response: response.trim()
|
|
440
|
-
}
|
|
441
|
-
};
|
|
450
|
+
return lint({
|
|
451
|
+
pattern,
|
|
452
|
+
condition: condition.trim(),
|
|
453
|
+
system: system.trim().replace(/^the\s+/i, ""),
|
|
454
|
+
response: response.trim()
|
|
455
|
+
}, statement);
|
|
442
456
|
}
|
|
443
457
|
if (keyword === "If" && !/,\s*then\s/.test(statement)) {
|
|
444
458
|
return reject('starts with "If" but has no ", then" \u2014 the failure clause never hands over to the system');
|
|
@@ -451,19 +465,16 @@ function parseEarsStatement(raw) {
|
|
|
451
465
|
if (/,/.test(system)) {
|
|
452
466
|
return reject('the clause before "shall" contains a comma but starts with no condition keyword (When/While/If/Where) \u2014 either drop the comma or lead with the condition');
|
|
453
467
|
}
|
|
454
|
-
return {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
response: response.trim()
|
|
461
|
-
}
|
|
462
|
-
};
|
|
468
|
+
return lint({
|
|
469
|
+
pattern: "always",
|
|
470
|
+
condition: null,
|
|
471
|
+
system: system.trim().replace(/^the\s+/i, ""),
|
|
472
|
+
response: response.trim()
|
|
473
|
+
}, statement);
|
|
463
474
|
}
|
|
464
475
|
return reject('nothing follows "shall" \u2014 the observable result is missing');
|
|
465
476
|
}
|
|
466
|
-
var EARS_EXPECTED, CONDITIONAL, ALWAYS_RE;
|
|
477
|
+
var EARS_EXPECTED, CONDITIONAL, ALWAYS_RE, SHAPE_EXPECTED, ONE_PROMISE_EXPECTED = "Expected one promise per statement: a second behavior is its own requirement, and the reason goes in the why.", RATIONALE_RE;
|
|
467
478
|
var init_ears = __esm(() => {
|
|
468
479
|
EARS_EXPECTED = [
|
|
469
480
|
'When <trigger>, the <system> shall <result> \u2014 e.g. "When an unknown id is passed, spec resolve shall print [] and exit 0."',
|
|
@@ -480,6 +491,9 @@ var init_ears = __esm(() => {
|
|
|
480
491
|
{ keyword: "Where", pattern: "where", re: /^Where\s+(.+?),\s*(.+?)\s+shall\s+(.+)$/s }
|
|
481
492
|
];
|
|
482
493
|
ALWAYS_RE = /^(.+?)\s+shall\s+(.+)$/s;
|
|
494
|
+
SHAPE_EXPECTED = `Expected one of:
|
|
495
|
+
${EARS_EXPECTED}`;
|
|
496
|
+
RATIONALE_RE = /\b(because|so that|in order to)\b/i;
|
|
483
497
|
});
|
|
484
498
|
|
|
485
499
|
// ../shared/src/flags.ts
|
|
@@ -670,7 +684,8 @@ CREATE INDEX IF NOT EXISTS idx_term_citations_term ON term_citations(term_id);
|
|
|
670
684
|
-- supersedes-via | amends-via), validated at parse time, never as a DB
|
|
671
685
|
-- constraint. Exactly one index, on req_id; never on issue_id, which would
|
|
672
686
|
-- treat the opaque payload as a lookup key.
|
|
673
|
-
-- @spec PROV-
|
|
687
|
+
-- @spec PROV-005
|
|
688
|
+
-- @spec PROV-006
|
|
674
689
|
CREATE TABLE IF NOT EXISTS provenance (
|
|
675
690
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
676
691
|
req_id TEXT NOT NULL,
|
|
@@ -3790,7 +3805,7 @@ var init_proven = __esm(() => {
|
|
|
3790
3805
|
|
|
3791
3806
|
// src/base/gitBase.ts
|
|
3792
3807
|
function isSafeRef(ref) {
|
|
3793
|
-
return /^[A-Za-z0-9._/-]
|
|
3808
|
+
return /^[A-Za-z0-9._/][A-Za-z0-9._/-]*$/.test(ref);
|
|
3794
3809
|
}
|
|
3795
3810
|
function gitRepoPrefix(platformDir) {
|
|
3796
3811
|
const proc = Bun.spawnSync(["git", "rev-parse", "--show-prefix"], { cwd: platformDir });
|
|
@@ -3828,7 +3843,7 @@ function gitLsTree(platformDir, ref, dir) {
|
|
|
3828
3843
|
function gitDiffNameStatus(platformDir, ref) {
|
|
3829
3844
|
if (!isSafeRef(ref))
|
|
3830
3845
|
return [];
|
|
3831
|
-
const proc = Bun.spawnSync(["git", "diff", "--name-status", "--no-renames", "--relative", ref], {
|
|
3846
|
+
const proc = Bun.spawnSync(["git", "diff", "--name-status", "--no-renames", "--relative", ref, "--"], {
|
|
3832
3847
|
cwd: platformDir
|
|
3833
3848
|
});
|
|
3834
3849
|
if (proc.exitCode !== 0)
|
|
@@ -4136,8 +4151,7 @@ function rowsForDomain2(key, domain2) {
|
|
|
4136
4151
|
source_file: specPaths("", key).rel,
|
|
4137
4152
|
line: found >= 0 ? found + 1 : 0,
|
|
4138
4153
|
req_id: req.id,
|
|
4139
|
-
detail: `${result.problem}.
|
|
4140
|
-
${result.expected}`
|
|
4154
|
+
detail: `${result.problem}. ${result.expected}`
|
|
4141
4155
|
});
|
|
4142
4156
|
}
|
|
4143
4157
|
return rows;
|
|
@@ -4832,8 +4846,7 @@ async function judgeStatementGrammar(platformDir, key, statement) {
|
|
|
4832
4846
|
severity: cfg.severity,
|
|
4833
4847
|
key,
|
|
4834
4848
|
message: `the statement does not match the domain's EARS shape \u2014 ${result.problem}.
|
|
4835
|
-
`
|
|
4836
|
-
${result.expected}`
|
|
4849
|
+
${result.expected}`
|
|
4837
4850
|
};
|
|
4838
4851
|
}
|
|
4839
4852
|
function grammarWarningText(cmdName, v) {
|
|
@@ -5718,10 +5731,34 @@ async function newDomain(platformDir, key, options = {}) {
|
|
|
5718
5731
|
async function listDomains(platformDir) {
|
|
5719
5732
|
return { ok: true, domains: await domainsWithScope(platformDir) };
|
|
5720
5733
|
}
|
|
5734
|
+
async function setDomainGrammar(platformDir, key, options) {
|
|
5735
|
+
if (options.grammar === undefined && options.grammarSeverity === undefined) {
|
|
5736
|
+
return fail("usage", "pass --grammar, --severity, or both");
|
|
5737
|
+
}
|
|
5738
|
+
const file = await readEnvelope(platformDir, key);
|
|
5739
|
+
if (!file.ok)
|
|
5740
|
+
return file;
|
|
5741
|
+
const domain2 = { ...file.domain, ...definedFields(options) };
|
|
5742
|
+
const res = await validateAndWrite(file.specPath, domain2, file.relFile);
|
|
5743
|
+
if (!res.ok) {
|
|
5744
|
+
return fail("invalid_domain_file", res.diagnostics.map((d) => d.detail).join(`
|
|
5745
|
+
`), {
|
|
5746
|
+
diagnostics: res.diagnostics
|
|
5747
|
+
});
|
|
5748
|
+
}
|
|
5749
|
+
return {
|
|
5750
|
+
ok: true,
|
|
5751
|
+
key,
|
|
5752
|
+
grammar: domain2.grammar,
|
|
5753
|
+
grammarSeverity: domain2.grammarSeverity,
|
|
5754
|
+
file: file.relFile
|
|
5755
|
+
};
|
|
5756
|
+
}
|
|
5721
5757
|
var init_domain2 = __esm(() => {
|
|
5722
5758
|
init_src();
|
|
5723
5759
|
init_domains();
|
|
5724
5760
|
init_constants();
|
|
5761
|
+
init__envelope();
|
|
5725
5762
|
init__result();
|
|
5726
5763
|
});
|
|
5727
5764
|
|
|
@@ -5730,6 +5767,7 @@ var exports_domain = {};
|
|
|
5730
5767
|
__export(exports_domain, {
|
|
5731
5768
|
domainNewCommand: () => domainNewCommand,
|
|
5732
5769
|
domainListCommand: () => domainListCommand,
|
|
5770
|
+
domainGrammarCommand: () => domainGrammarCommand,
|
|
5733
5771
|
domainCommand: () => domainCommand
|
|
5734
5772
|
});
|
|
5735
5773
|
import { defineCommand as defineCommand4 } from "citty";
|
|
@@ -5740,7 +5778,15 @@ function exitOnNewFailure(failure) {
|
|
|
5740
5778
|
}
|
|
5741
5779
|
exitOnFailure("spec domain new", failure);
|
|
5742
5780
|
}
|
|
5743
|
-
|
|
5781
|
+
function enumFlag(flag, value, allowed) {
|
|
5782
|
+
if (value === undefined)
|
|
5783
|
+
return;
|
|
5784
|
+
if (allowed.includes(value))
|
|
5785
|
+
return value;
|
|
5786
|
+
console.error(`spec domain grammar: --${flag} must be one of ${allowed.join(", ")}`);
|
|
5787
|
+
process.exit(EXIT.USAGE);
|
|
5788
|
+
}
|
|
5789
|
+
var domainNewCommand, domainListCommand, GRAMMARS, SEVERITIES, domainGrammarCommand, domainCommand;
|
|
5744
5790
|
var init_domain3 = __esm(() => {
|
|
5745
5791
|
init_domains();
|
|
5746
5792
|
init_constants();
|
|
@@ -5808,6 +5854,55 @@ var init_domain3 = __esm(() => {
|
|
|
5808
5854
|
}
|
|
5809
5855
|
}
|
|
5810
5856
|
});
|
|
5857
|
+
GRAMMARS = ["ears", "freeform"];
|
|
5858
|
+
SEVERITIES = ["warning", "error"];
|
|
5859
|
+
domainGrammarCommand = defineCommand4({
|
|
5860
|
+
meta: {
|
|
5861
|
+
name: "grammar",
|
|
5862
|
+
description: "Set a domain's statement grammar and its severity"
|
|
5863
|
+
},
|
|
5864
|
+
args: {
|
|
5865
|
+
name: {
|
|
5866
|
+
type: "positional",
|
|
5867
|
+
required: true,
|
|
5868
|
+
description: "Domain key (case-insensitive)"
|
|
5869
|
+
},
|
|
5870
|
+
platformDir: platformDirArg,
|
|
5871
|
+
grammar: {
|
|
5872
|
+
type: "string",
|
|
5873
|
+
description: "ears | freeform"
|
|
5874
|
+
},
|
|
5875
|
+
severity: {
|
|
5876
|
+
type: "string",
|
|
5877
|
+
description: "warning | error \u2014 how STATEMENT_GRAMMAR and write-time judging treat a miss"
|
|
5878
|
+
},
|
|
5879
|
+
json: {
|
|
5880
|
+
type: "boolean",
|
|
5881
|
+
description: "Print { key, grammar, grammar_severity, file }"
|
|
5882
|
+
}
|
|
5883
|
+
},
|
|
5884
|
+
async run({ args }) {
|
|
5885
|
+
const platformDir = resolvePlatformDir(args);
|
|
5886
|
+
const grammar = enumFlag("grammar", args.grammar, GRAMMARS);
|
|
5887
|
+
const grammarSeverity = enumFlag("severity", args.severity, SEVERITIES);
|
|
5888
|
+
const result = await setDomainGrammar(platformDir, normalizeDomainKey(args.name), {
|
|
5889
|
+
grammar,
|
|
5890
|
+
grammarSeverity
|
|
5891
|
+
});
|
|
5892
|
+
if (!result.ok)
|
|
5893
|
+
exitOnFailure("spec domain grammar", result);
|
|
5894
|
+
if (args.json) {
|
|
5895
|
+
console.log(JSON.stringify({
|
|
5896
|
+
key: result.key,
|
|
5897
|
+
grammar: result.grammar ?? null,
|
|
5898
|
+
grammar_severity: result.grammarSeverity ?? null,
|
|
5899
|
+
file: result.file
|
|
5900
|
+
}));
|
|
5901
|
+
return;
|
|
5902
|
+
}
|
|
5903
|
+
console.log(`${result.file}: grammar ${result.grammar ?? "freeform"}, severity ${result.grammarSeverity ?? "warning"}`);
|
|
5904
|
+
}
|
|
5905
|
+
});
|
|
5811
5906
|
domainCommand = defineCommand4({
|
|
5812
5907
|
meta: {
|
|
5813
5908
|
name: "domain",
|
|
@@ -5815,7 +5910,8 @@ var init_domain3 = __esm(() => {
|
|
|
5815
5910
|
},
|
|
5816
5911
|
subCommands: {
|
|
5817
5912
|
new: domainNewCommand,
|
|
5818
|
-
list: domainListCommand
|
|
5913
|
+
list: domainListCommand,
|
|
5914
|
+
grammar: domainGrammarCommand
|
|
5819
5915
|
}
|
|
5820
5916
|
});
|
|
5821
5917
|
});
|
|
@@ -8440,6 +8536,20 @@ function jsonResult(value) {
|
|
|
8440
8536
|
function errorResult(message) {
|
|
8441
8537
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
8442
8538
|
}
|
|
8539
|
+
function amendFieldsOf(args) {
|
|
8540
|
+
const fields = {};
|
|
8541
|
+
if (args.text !== undefined)
|
|
8542
|
+
fields.statement = args.text;
|
|
8543
|
+
if (args.why !== undefined)
|
|
8544
|
+
fields.why = args.why || null;
|
|
8545
|
+
if (args.lives !== undefined)
|
|
8546
|
+
fields.livesIn = args.lives;
|
|
8547
|
+
if (args.relates !== undefined)
|
|
8548
|
+
fields.relates = args.relates;
|
|
8549
|
+
if (args.issue)
|
|
8550
|
+
fields.issue = args.issue;
|
|
8551
|
+
return fields;
|
|
8552
|
+
}
|
|
8443
8553
|
function buildMcpServer(platformDir) {
|
|
8444
8554
|
const server = new McpServer({ name: "spec", version: package_default.version });
|
|
8445
8555
|
const fresh = (fn) => withIndex({ platformDir, build: "fresh" }, (h) => fn(h.storage));
|
|
@@ -8593,6 +8703,94 @@ function buildMcpServer(platformDir) {
|
|
|
8593
8703
|
const r = await accept({ platformDir, id: req_id });
|
|
8594
8704
|
return r.ok ? jsonResult({ id: r.id, file: r.file }) : errorResult(r.detail);
|
|
8595
8705
|
});
|
|
8706
|
+
server.registerTool("spec_req", {
|
|
8707
|
+
title: "Author a requirement",
|
|
8708
|
+
description: "Append a new requirement to a domain (Active, or Draft with draft: true). The same JSON as `spec req --text \u2026 --json`: { id, file }, plus clauses when the statement parses as EARS.",
|
|
8709
|
+
inputSchema: {
|
|
8710
|
+
domain: z4.string().min(1).describe("Domain key or unique case-insensitive prefix"),
|
|
8711
|
+
text: z4.string().trim().min(1).describe("The requirement statement"),
|
|
8712
|
+
why: z4.string().trim().default("").describe("What breaks without it"),
|
|
8713
|
+
lives: z4.array(z4.string()).default([]).describe("livesIn paths"),
|
|
8714
|
+
relates: z4.array(z4.string()).default([]).describe("Related requirement ids, each comma-split"),
|
|
8715
|
+
issue: z4.string().trim().optional().describe("Originating ticket, recorded as created"),
|
|
8716
|
+
draft: z4.boolean().default(false).describe("Author as Draft instead of Active")
|
|
8717
|
+
}
|
|
8718
|
+
}, async ({ domain: domain2, text, why, lives, relates, issue, draft }) => {
|
|
8719
|
+
const allocated = await nextId(platformDir, domain2);
|
|
8720
|
+
if (!allocated.ok)
|
|
8721
|
+
return errorResult(allocated.detail);
|
|
8722
|
+
const r = await mint({
|
|
8723
|
+
platformDir,
|
|
8724
|
+
key: allocated.key,
|
|
8725
|
+
statement: text,
|
|
8726
|
+
why,
|
|
8727
|
+
livesIn: lives,
|
|
8728
|
+
relates,
|
|
8729
|
+
issue: issue || undefined,
|
|
8730
|
+
status: draft ? "draft" : "active"
|
|
8731
|
+
});
|
|
8732
|
+
if (!r.ok)
|
|
8733
|
+
return errorResult(r.detail);
|
|
8734
|
+
for (const w of r.warnings)
|
|
8735
|
+
console.error(`spec_req: ${w.text}`);
|
|
8736
|
+
return jsonResult(r.clauses ? { id: r.id, file: r.file, clauses: r.clauses } : { id: r.id, file: r.file });
|
|
8737
|
+
});
|
|
8738
|
+
server.registerTool("spec_amend", {
|
|
8739
|
+
title: "Amend a requirement in place",
|
|
8740
|
+
description: "Revise an Active or Draft requirement's fields in place (same id, no version change); a shipped requirement's statement and why are superseded instead. The same JSON as `spec amend --json`.",
|
|
8741
|
+
inputSchema: {
|
|
8742
|
+
id: z4.string().describe("The requirement id to amend (KEY-NNN)"),
|
|
8743
|
+
text: z4.string().trim().min(1).optional().describe("New statement"),
|
|
8744
|
+
why: z4.string().trim().optional().describe("New why; empty clears it"),
|
|
8745
|
+
lives: z4.array(z4.string()).optional().describe("Replacement livesIn list; empty clears"),
|
|
8746
|
+
relates: z4.array(z4.string()).optional().describe("Replacement related ids, each comma-split; empty clears"),
|
|
8747
|
+
issue: z4.string().trim().optional().describe("Ticket recorded as amends-via")
|
|
8748
|
+
}
|
|
8749
|
+
}, async ({ id, text, why, lives, relates, issue }) => {
|
|
8750
|
+
if (!ID_RE.test(id))
|
|
8751
|
+
return errorResult(`id must be a requirement id (KEY-NNN); got ${id}`);
|
|
8752
|
+
const fields = amendFieldsOf({ text, why, lives, relates, issue });
|
|
8753
|
+
if (Object.keys(fields).length === 0) {
|
|
8754
|
+
return errorResult("nothing to amend \u2014 pass at least one of text, why, lives, relates");
|
|
8755
|
+
}
|
|
8756
|
+
const r = await amend({ platformDir, id, fields });
|
|
8757
|
+
if (!r.ok)
|
|
8758
|
+
return errorResult(r.detail);
|
|
8759
|
+
for (const w of r.warnings)
|
|
8760
|
+
console.error(`spec_amend: ${w.text}`);
|
|
8761
|
+
return jsonResult({ id, file: r.file, fields_changed: r.fieldsChanged });
|
|
8762
|
+
});
|
|
8763
|
+
server.registerTool("spec_move", {
|
|
8764
|
+
title: "Move a requirement to another domain",
|
|
8765
|
+
description: "Mint the successor in newDomain carrying the source's fields, mark the source superseded, and return the retag worklist. The same JSON as `spec move --json`.",
|
|
8766
|
+
inputSchema: {
|
|
8767
|
+
id: z4.string().describe("The Active requirement id to move (KEY-NNN)"),
|
|
8768
|
+
newDomain: z4.string().min(1).describe("The existing target domain key"),
|
|
8769
|
+
text: z4.string().optional().describe("Successor statement (default: copied)"),
|
|
8770
|
+
why: z4.string().optional().describe("Successor why (default: copied)"),
|
|
8771
|
+
lives: z4.array(z4.string()).optional().describe("Successor livesIn list; empty clears (default: copied)")
|
|
8772
|
+
}
|
|
8773
|
+
}, async ({ id, newDomain: newDomain2, text, why, lives }) => {
|
|
8774
|
+
if (!ID_RE.test(id))
|
|
8775
|
+
return errorResult(`id must be a requirement id (KEY-NNN); got ${id}`);
|
|
8776
|
+
const targetKey = normalizeDomainKey(newDomain2);
|
|
8777
|
+
if (targetKey === "")
|
|
8778
|
+
return errorResult(`newDomain must be a domain key; got ${newDomain2}`);
|
|
8779
|
+
const r = await move({ platformDir, id, targetKey, statement: text, why, livesIn: lives }, coldFreshTags(platformDir));
|
|
8780
|
+
if (!r.ok)
|
|
8781
|
+
return errorResult(r.detail);
|
|
8782
|
+
for (const w of r.warnings)
|
|
8783
|
+
console.error(`spec_move: ${w.text}`);
|
|
8784
|
+
return jsonResult({
|
|
8785
|
+
old_id: r.oldId,
|
|
8786
|
+
new_id: r.newId,
|
|
8787
|
+
from_file: r.fromFile,
|
|
8788
|
+
to_file: r.toFile,
|
|
8789
|
+
source_spec_version: r.sourceSpecVersion,
|
|
8790
|
+
target_spec_version: r.targetSpecVersion,
|
|
8791
|
+
retag: r.retag
|
|
8792
|
+
});
|
|
8793
|
+
});
|
|
8596
8794
|
server.registerPrompt("author_requirements", {
|
|
8597
8795
|
title: "Author requirements from a brief",
|
|
8598
8796
|
description: "Turn a vague brief/ticket into well-formed Spec Engine requirements: one requirement per testable promise, placed by domain charter, deduped via spec query, drafted to the GUARD template + cold-read rubric, minted with spec req. The engine returns a static playbook template \u2014 your model runs it.",
|
|
@@ -8629,8 +8827,11 @@ var init_mcp = __esm(() => {
|
|
|
8629
8827
|
init_domains();
|
|
8630
8828
|
init__index();
|
|
8631
8829
|
init_accept();
|
|
8830
|
+
init_amend();
|
|
8632
8831
|
init_check();
|
|
8633
8832
|
init_deprecate();
|
|
8833
|
+
init_mint();
|
|
8834
|
+
init_move();
|
|
8634
8835
|
init_nextId();
|
|
8635
8836
|
init_reads2();
|
|
8636
8837
|
init_records();
|
|
@@ -12153,6 +12354,9 @@ function guarded(handler) {
|
|
|
12153
12354
|
try {
|
|
12154
12355
|
return await handler(c);
|
|
12155
12356
|
} catch (err) {
|
|
12357
|
+
if (err instanceof InvalidDomainFilesError) {
|
|
12358
|
+
return c.json({ error: "invalid_domain_file", diagnostics: sortDiagnostics(err.diagnostics) }, 422);
|
|
12359
|
+
}
|
|
12156
12360
|
const info = describeStorageError(err);
|
|
12157
12361
|
if (info === null)
|
|
12158
12362
|
throw err;
|
|
@@ -12404,6 +12608,52 @@ function mountApi(app, storage2, platformDir = process.cwd(), bindHost = DEFAULT
|
|
|
12404
12608
|
}, 201);
|
|
12405
12609
|
});
|
|
12406
12610
|
}));
|
|
12611
|
+
app.post("/api/requirements/:id/move", guarded(async function moveRequirement(c) {
|
|
12612
|
+
if (!featureEnabled("editor")) {
|
|
12613
|
+
return c.json({ error: featureDisabledMessage("editor") }, 404);
|
|
12614
|
+
}
|
|
12615
|
+
const originReject = rejectCrossOrigin(c, bindHost);
|
|
12616
|
+
if (originReject)
|
|
12617
|
+
return originReject;
|
|
12618
|
+
const headerReject = rejectBadWriteHeaders(c);
|
|
12619
|
+
if (headerReject)
|
|
12620
|
+
return headerReject;
|
|
12621
|
+
const parsed = await readJsonBody(c);
|
|
12622
|
+
if (!parsed.ok)
|
|
12623
|
+
return parsed.res;
|
|
12624
|
+
const body = parsed.body;
|
|
12625
|
+
const id = c.req.param("id") ?? "";
|
|
12626
|
+
if (!ID_RE.test(id))
|
|
12627
|
+
return c.json({ error: "id must be a requirement id (KEY-NNN)" }, 400);
|
|
12628
|
+
const targetKey = normalizeDomainKey(typeof body.newDomain === "string" ? body.newDomain : "");
|
|
12629
|
+
if (targetKey === "")
|
|
12630
|
+
return c.json({ error: "newDomain is required (a domain key)" }, 400);
|
|
12631
|
+
return withWriteLock(async () => {
|
|
12632
|
+
const r = await move({
|
|
12633
|
+
platformDir,
|
|
12634
|
+
id,
|
|
12635
|
+
targetKey,
|
|
12636
|
+
statement: typeof body.text === "string" ? body.text : undefined,
|
|
12637
|
+
why: typeof body.why === "string" ? body.why : undefined,
|
|
12638
|
+
livesIn: "lives" in body ? toStringList(body.lives) : undefined
|
|
12639
|
+
}, async (reqId) => {
|
|
12640
|
+
await runIndex({ platformDir, storage: storage2 });
|
|
12641
|
+
return storage2.listTags({ req_id: reqId });
|
|
12642
|
+
});
|
|
12643
|
+
if (!r.ok)
|
|
12644
|
+
return failureResponse(c, r);
|
|
12645
|
+
return c.json({
|
|
12646
|
+
ok: true,
|
|
12647
|
+
old_id: r.oldId,
|
|
12648
|
+
new_id: r.newId,
|
|
12649
|
+
from_file: r.fromFile,
|
|
12650
|
+
to_file: r.toFile,
|
|
12651
|
+
source_spec_version: r.sourceSpecVersion,
|
|
12652
|
+
target_spec_version: r.targetSpecVersion,
|
|
12653
|
+
retag: r.retag
|
|
12654
|
+
}, 201);
|
|
12655
|
+
});
|
|
12656
|
+
}));
|
|
12407
12657
|
app.post("/api/requirements/:id/deprecate", guarded(async function deprecateRequirement(c) {
|
|
12408
12658
|
if (!featureEnabled("editor")) {
|
|
12409
12659
|
return c.json({ error: featureDisabledMessage("editor") }, 404);
|
|
@@ -12542,6 +12792,7 @@ var init_api = __esm(() => {
|
|
|
12542
12792
|
init_amend();
|
|
12543
12793
|
init_deprecate();
|
|
12544
12794
|
init_mint();
|
|
12795
|
+
init_move();
|
|
12545
12796
|
init_reads2();
|
|
12546
12797
|
init_records();
|
|
12547
12798
|
init_supersede();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spec-engine/spec-engine",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
43
43
|
"@spec-engine/platform-map": "0.5.0",
|
|
44
44
|
"citty": "0.2.2",
|
|
45
|
-
"hono": "4.
|
|
45
|
+
"hono": "4.13.11",
|
|
46
46
|
"zod": "3.25.76"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer starlight.components{svg:where(.astro-lm5mbbcm){color:var(--sl-icon-color);font-size:var(--sl-icon-size, 1em);width:1em;height:1em}}@layer starlight.components{:root{--sl-badge-default-border: var(--sl-color-accent);--sl-badge-default-bg: var(--sl-color-accent-low);--sl-badge-default-text: #fff;--sl-badge-note-border: var(--sl-color-blue);--sl-badge-note-bg: var(--sl-color-blue-low);--sl-badge-note-text: #fff;--sl-badge-danger-border: var(--sl-color-red);--sl-badge-danger-bg: var(--sl-color-red-low);--sl-badge-danger-text: #fff;--sl-badge-success-border: var(--sl-color-green);--sl-badge-success-bg: var(--sl-color-green-low);--sl-badge-success-text: #fff;--sl-badge-caution-border: var(--sl-color-orange);--sl-badge-caution-bg: var(--sl-color-orange-low);--sl-badge-caution-text: #fff;--sl-badge-tip-border: var(--sl-color-purple);--sl-badge-tip-bg: var(--sl-color-purple-low);--sl-badge-tip-text: #fff}[data-theme=light]:root{--sl-badge-default-bg: var(--sl-color-accent-high);--sl-badge-note-bg: var(--sl-color-blue-high);--sl-badge-danger-bg: var(--sl-color-red-high);--sl-badge-success-bg: var(--sl-color-green-high);--sl-badge-caution-bg: var(--sl-color-orange-high);--sl-badge-tip-bg: var(--sl-color-purple-high)}.sl-badge:where(.astro-dr5neazu){display:inline-block;border:1px solid var(--sl-color-border-badge);border-radius:.25rem;font-family:var(--sl-font-system-mono);line-height:normal;color:var(--sl-color-text-badge);background-color:var(--sl-color-bg-badge);overflow-wrap:anywhere}.sidebar-content .sl-badge:where(.astro-dr5neazu){line-height:1;font-size:var(--sl-text-xs);padding:.125rem .375rem}.sidebar-content a[aria-current=page]>.sl-badge:where(.astro-dr5neazu){--sl-color-bg-badge: transparent;--sl-color-border-badge: currentColor;color:inherit}.default:where(.astro-dr5neazu){--sl-color-bg-badge: var(--sl-badge-default-bg);--sl-color-border-badge: var(--sl-badge-default-border);--sl-color-text-badge: var(--sl-badge-default-text)}.note:where(.astro-dr5neazu){--sl-color-bg-badge: var(--sl-badge-note-bg);--sl-color-border-badge: var(--sl-badge-note-border);--sl-color-text-badge: var(--sl-badge-note-text)}.danger:where(.astro-dr5neazu){--sl-color-bg-badge: var(--sl-badge-danger-bg);--sl-color-border-badge: var(--sl-badge-danger-border);--sl-color-text-badge: var(--sl-badge-danger-text)}.success:where(.astro-dr5neazu){--sl-color-bg-badge: var(--sl-badge-success-bg);--sl-color-border-badge: var(--sl-badge-success-border);--sl-color-text-badge: var(--sl-badge-success-text)}.tip:where(.astro-dr5neazu){--sl-color-bg-badge: var(--sl-badge-tip-bg);--sl-color-border-badge: var(--sl-badge-tip-border);--sl-color-text-badge: var(--sl-badge-tip-text)}.caution:where(.astro-dr5neazu){--sl-color-bg-badge: var(--sl-badge-caution-bg);--sl-color-border-badge: var(--sl-badge-caution-border);--sl-color-text-badge: var(--sl-badge-caution-text)}.small:where(.astro-dr5neazu){font-size:var(--sl-text-xs);padding:.125rem .25rem}.medium:where(.astro-dr5neazu){font-size:var(--sl-text-sm);padding:.175rem .35rem}.large:where(.astro-dr5neazu){font-size:var(--sl-text-base);padding:.225rem .45rem}.sl-markdown-content :is(h1,h2,h3,h4,h5,h6) .sl-badge:where(.astro-dr5neazu){vertical-align:middle}}@layer starlight.components{starlight-tabs:where(.astro-njbqzmfe){display:block}.tablist-wrapper:where(.astro-njbqzmfe){overflow-x:auto}:where(.astro-njbqzmfe)[role=tablist]{display:flex;list-style:none;border-bottom:2px solid var(--sl-color-gray-5);padding:0}.tab:where(.astro-njbqzmfe){display:flex}.tab:where(.astro-njbqzmfe)>:where(.astro-njbqzmfe)[role=tab]{--sl-tab-color-border: var(--sl-color-gray-5);display:flex;align-items:center;gap:.5rem;line-height:var(--sl-line-height-headings);padding:.275rem 1.25rem;text-decoration:none;box-shadow:0 2px 0 var(--sl-tab-color-border);color:var(--sl-color-gray-3);outline-offset:var(--sl-outline-offset-inside);overflow-wrap:initial}.tab:where(.astro-njbqzmfe) :where(.astro-njbqzmfe)[role=tab][aria-selected=true]{--sl-tab-color-border: var(--sl-color-text-accent);color:var(--sl-color-white);font-weight:600}.tablist-wrapper:where(.astro-njbqzmfe)~[role=tabpanel]{margin-top:1rem}}@layer starlight.components{.sl-steps{--bullet-size: calc(var(--sl-line-height) * 1rem);--bullet-margin: .375rem;list-style:none;counter-reset:steps-counter var(--sl-steps-start, 0);padding-inline-start:0}.sl-steps>li{counter-increment:steps-counter;position:relative;padding-inline-start:calc(var(--bullet-size) + 1rem);padding-bottom:1px;min-height:calc(var(--bullet-size) + var(--bullet-margin))}.sl-steps>li+li{margin-top:0}.sl-steps>li:before{content:counter(steps-counter);position:absolute;top:0;inset-inline-start:0;width:var(--bullet-size);height:var(--bullet-size);line-height:var(--bullet-size);font-size:var(--sl-text-xs);font-weight:600;text-align:center;color:var(--sl-color-white);background-color:var(--sl-color-gray-6);border-radius:99rem;box-shadow:inset 0 0 0 1px var(--sl-color-gray-5)}.sl-steps>li:after{--guide-width: 1px;content:"";position:absolute;top:calc(var(--bullet-size) + var(--bullet-margin));bottom:var(--bullet-margin);inset-inline-start:calc((var(--bullet-size) - var(--guide-width)) / 2);width:var(--guide-width);background-color:var(--sl-color-hairline-light)}}@layer starlight.content{.sl-steps>li>:first-child{--lh: calc(1em * var(--sl-line-height));--shift-y: calc(.5 * (var(--bullet-size) - var(--lh)));transform:translateY(var(--shift-y));margin-bottom:var(--shift-y)}.sl-steps>li>:first-child:where(h1,h2,h3,h4,h5,h6){--lh: calc(1em * var(--sl-line-height-headings))}@supports (--prop: 1lh){.sl-steps>li>:first-child{--lh: 1lh}}}@layer starlight.components{.sl-link-button:where(.astro-yiov523n){align-items:center;border:1px solid transparent;border-radius:999rem;display:inline-flex;font-size:var(--sl-text-sm);gap:.5em;line-height:1.1875;outline-offset:.25rem;padding:.4375rem 1.125rem;text-decoration:none}.sl-link-button:where(.astro-yiov523n).primary{background:var(--sl-color-text-accent);border-color:var(--sl-color-text-accent);color:var(--sl-color-black)}.sl-link-button:where(.astro-yiov523n).primary:hover{color:var(--sl-color-black)}.sl-link-button:where(.astro-yiov523n).secondary{border-color:inherit;color:var(--sl-color-white)}.sl-link-button:where(.astro-yiov523n).minimal{color:var(--sl-color-white);padding-inline:0}.sl-link-button:where(.astro-yiov523n) svg{flex-shrink:0}@media(min-width:50rem){.sl-link-button:where(.astro-yiov523n){font-size:var(--sl-text-base);padding:.9375rem 1.25rem}}.sl-markdown-content .sl-link-button:where(.astro-yiov523n){margin-inline-end:1rem}.sl-markdown-content .sl-link-button:where(.astro-yiov523n):not(:where(p *)){margin-block:1rem}}
|
package/dist/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.hwBsy0Mo.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{S as r}from"./TableOfContents.astro_astro_type_script_index_0_lang.FuRcXuRY.js";class c extends r{set current(e){super.current=e;const t=this.querySelector(".display-current");t&&(t.textContent=e.textContent)}constructor(){super();const e=this.querySelector("details");if(!e)return;const t=()=>{e.open=!1};e.querySelectorAll("a").forEach(s=>{s.addEventListener("click",t)}),window.addEventListener("click",s=>{e.contains(s.target)||t()}),window.addEventListener("keydown",s=>{if(s.key==="Escape"&&e.open){const o=e.contains(document.activeElement);if(t(),o){const n=e.querySelector("summary");n&&n.focus()}}})}}customElements.define("mobile-starlight-toc",c);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const y=(function(){const r=typeof document<"u"&&document.createElement("link").relList;return r&&r.supports&&r.supports("modulepreload")?"modulepreload":"preload"})(),S=function(m){return"/"+m},g={},w=function(r,c,d){let p=Promise.resolve();if(c&&c.length>0){let f=function(n){return Promise.all(n.map(i=>Promise.resolve(i).then(s=>({status:"fulfilled",value:s}),s=>({status:"rejected",reason:s}))))};document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),t=o?.nonce||o?.getAttribute("nonce");p=f(c.map(n=>{if(n=S(n),n in g)return;g[n]=!0;const i=n.endsWith(".css"),s=i?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${n}"]${s}`))return;const e=document.createElement("link");if(e.rel=i?"stylesheet":y,i||(e.as="script"),e.crossOrigin="",e.href=n,t&&e.setAttribute("nonce",t),document.head.appendChild(e),i)return new Promise((l,a)=>{e.addEventListener("load",l),e.addEventListener("error",()=>a(new Error(`Unable to preload CSS for ${n}`)))})}))}function u(o){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=o,window.dispatchEvent(t),!t.defaultPrevented)throw o}return p.then(o=>{for(const t of o||[])t.status==="rejected"&&u(t.reason);return r().catch(u)})},E={ranking:{pageLength:.1,termFrequency:.1,termSaturation:2,termSimilarity:9,diacriticSimilarity:.8}};class v extends HTMLElement{constructor(){super();const r=this.querySelector("button[data-open-modal]"),c=this.querySelector("button[data-close-modal]"),d=this.querySelector("dialog"),p=this.querySelector(".dialog-frame"),u=e=>{("href"in(e.target||{})||document.body.contains(e.target)&&!p.contains(e.target))&&t()},o=e=>{d.showModal(),document.body.toggleAttribute("data-search-modal-open",!0),this.querySelector("input")?.focus(),e?.stopPropagation(),window.addEventListener("click",u)},t=()=>d.close();r.addEventListener("click",o),r.disabled=!1,c.addEventListener("click",t),d.addEventListener("close",()=>{document.body.toggleAttribute("data-search-modal-open",!1),window.removeEventListener("click",u)}),window.addEventListener("keydown",e=>{(e.metaKey===!0||e.ctrlKey===!0)&&e.key==="k"&&(d.open?t():o(),e.preventDefault())});let f={};try{f=JSON.parse(this.dataset.translations||"{}")}catch{}const s=this.dataset.stripTrailingSlash!==void 0?e=>e.replace(/(.)\/(#.*)?$/,"$1$2"):e=>e;window.addEventListener("DOMContentLoaded",()=>{(window.requestIdleCallback||(l=>setTimeout(l,1)))(async()=>{const{PagefindUI:l}=await w(async()=>{const{PagefindUI:a}=await import("./ui-core.C8327L_n.js");return{PagefindUI:a}},[]);new l({...E,element:"#starlight__search",baseUrl:"/",bundlePath:"/".replace(/\/$/,"")+"/pagefind/",showImages:!1,translations:f,showSubResults:!0,processResult:a=>{a.url=s(a.url),a.sub_results=a.sub_results.map(h=>(h.url=s(h.url),h))}})})})}}customElements.define("site-search",v);export{w as _};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const m="_top";class H extends HTMLElement{constructor(){super(),this._current=this.querySelector('a[aria-current="true"]'),this.minH=parseInt(this.dataset.minH||"2",10),this.maxH=parseInt(this.dataset.maxH||"3",10),this.tocHeadingSelector=`h1#${m},:where(${[...Array.from({length:1+this.maxH-this.minH}).map((e,n)=>`h${this.minH+n}`)].join()})[id]`,this.onIdle=e=>(window.requestIdleCallback||(n=>setTimeout(n,1)))(e),this.init=()=>{const e=[...this.querySelectorAll("a")],n=t=>t.matches(this.tocHeadingSelector),i=t=>{if(!t)return null;const h=t;for(;t;){if(t.matches(".sl-markdown-content, main > *"))return document.getElementById(m);if(n(t))return t;const s=t.querySelector(this.tocHeadingSelector);if(s)return s;for(t=t.previousElementSibling;t?.lastElementChild;)t=t.lastElementChild;const o=i(t);if(o)return o}return i(h.parentElement)},c=t=>{for(const{isIntersecting:h,target:s}of t){if(!h)continue;const o=i(s);if(!o)continue;const l=e.find(g=>g.hash==="#"+encodeURIComponent(o.id));if(l){this.current=l;break}}},a=document.querySelectorAll([`main :where(${this.tocHeadingSelector})`,`main :where(${this.tocHeadingSelector}, .sl-heading-wrapper) ~ *:not(:has(${this.tocHeadingSelector}))`,`main .sl-markdown-content > *:not(:has(${this.tocHeadingSelector}))`,`main > *:not(:has(${this.tocHeadingSelector}))`].join());let r;const u=()=>{r||(r=new IntersectionObserver(c,{rootMargin:this.getRootMargin()}),a.forEach(t=>r.observe(t)))};u();let d;window.addEventListener("resize",()=>{r&&(r.disconnect(),r=void 0),clearTimeout(d),d=setTimeout(()=>this.onIdle(u),200)})},this.onIdle(()=>this.init())}set current(e){e!==this._current&&(this._current&&this._current.removeAttribute("aria-current"),e.setAttribute("aria-current","true"),this._current=e)}getRootMargin(){const e=document.querySelector("header")?.getBoundingClientRect().height||0,n=this.querySelector("summary")?.getBoundingClientRect().height||0,i=e+n+32,c=i+53,a=document.documentElement.clientHeight;return`-${i}px 0% ${c-a}px`}}customElements.define("starlight-toc",H);export{H as S};
|