: (state.exists ? state.specsDir : "No features yet — describe your first feature below.");
document.getElementById("setup").hidden = !setupRequired;
document.getElementById("newFeature").hidden = setupRequired;
- document.getElementById("constitution").hidden = setupRequired;
+ document.getElementById("constitution").hidden = setupRequired && !state.constitution?.exists;
document.getElementById("setupText").textContent = prereq.initialized
? "Spec Kit is initialized, but the core spec-driven skills are not all installed."
: "Initialize Spec Kit in Copilot skills mode so the core spec-driven commands are available.";
@@ -257,7 +273,7 @@
Resolve clarification
e.textContent = "No features yet — describe your first feature below.";
c.appendChild(e);
}
- if (!setupRequired) state.features.forEach((feature) => c.appendChild(card(feature)));
+ state.features.forEach((feature) => c.appendChild(card(feature)));
}
function renderConstitution(constitution) {
@@ -279,6 +295,7 @@
Resolve clarification
function card(feature) {
const el = document.createElement("div"); el.className = "card" + (feature.active ? " active" : "");
+ el.dataset.feature = feature.slug;
const head = document.createElement("div"); head.className = "row";
const left = document.createElement("div");
left.innerHTML = "
" + esc(feature.title) + "
" + esc(feature.slug) + "";
@@ -325,6 +342,7 @@
Resolve clarification
function pill(key, feature) {
const st = feature.stages[key];
const p = document.createElement("button");
+ p.dataset.stage = key;
let kind = "pending";
if (key === "implement") {
kind = feature.implement.done ? "done" : (feature.implement.started ? "next" : (canRun(key, feature) ? "available" : "pending"));
@@ -481,38 +499,58 @@
if (!result.ok) { toast("Error: " + (result.error || "failed")); return; }
PENDING_CLARIFICATION = null;
document.getElementById("clarifyDialog").close();
- window.location.assign(endpoint("/"));
+ clarificationSubmitting = false;
+ document.getElementById("closeArt").click();
} catch (error) {
- toast("Error: " + error.message);
+ toast("The clarification could not be submitted. Refresh the source before retrying.");
+ } finally {
+ clarificationSubmitting = false;
+ document.getElementById("confirmClarify").disabled = false;
+ document.getElementById("cancelClarify").disabled = false;
}
};
+document.getElementById("clarifyDialog").addEventListener("cancel", (event) => {
+ if (clarificationSubmitting) event.preventDefault();
+ else PENDING_CLARIFICATION = null;
+});
function startsMarkdownBlock(lines, index) {
const line = lines[index];
@@ -720,9 +777,37 @@
Resolve clarification
if (cursor < text.length) parent.appendChild(document.createTextNode(text.slice(cursor)));
}
document.getElementById("closeArt").onclick = () => {
+ if (clarificationSubmitting) { toast("Wait for the clarification submission to finish."); return; }
+ if (artifactReview || artifactReviewReturn) {
+ disposeArtifactReview();
+ document.getElementById("artBody").replaceChildren();
+ if (!artifactReviewReturn) {
+ window.location.assign(endpoint("/"));
+ return;
+ }
+ document.body.classList.remove("artifact-view");
+ document.getElementById("aside").classList.remove("open");
+ const { trigger, scroll, feature, stage } = artifactReviewReturn;
+ artifactReviewReturn = null;
+ if (dashboardRefreshPending && STATE) render(STATE);
+ for (const entry of scroll) {
+ if (!entry.element.isConnected) continue;
+ entry.element.scrollTop = entry.top;
+ entry.element.scrollLeft = entry.left;
+ }
+ let target = trigger;
+ if (!target?.isConnected) {
+ const featureCard = [...document.querySelectorAll(".card")].find((element) => element.dataset.feature === feature);
+ target = stage === "constitution" ? document.getElementById("constView")
+ : [...featureCard?.querySelectorAll("[data-stage]") ?? []].find((element) => element.dataset.stage === stage);
+ }
+ if (target?.isConnected) target.focus({ preventScroll: true });
+ return;
+ }
if (document.body.classList.contains("artifact-view")) window.location.assign(endpoint("/"));
else document.getElementById("aside").classList.remove("open");
};
+window.addEventListener("pagehide", disposeArtifactReview);
async function initializeArtifactView(feature, stage, title) {
try {
@@ -748,7 +833,14 @@
Resolve clarification
try {
const es = new EventSource(endpoint("/events"));
es.addEventListener("state", (e) => { try { render(JSON.parse(e.data)); } catch (_) {} });
- es.onerror = () => {};
+ es.addEventListener("review", (event) => {
+ try {
+ const review = JSON.parse(event.data);
+ if (!review.contextHint || review.contextHint === artifactReview?.context?.contextId) void artifactReview?.refresh();
+ } catch (_) {}
+ });
+ es.onopen = () => { reviewConnected = true; artifactReview?.setConnected(true); void artifactReview?.refresh(); };
+ es.onerror = () => { reviewConnected = false; artifactReview?.setConnected(false); };
} catch (e) { /* fall back to one-shot fetch */ }
fetch(endpoint("/api/state")).then((r) => r.json()).then(render).catch(() => {});
}
diff --git a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/sdd.mjs b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/sdd.mjs
index 2d5e814..82d740a 100644
--- a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/sdd.mjs
+++ b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/sdd.mjs
@@ -12,6 +12,7 @@
import { closeSync, constants, fstatSync, lstatSync, openSync, readdirSync, readSync, realpathSync } from "node:fs";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
+import { parseClarificationSource } from "./vendor/artifact-clarifications.mjs";
// The primary artifact spine, in pipeline order. Each stage owns exactly one
// Markdown artifact under the feature directory and one generated skill. These
@@ -517,33 +518,11 @@ export function readArtifact(projectRoot, featureInput, stageKey) {
// Pull `[NEEDS CLARIFICATION: …]` markers out of a spec so the canvas can offer
// a targeted clarify action. Contents are treated strictly as data.
export function extractClarifications(text) {
- const clarifications = [];
- let section = "";
- let inCodeFence = false;
- for (const line of String(text || "").split(/\r?\n/)) {
- if (line.startsWith("```")) {
- inCodeFence = !inCodeFence;
- continue;
- }
- if (inCodeFence) continue;
- const heading = line.match(/^#{1,6}\s+(.+?)\s*$/);
- if (heading) {
- section = heading[1].trim();
- continue;
- }
- for (const match of line.matchAll(/\[NEEDS CLARIFICATION:\s*([^\]]+)\]/gi)) {
- clarifications.push({
- index: clarifications.length,
- section,
- question: match[1].trim(),
- });
- }
- }
- return clarifications;
+ return parseClarificationSource(String(text || "")).map(({ index, section, question }) => ({ index, section, question }));
}
// Build a signature string for change detection (used by the SSE poller).
-export function stateSignature(state) {
+export function stateSignature(state, reviewSignature) {
const parts = [
state.exists ? "1" : "0",
state.prerequisites.initialized ? "i" : "-",
@@ -561,5 +540,6 @@ export function stateSignature(state) {
parts.push(`ck:${feature.checklistCount}`);
parts.push(`im:${feature.implement.completed}/${feature.implement.total}`);
}
+ if (reviewSignature) parts.push(`review:${reviewSignature}`);
return parts.join("|");
}
diff --git a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/artifact-review.test.mjs b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/artifact-review.test.mjs
new file mode 100644
index 0000000..4c8a4b1
--- /dev/null
+++ b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/artifact-review.test.mjs
@@ -0,0 +1,282 @@
+import assert from "node:assert/strict";
+import * as fs from "node:fs/promises";
+import { createRequire } from "node:module";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { test } from "node:test";
+import { reviewFreshnessTests } from "../../../../spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/review-freshness-fixture.mjs";
+import { reviewHttpSecurityTests } from "../../../../spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/review-http-security-fixture.mjs";
+
+reviewHttpSecurityTests("sdd");
+
+const require = createRequire(new URL("../../../../spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/markdown-reader/package.json", import.meta.url));
+const { JSDOM } = require("jsdom");
+
+async function serviceFixture(run) {
+ const domain = await import("../artifact-review.mjs").catch((error) => {
+ if (error.code === "ERR_MODULE_NOT_FOUND") assert.fail("T023: SDD primary review service is missing");
+ throw error;
+ });
+ const root = await fs.mkdtemp(join(tmpdir(), "sdd-primary-review-"));
+ try {
+ await fs.mkdir(join(root, "specs/001-fixture"), { recursive: true });
+ await fs.mkdir(join(root, ".specify/memory"), { recursive: true });
+ await fs.writeFile(join(root, "specs/001-fixture/spec.md"), "# SDD fixture\n");
+ await fs.writeFile(join(root, ".specify/memory/constitution.md"), "# Governance\n");
+ const state = { projectRoot: root, prerequisites: { setupRequired: true }, features: [
+ { slug: "001-fixture", stages: { specify: { exists: true, done: true } } },
+ ] };
+ const service = domain.createSddReviewService({ workspacePath: root, instanceId: "sdd-fixture", getState: async () => state });
+ await run({ root, state, service });
+ } finally { await fs.rm(root, { recursive: true, force: true }); }
+}
+
+test("SDD reviews a primary artifact even when stage execution is gated", () => serviceFixture(async ({ service, state }) => {
+ const before = structuredClone(state);
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ const document = await service.content(opened.contextId, opened.primaryArtifactId);
+ assert.equal(document.content, "# SDD fixture\n");
+ assert.equal(document.artifact.relativePath, "specs/001-fixture/spec.md");
+ assert.match(document.revision, /^sha256:[a-f0-9]{64}$/);
+ assert.deepEqual(state, before);
+}));
+
+test("SDD constitution review is project scoped and does not require a feature", () => serviceFixture(async ({ service }) => {
+ const opened = await service.open({ stage: "constitution" });
+ const document = await service.content(opened.contextId, opened.primaryArtifactId);
+ assert.equal(document.artifact.relativePath, ".specify/memory/constitution.md");
+ assert.equal(document.content, "# Governance\n");
+}));
+
+test("SDD rejects unknown features and keeps artifact IDs inside their context", () => serviceFixture(async ({ service, state }) => {
+ await assert.rejects(service.open({ feature: "../escape", stage: "specify" }), { code: "artifact_unavailable" });
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ const other = await service.open({ stage: "constitution" });
+ await assert.rejects(service.content(other.contextId, opened.primaryArtifactId), { code: "artifact_unavailable" });
+ state.features = [];
+ await assert.rejects(service.content(opened.contextId, opened.primaryArtifactId), { code: "invalid_context" });
+}));
+
+test("SDD uses exact-byte revisions and reports a deleted primary artifact", () => serviceFixture(async ({ service, root }) => {
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ const document = await service.content(opened.contextId, opened.primaryArtifactId);
+ await fs.writeFile(join(root, "specs/001-fixture/spec.md"), "# Changed\n");
+ await assert.rejects(service.content(opened.contextId, opened.primaryArtifactId, { expectedRevision: document.revision }), { code: "changed_source" });
+ await fs.rm(join(root, "specs/001-fixture/spec.md"));
+ await assert.rejects(service.content(opened.contextId, opened.primaryArtifactId), { code: "artifact_unavailable" });
+}));
+
+test("SDD adapter mounts in the current artifact view, keeps capability, and returns focus", async () => {
+ const adapter = await import("../ui/artifact-review.js").catch((error) => {
+ if (error.code === "ERR_MODULE_NOT_FOUND") assert.fail("T023: SDD reader adapter is missing");
+ throw error;
+ });
+ const dom = new JSDOM('
', { url: "http://127.0.0.1:32101/?cap=fixture-cap" });
+ const trigger = dom.window.document.getElementById("trigger");
+ const container = dom.window.document.getElementById("reader");
+ trigger.focus();
+ const artifact = { id: "artifact_sdd", relativePath: "specs/001-fixture/spec.md", label: "Specification", role: "primary", availability: "available", suffix: ".md" };
+ let disposed = 0;
+ let mounted;
+ const requests = [];
+ const review = adapter.createArtifactReview({
+ container, scrollElement: container.parentElement, readerId: "sdd-fixture",
+ mount: (_element, options) => { mounted = options; return { update() {}, unmount() { disposed++; } }; },
+ fetch: async (input, options) => {
+ const url = new URL(input);
+ requests.push({ cap: url.searchParams.get("cap"), method: options?.method ?? "GET" });
+ const data = url.pathname.endsWith("/context")
+ ? { contextId: "ctx_sdd", generation: 1, primaryArtifactId: artifact.id, items: [artifact] }
+ : { artifact, content: "# SDD\n", revision: `sha256:${"b".repeat(64)}`, byteSize: 6, sourceKind: "working-tree" };
+ return { ok: true, json: async () => ({ ok: true, data }) };
+ },
+ });
+ try {
+ await review.open({ feature: "001-fixture", stage: "specify" });
+ assert.equal(mounted.document.artifact.id, artifact.id);
+ assert.deepEqual(requests, [{ cap: "fixture-cap", method: "GET" }, { cap: "fixture-cap", method: "GET" }]);
+ review.close();
+ review.close();
+ assert.equal(disposed, 1);
+ assert.equal(dom.window.document.activeElement, trigger);
+ } finally { review.close(); dom.window.close(); }
+});
+
+test("SDD discovery includes supporting Markdown, checklists, contracts, and constitution only in scope", () => serviceFixture(async ({ root, service }) => {
+ await fs.mkdir(join(root, "specs/001-fixture/checklists"), { recursive: true });
+ await fs.mkdir(join(root, "specs/001-fixture/contracts"), { recursive: true });
+ await fs.mkdir(join(root, "specs/002-other"), { recursive: true });
+ const supporting = ["research.md", "data-model.md", "custom.markdown", "checklists/requirements.md", "contracts/api.md"];
+ for (const file of supporting) await fs.writeFile(join(root, "specs/001-fixture", file), "# Supporting\n");
+ await fs.writeFile(join(root, "specs/002-other/private.md"), "Other feature");
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ const paths = opened.items.map((entry) => entry.relativePath);
+ for (const file of supporting) assert.ok(paths.includes(`specs/001-fixture/${file}`), `Missing scoped artifact ${file}`);
+ assert.ok(paths.includes(".specify/memory/constitution.md"));
+ assert.ok(!paths.includes("specs/002-other/private.md"));
+ const expected = opened.items.find((entry) => entry.relativePath.endsWith("/plan.md"));
+ assert.equal(expected?.availability, "expected");
+ await assert.rejects(service.content(opened.contextId, expected.id), { code: "artifact_unavailable" });
+}));
+
+test("SDD discovery pages supporting files without duplicates or a workflow transition", () => serviceFixture(async ({ root, service, state }) => {
+ for (let index = 0; index < 205; index++) await fs.writeFile(join(root, `specs/001-fixture/note-${String(index).padStart(3, "0")}.md`), "# Note\n");
+ const before = structuredClone(state);
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ assert.equal(opened.items.length, 200);
+ assert.ok(opened.nextCursor);
+ const next = await service.list(opened.contextId, { cursor: opened.nextCursor });
+ const all = [...opened.items, ...next.items];
+ assert.equal(all.filter((entry) => /\/note-/.test(entry.relativePath)).length, 205);
+ assert.equal(new Set(all.map((entry) => entry.id)).size, all.length);
+ assert.equal(next.nextCursor, null);
+ assert.deepEqual(state, before);
+}));
+
+test("SDD resolves only revision-validated safe Markdown references", () => serviceFixture(async ({ root, service }) => {
+ await fs.writeFile(join(root, "specs/001-fixture/research.md"), "# Research\n");
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ const source = await service.content(opened.contextId, opened.primaryArtifactId);
+ assert.equal(typeof service.resolveLink, "function", "T033: SDD link resolution is missing");
+ const linked = await service.resolveLink(opened.contextId, opened.primaryArtifactId, source.revision, "research.md#research");
+ assert.equal(linked.kind, "artifact");
+ assert.equal(linked.artifact.role, "reference");
+ assert.equal((await service.content(opened.contextId, linked.artifact.id)).content, "# Research\n");
+ assert.equal((await service.resolveLink(opened.contextId, opened.primaryArtifactId, source.revision, "file:///private.md")).kind, "inert");
+ await assert.rejects(service.resolveLink(opened.contextId, opened.primaryArtifactId, `sha256:${"0".repeat(64)}`, "#research"), { code: "changed_source" });
+}));
+
+test("SDD packaged discovery retains the cumulative inspection bound", async () => {
+ const discovery = await import("../vendor/artifact-discovery.mjs").catch((error) => {
+ if (error.code === "ERR_MODULE_NOT_FOUND") assert.fail("T033: packaged bounded discovery is missing");
+ throw error;
+ });
+ const entries = async function* () {
+ for (let index = 0; index < 10_001; index++) yield {
+ name: index % 2 ? `image-${index}.png` : `note-${index}.md`,
+ isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false,
+ };
+ };
+ const result = await discovery.scanArtifactCandidates({ workspacePath: tmpdir(), roots: ["specs/001-fixture"], explicit: [], entries });
+ assert.equal(result.inspectedCount, 10_000);
+ assert.equal(result.limitReached, true);
+ assert.equal(result.candidates.length, 5000);
+});
+
+test("SDD guarded review HTTP routes preserve legacy content and deny unauthorized contexts", async () => {
+ const { startFixture } = await import("../../../../../scripts/canvas-reader/serve-fixture.mjs");
+ const fixture = await startFixture({ canvas: "sdd" });
+ const endpoint = (route, parameters = {}) => {
+ const url = new URL(fixture.url);
+ url.pathname = route;
+ for (const [key, value] of Object.entries(parameters)) url.searchParams.set(key, value);
+ return url;
+ };
+ try {
+ const response = await fetch(endpoint("/api/review/context", { feature: "999-canvas-preview-fixture", stage: "specify" }));
+ assert.equal(response.status, 200);
+ assert.equal(response.headers.get("cache-control"), "no-store");
+ assert.equal(response.headers.get("x-content-type-options"), "nosniff");
+ const { data: opened } = await response.json();
+ assert.ok(opened.items.some((artifact) => artifact.relativePath.endsWith("/research.md")));
+ const current = await (await fetch(endpoint("/api/review/content", { context: opened.contextId, artifactId: opened.primaryArtifactId }))).json();
+ const legacy = await (await fetch(endpoint("/api/artifact", { feature: "999-canvas-preview-fixture", stage: "specify" }))).json();
+ assert.equal(current.data.content, legacy.content);
+ const linked = await fetch(endpoint("/api/review/resolve-link"), {
+ method: "POST", headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ contextId: opened.contextId, sourceArtifactId: opened.primaryArtifactId, expectedRevision: current.data.revision, target: "research.md#findings" }),
+ });
+ assert.equal(linked.status, 200);
+ assert.equal((await linked.json()).data.kind, "artifact");
+ const forbidden = endpoint("/api/review/artifacts", { context: opened.contextId });
+ forbidden.searchParams.delete("cap");
+ assert.equal((await fetch(forbidden)).status, 403);
+ const invalid = await fetch(endpoint("/api/review/artifacts", { context: "ctx_other_instance" }));
+ assert.equal(invalid.status, 404);
+ assert.equal((await invalid.json()).error.code, "invalid_context");
+ assert.equal(fixture.dispatchCount(), 0);
+ assert.equal(fixture.blockedWrites(), 0);
+ assert.equal(fixture.workspaceChanged(), false);
+ } finally { assert.equal((await fixture.stop()).cleaned, true); }
+});
+
+reviewFreshnessTests("SDD", new URL("../ui/artifact-review.js", import.meta.url).href);
+
+test("SDD refresh retains expected descriptors across atomic replacement and deletion", () => serviceFixture(async ({ root, service }) => {
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ const original = await service.content(opened.contextId, opened.primaryArtifactId);
+ await fs.writeFile(join(root, "replacement.md"), "# Atomic replacement\n");
+ await fs.rename(join(root, "replacement.md"), join(root, "specs/001-fixture/spec.md"));
+ await assert.rejects(service.content(opened.contextId, opened.primaryArtifactId, { expectedRevision: original.revision }), { code: "changed_source" });
+ await fs.rm(join(root, "specs/001-fixture/spec.md"));
+ const page = await service.list(opened.contextId);
+ assert.equal(page.items.find((artifact) => artifact.id === opened.primaryArtifactId)?.availability, "expected");
+ await assert.rejects(service.content(opened.contextId, opened.primaryArtifactId), { code: "artifact_unavailable" });
+}));
+
+test("SDD refresh signature detects scoped supporting changes without reading document bodies", () => serviceFixture(async ({ root, service }) => {
+ await fs.writeFile(join(root, "specs/001-fixture/research.md"), "# First\n");
+ await service.open({ feature: "001-fixture", stage: "specify" });
+ assert.equal(typeof service.signature, "function", "Scoped review signature is missing");
+ const first = await service.signature();
+ assert.match(first, /^[a-f0-9]{64}$/);
+ await fs.writeFile(join(root, "specs/001-fixture/research.md"), "# Different supporting document\n");
+ const second = await service.signature();
+ assert.notEqual(first, second);
+ await fs.writeFile(join(root, "specs/001-fixture/new.markdown"), "# New\n");
+ assert.notEqual(await service.signature(), second);
+}));
+
+test("SDD clarification bindings revalidate exact revision, index, and question without batching", () => serviceFixture(async ({ root, service }) => {
+ await fs.writeFile(join(root, "specs/001-fixture/spec.md"), "# Questions\n\n[NEEDS CLARIFICATION: Which scope?]\n\n`[NEEDS CLARIFICATION: Example?]`\n");
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ assert.equal(typeof service.clarifications, "function", "T068: revision-bound question descriptors are missing");
+ const bindings = await service.clarifications(opened.contextId, opened.primaryArtifactId);
+ assert.equal(bindings.length, 1);
+ assert.equal(bindings[0].mode, "sdd-immediate");
+ assert.equal(bindings[0].question, "Which scope?");
+ assert.equal(bindings[0].index, 0);
+ const request = { questionId: bindings[0].questionId, question: bindings[0].question, index: 0, answer: "Only this feature" };
+ const accepted = await service.validateClarifications(opened.contextId, opened.primaryArtifactId, bindings[0].revision, [request]);
+ assert.equal(accepted[0].answer, request.answer);
+ await assert.rejects(service.validateClarifications(opened.contextId, opened.primaryArtifactId, bindings[0].revision, [{ ...request, question: "Changed question" }]), { code: "changed_source" });
+ await assert.rejects(service.validateClarifications(opened.contextId, opened.primaryArtifactId, bindings[0].revision, [request, request]), { code: "invalid_request" });
+ await fs.writeFile(join(root, "specs/001-fixture/spec.md"), "# Changed\n\n[NEEDS CLARIFICATION: New question?]\n");
+ await assert.rejects(service.validateClarifications(opened.contextId, opened.primaryArtifactId, bindings[0].revision, [request]), { code: "changed_source" });
+}));
+
+test("SDD supporting documents never inherit primary-spec clarification actions", () => serviceFixture(async ({ root, service }) => {
+ await fs.writeFile(join(root, "specs/001-fixture/research.md"), "[NEEDS CLARIFICATION: Inert supporting question?]\n");
+ const opened = await service.open({ feature: "001-fixture", stage: "specify" });
+ assert.equal(typeof service.clarifications, "function", "T068: supporting-document clarification isolation is missing");
+ const research = opened.items.find((artifact) => artifact.relativePath.endsWith("/research.md"));
+ assert.deepEqual(await service.clarifications(opened.contextId, research.id), []);
+}));
+
+test("SDD immediate HTTP submission denies stale questions before the mocked SDK dispatch", async () => {
+ const { startFixture } = await import("../../../../../scripts/canvas-reader/serve-fixture.mjs");
+ const fixture = await startFixture({ canvas: "sdd", markdown: "# Scope\n\n[NEEDS CLARIFICATION: Which scope?]\n", allowMockDispatch: true });
+ const endpoint = (route, parameters = {}) => {
+ const url = new URL(fixture.url);
+ url.pathname = route;
+ for (const [key, value] of Object.entries(parameters)) url.searchParams.set(key, value);
+ return url;
+ };
+ try {
+ const { data: opened } = await (await fetch(endpoint("/api/review/context", { feature: "999-canvas-preview-fixture", stage: "specify" }))).json();
+ const { data: current } = await (await fetch(endpoint("/api/review/content", { context: opened.contextId, artifactId: opened.primaryArtifactId }))).json();
+ const question = current.clarifications[0];
+ const body = { feature: "999-canvas-preview-fixture", contextId: opened.contextId, artifactId: opened.primaryArtifactId,
+ expectedRevision: current.revision, questionId: question.questionId, index: question.index, question: question.question, answer: "Only this fixture" };
+ const submit = (value) => fetch(endpoint("/api/clarify"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(value) });
+ for (const invalid of [{ ...body, expectedRevision: `sha256:${"0".repeat(64)}` }, { ...body, question: "Other question" }, { ...body, index: 2 }]) {
+ assert.equal((await submit(invalid)).status, 400);
+ assert.equal(fixture.dispatchCount(), 0);
+ }
+ const response = await submit(body);
+ assert.equal(response.status, 200);
+ assert.equal((await response.json()).ok, true);
+ assert.equal(fixture.dispatchCount(), 1);
+ assert.equal(fixture.workspaceChanged(), false);
+ } finally { assert.equal((await fixture.stop()).cleaned, true); }
+});
diff --git a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/reader-probe.test.mjs b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/reader-probe.test.mjs
new file mode 100644
index 0000000..a2d7890
--- /dev/null
+++ b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/reader-probe.test.mjs
@@ -0,0 +1,82 @@
+import assert from "node:assert/strict";
+import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
+import { get as httpGet } from "node:http";
+import { registerHooks } from "node:module";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { test } from "node:test";
+
+test("SDD preserves its canvas actions and serves only guarded reader assets", async () => {
+ const workspace = await mkdtemp(join(tmpdir(), "sdd-reader-probe-"));
+ await mkdir(join(workspace, ".specify"));
+ await mkdir(join(workspace, "specs/999-canvas-preview-fixture"), { recursive: true });
+ await writeFile(join(workspace, "specs/999-canvas-preview-fixture/spec.md"), "# Owned SDD fixture\n");
+ const holder = {
+ canvases: [],
+ session: {
+ rpc: { metadata: { snapshot: async () => ({ workingDirectory: workspace }) } },
+ send: async () => assert.fail("Reader probe must not dispatch a workflow."),
+ log: async () => {},
+ },
+ };
+ globalThis.__sddReaderProbeTest = holder;
+ const mock = "export const createCanvas = definition => definition; export class CanvasError extends Error {} export async function joinSession(options) { const state = globalThis.__sddReaderProbeTest; state.canvases = options.canvases; return state.session; }";
+ const sdkUrl = `data:text/javascript,${encodeURIComponent(mock)}`;
+ const hooks = registerHooks({ resolve(specifier, context, nextResolve) {
+ if (specifier === "@github/copilot-sdk/extension") return { url: sdkUrl, shortCircuit: true };
+ return nextResolve(specifier, context);
+ } });
+ let canvas;
+ try {
+ await import("../extension.mjs?reader-probe-test");
+ canvas = holder.canvases[0];
+ assert.equal(canvas.id, "sdd-canvas");
+ assert.deepEqual(canvas.actions.map((action) => action.name), ["list_features", "setup_sdd", "clarify_item", "run_stage"]);
+ const first = new URL((await canvas.open({ instanceId: "probe-first" })).url);
+ const second = new URL((await canvas.open({ instanceId: "probe-second" })).url);
+ assert.deepEqual(canvas.inputSchema.properties, {});
+ const reopened = new URL((await canvas.open({ instanceId: "probe-second" })).url);
+ assert.equal(reopened.href, second.href);
+ async function request(path, cap = first.searchParams.get("cap"), headers = {}) {
+ const url = new URL(path, first.origin);
+ if (cap) url.searchParams.set("cap", cap);
+ if (headers.Host) {
+ return new Promise((resolve, reject) => {
+ const outgoing = httpGet(url, { headers }, (response) => {
+ response.resume();
+ resolve({ status: response.statusCode });
+ });
+ outgoing.on("error", () => reject(new Error("Owned SDD probe HTTP request failed.")));
+ });
+ }
+ try { return await fetch(url, { headers }); }
+ catch { throw new Error("Owned SDD probe HTTP request failed."); }
+ }
+ const assetPath = "/ui/vendor/markdown-reader/markdown-reader.js";
+ const script = await request(assetPath);
+ assert.equal(script.status, 200, "T014 has not exposed the guarded reader asset.");
+ assert.match(script.headers.get("content-type"), /javascript/);
+ assert.equal(script.headers.get("cache-control"), "no-store");
+ assert.equal(script.headers.get("referrer-policy"), "no-referrer");
+ assert.equal(script.headers.get("x-content-type-options"), "nosniff");
+ assert.match(await script.text(), /mountMarkdownReader/);
+ assert.equal((await request("/ui/vendor/markdown-reader/markdown-reader.css")).status, 200);
+ assert.equal((await request(assetPath, null)).status, 403);
+ assert.equal((await request(assetPath, second.searchParams.get("cap"))).status, 403);
+ assert.equal((await request(assetPath, undefined, { Origin: "https://example.invalid" })).status, 403);
+ assert.equal((await request(assetPath, undefined, { Host: "example.invalid" })).status, 403);
+ assert.equal((await request("/ui/vendor/markdown-reader/private.txt")).status, 404);
+ const legacy = await request("/api/artifact?feature=999-canvas-preview-fixture&stage=specify");
+ const artifact = await legacy.json();
+ assert.equal(artifact.ok, true);
+ assert.equal(artifact.content, "# Owned SDD fixture\n");
+ } finally {
+ if (canvas) {
+ await canvas.onClose({ instanceId: "probe-first" });
+ await canvas.onClose({ instanceId: "probe-second" });
+ }
+ hooks.deregister();
+ delete globalThis.__sddReaderProbeTest;
+ await rm(workspace, { recursive: true, force: true });
+ }
+});
diff --git a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/sdd.test.mjs b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/sdd.test.mjs
index f2a3b14..a650248 100644
--- a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/sdd.test.mjs
+++ b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/tests/sdd.test.mjs
@@ -1,3 +1,15 @@
+test("clarification controls ignore inline code, indented code, tilde fences, and comments", () => {
+ const markdown = [
+ "# Specification", "[NEEDS CLARIFICATION: Real question?]",
+ "`[NEEDS CLARIFICATION: Inline example?]`",
+ " [NEEDS CLARIFICATION: Indented example?]",
+ "~~~markdown\n[NEEDS CLARIFICATION: Fenced example?]\n~~~",
+ "",
+ "",
+ ].join("\n\n");
+ assert.deepEqual(extractClarifications(markdown).map((item) => item.question), ["Real question?"]);
+});
+
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, utimesSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
@@ -61,10 +73,11 @@ test("clarifications retain stable indices across supported markdown blocks", ()
]);
});
-test("dashboard gates setup controls and exposes stage status names", () => {
+test("dashboard keeps artifacts visible while setup gates execution and exposes stage status names", () => {
const html = readFileSync(new URL("../index.html", import.meta.url), "utf8");
- assert.match(html, /if \(!setupRequired\) state\.features\.forEach/);
+ assert.match(html, /\n state\.features\.forEach/);
+ assert.doesNotMatch(html, /if \(!setupRequired\) state\.features\.forEach/);
assert.match(html, /if \(STATE\?\.prerequisites\?\.setupRequired\) return false/);
assert.match(html, /p\.setAttribute\("aria-label", text \+ ": " \+ kind\)/);
assert.match(html, /button\.disabled = Boolean\(STATE\?\.prerequisites\?\.setupRequired\)/);
diff --git a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/ui/artifact-review.js b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/ui/artifact-review.js
new file mode 100644
index 0000000..2169384
--- /dev/null
+++ b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/ui/artifact-review.js
@@ -0,0 +1,418 @@
+const REVIEW_OUTCOMES = new Set(["invalid_request", "forbidden", "invalid_context", "artifact_unavailable", "changed_source",
+ "artifact_too_large", "unsupported_artifact", "invalid_encoding", "read_failed", "workspace_unavailable"]);
+
+function reviewError(code) {
+ const error = new Error("Artifact review request failed.");
+ error.code = REVIEW_OUTCOMES.has(code) ? code : "read_failed";
+ error.stack = `${error.name}: ${error.message}`;
+ return error;
+}
+
+export function createArtifactReview({
+ container, scrollElement, readerId, fetch: request, mount: suppliedMount,
+ headers = {}, onReturn, onDocument, getClarifications, onClarification, canNavigate,
+}) {
+ const document = container.ownerDocument;
+ const window = document.defaultView;
+ const fetchContent = request ?? window.fetch.bind(window);
+ let mounted;
+ let controller;
+ let generation = 0;
+ let returnTarget;
+ let returnScroll;
+ let activeContext;
+ let contextSelection;
+ let currentDocument;
+ let artifacts = [];
+ let history = [];
+ let historyIndex = -1;
+ let currentFragment = "";
+ let pendingRestore;
+ let selectedArtifactId;
+ let baseState = "idle";
+ let connectionState = "connected";
+ let refreshing = false;
+ let refreshAgain = false;
+ let renewContextQueued = false;
+
+ function urlFor(route, parameters = {}) {
+ const url = new URL(route, document.location.href);
+ const host = new URL(document.location.href);
+ for (const key of ["token", "cap"]) {
+ if (host.searchParams.has(key)) url.searchParams.set(key, host.searchParams.get(key));
+ }
+ for (const [key, value] of Object.entries(parameters)) {
+ if (value !== undefined && value !== null && value !== "") url.searchParams.set(key, String(value));
+ }
+ return url.href;
+ }
+
+ async function json(route, parameters, signal, body) {
+ try {
+ const response = await fetchContent(urlFor(route, parameters), {
+ signal, headers: body ? { ...headers, "Content-Type": "application/json" } : headers,
+ ...(body ? { method: "POST", body: JSON.stringify(body) } : {}),
+ });
+ const payload = await response.json();
+ if (!response.ok || !payload.ok) throw reviewError(payload?.error?.code);
+ return payload.data;
+ } catch (error) { throw reviewError(error?.code); }
+ }
+
+ async function loadMount() {
+ if (suppliedMount) return suppliedMount;
+ if (!document.querySelector("link[data-artifact-reader-css]")) {
+ const stylesheet = document.createElement("link");
+ stylesheet.rel = "stylesheet";
+ stylesheet.href = urlFor("/ui/vendor/markdown-reader/markdown-reader.css");
+ stylesheet.dataset.artifactReaderCss = "true";
+ document.head.append(stylesheet);
+ }
+ const module = await import(urlFor("/ui/vendor/markdown-reader/markdown-reader.js"));
+ return module.mountMarkdownReader;
+ }
+
+ function rememberReturn() {
+ if (returnTarget) return;
+ returnTarget = document.activeElement;
+ returnScroll = new Map([[scrollElement, { top: scrollElement.scrollTop, left: scrollElement.scrollLeft }]]);
+ for (let element = returnTarget?.parentElement; element; element = element.parentElement) {
+ if (!returnScroll.has(element)) returnScroll.set(element, { top: element.scrollTop, left: element.scrollLeft });
+ }
+ }
+
+ function optionsFor(content) {
+ const renderGeneration = generation;
+ return {
+ readerId, state: baseState, connectionState,
+ document: content ?? undefined, artifacts, selectedArtifactId, scrollElement,
+ clarifications: content ? getClarifications?.(content, activeContext) ?? content.clarifications ?? [] : [],
+ onClarification,
+ navigationEnabled: true, canNavigateBack: historyIndex > 0, canNavigateForward: historyIndex < history.length - 1,
+ fragment: currentFragment || undefined,
+ onSelectArtifact: (artifactId) => selectArtifact(artifactId),
+ onNavigateReference: (target) => navigateReference(target),
+ onNavigateHistory: (direction) => navigateHistory(direction),
+ onReturnToWorkflow: () => { if (onReturn) onReturn(); else close(); },
+ onRefresh: () => refresh({ renewContext: true }),
+ onRendered: (event) => {
+ if (renderGeneration !== generation || event.artifactId !== selectedArtifactId || event.revision !== currentDocument?.revision) return;
+ if (["ready", "no-heading", "empty"].includes(baseState)) {
+ baseState = event.state;
+ container.dataset.reviewState = baseState;
+ }
+ if (pendingRestore) {
+ if (!currentFragment) scrollElement.scrollTop = pendingRestore.offset;
+ pendingRestore = null;
+ }
+ },
+ };
+ }
+
+ async function showState(state, requestGeneration, content = null) {
+ if (requestGeneration !== generation) return false;
+ baseState = state;
+ currentDocument = content;
+ container.dataset.reviewState = state;
+ if (activeContext) container.dataset.reviewContext = activeContext.contextId;
+ try {
+ const options = optionsFor(content);
+ if (mounted) mounted.update(options);
+ else {
+ const mount = await loadMount();
+ if (requestGeneration !== generation || !container.isConnected) return false;
+ container.replaceChildren();
+ mounted = mount(container, options);
+ }
+ return true;
+ } catch {
+ if (requestGeneration !== generation || !container.isConnected) return false;
+ try { mounted?.unmount(); } catch {}
+ mounted = null;
+ baseState = "error";
+ currentDocument = null;
+ container.dataset.reviewState = "error";
+ const message = document.createElement("p");
+ message.setAttribute("role", "status");
+ message.textContent = "The artifact could not be read. Try refreshing it.";
+ container.replaceChildren(message);
+ return false;
+ }
+ }
+
+ function failureState(error, wasAvailable = false) {
+ if (error.code === "artifact_unavailable") return wasAvailable ? "deleted" : "missing";
+ if (["unsupported_artifact", "forbidden"].includes(error.code)) return "unsupported";
+ if (error.code === "changed_source") return "changed";
+ return "error";
+ }
+
+ async function allArtifacts(context, signal, requestGeneration) {
+ const first = await json("/api/review/artifacts", { context: context.contextId }, signal);
+ const items = first.items;
+ let cursor = first.nextCursor;
+ while (cursor) {
+ if (requestGeneration !== generation) return null;
+ const page = await json("/api/review/artifacts", { context: context.contextId, cursor }, signal);
+ items.push(...page.items);
+ cursor = page.nextCursor;
+ }
+ return requestGeneration === generation ? items : null;
+ }
+
+ function savePosition() {
+ const entry = history[historyIndex];
+ if (entry && currentDocument?.revision === entry.revision) {
+ entry.scrollOffset = scrollElement.scrollTop;
+ entry.logicalFragment = currentFragment;
+ }
+ }
+
+ async function present(content, requestGeneration, { targetIndex, fragment = "" } = {}) {
+ if (requestGeneration !== generation) return false;
+ const previous = targetIndex !== undefined ? history[targetIndex] : null;
+ const restore = previous?.revision === content.revision;
+ currentDocument = content;
+ selectedArtifactId = content.artifact.id;
+ baseState = content.content.trim() ? "ready" : "empty";
+ currentFragment = fragment || (restore ? previous.logicalFragment : "");
+ pendingRestore = { offset: restore ? previous.scrollOffset : 0 };
+ if (targetIndex !== undefined) {
+ historyIndex = targetIndex;
+ if (!restore) history[historyIndex] = { artifactId: content.artifact.id, revision: content.revision, scrollOffset: 0, logicalFragment: fragment };
+ } else if (history[historyIndex]?.artifactId !== content.artifact.id || history[historyIndex]?.revision !== content.revision) {
+ history = history.slice(0, historyIndex + 1);
+ history.push({ artifactId: content.artifact.id, revision: content.revision, scrollOffset: 0, logicalFragment: fragment });
+ if (history.length > 50) history.shift();
+ historyIndex = history.length - 1;
+ }
+ mounted?.unmount();
+ mounted = null;
+ container.replaceChildren();
+ container.dataset.reviewContext = activeContext.contextId;
+ container.dataset.reviewState = baseState;
+ if (await onDocument?.(content) === true) return true;
+ const mount = await loadMount();
+ if (requestGeneration !== generation || !container.isConnected) return false;
+ mounted = mount(container, optionsFor(content));
+ return true;
+ }
+
+ async function selectArtifact(artifactId, options = {}) {
+ if (canNavigate?.() === false) return false;
+ if (!activeContext || !artifacts.some((artifact) => artifact.id === artifactId && artifact.availability === "available")) return false;
+ savePosition();
+ const requestGeneration = ++generation;
+ controller?.abort();
+ controller = new AbortController();
+ const context = activeContext;
+ selectedArtifactId = artifactId;
+ currentFragment = options.fragment ?? "";
+ await showState("loading", requestGeneration);
+ if (requestGeneration !== generation) return false;
+ try {
+ const content = await json("/api/review/content", { context: context.contextId, artifactId }, controller.signal);
+ return await present(content, requestGeneration, options);
+ } catch (error) {
+ await showState(failureState(error, true), requestGeneration);
+ return false;
+ }
+ }
+
+ async function refresh({ renewContext = false } = {}) {
+ if (canNavigate?.() === false) return false;
+ if (!activeContext || !selectedArtifactId || baseState === "loading") return false;
+ if (refreshing) { refreshAgain = true; renewContextQueued ||= renewContext; return false; }
+ refreshing = true;
+ savePosition();
+ const context = activeContext;
+ const artifactId = selectedArtifactId;
+ const previousDocument = currentDocument;
+ const priorArtifact = artifacts.find((artifact) => artifact.id === artifactId);
+ const requestGeneration = ++generation;
+ controller?.abort();
+ controller = new AbortController();
+ const signal = controller.signal;
+ try {
+ const nextArtifacts = await allArtifacts(context, signal, requestGeneration);
+ if (!nextArtifacts || requestGeneration !== generation) return false;
+ if (priorArtifact?.role === "reference" && !nextArtifacts.some((artifact) => artifact.id === artifactId)) nextArtifacts.push(priorArtifact);
+ artifacts = nextArtifacts;
+ const selected = artifacts.find((artifact) => artifact.id === artifactId);
+ if (!selected || selected.availability !== "available") {
+ return showState(previousDocument || priorArtifact?.availability === "available" ? "deleted" : "missing", requestGeneration);
+ }
+ let content;
+ try {
+ content = await json("/api/review/content", { context: context.contextId, artifactId, expectedRevision: previousDocument?.revision }, signal);
+ } catch (error) {
+ if (error.code !== "changed_source") throw error;
+ await showState("changed", requestGeneration, previousDocument);
+ if (requestGeneration !== generation) return false;
+ const frame = window.requestAnimationFrame?.bind(window);
+ if (frame) await new Promise((resolve) => frame(() => frame(resolve)));
+ if (requestGeneration !== generation) return false;
+ content = await json("/api/review/content", { context: context.contextId, artifactId }, signal);
+ }
+ if (requestGeneration !== generation) return false;
+ if (previousDocument?.revision === content.revision) {
+ currentDocument = content;
+ container.dataset.reviewState = baseState;
+ mounted?.update(optionsFor(content));
+ return true;
+ }
+ return await present(content, requestGeneration, { targetIndex: historyIndex >= 0 ? historyIndex : undefined, fragment: currentFragment });
+ } catch (error) {
+ if (error.code === "invalid_context" && renewContext && requestGeneration === generation && contextSelection) {
+ return await review.open(contextSelection, { relativePath: priorArtifact?.relativePath, fragment: currentFragment });
+ }
+ await showState(failureState(error, Boolean(previousDocument)), requestGeneration,
+ error.code === "changed_source" ? previousDocument : null);
+ return false;
+ } finally {
+ refreshing = false;
+ if (refreshAgain) {
+ const retry = { renewContext: renewContextQueued };
+ refreshAgain = false;
+ renewContextQueued = false;
+ if (activeContext === context) void refresh(retry);
+ }
+ }
+ }
+
+ function setConnected(connected) {
+ connectionState = connected ? "connected" : "disconnected";
+ mounted?.update(optionsFor(currentDocument));
+ }
+
+ function navigateHistory(direction) {
+ const targetIndex = historyIndex + (direction === "back" ? -1 : direction === "forward" ? 1 : 0);
+ if (targetIndex < 0 || targetIndex >= history.length || targetIndex === historyIndex) return false;
+ return selectArtifact(history[targetIndex].artifactId, { targetIndex });
+ }
+
+ async function navigateReference(target) {
+ if (canNavigate?.() === false) return false;
+ if (!activeContext || !currentDocument) return false;
+ const requestGeneration = generation;
+ try {
+ const resolved = await json("/api/review/resolve-link", {}, controller?.signal, {
+ contextId: activeContext.contextId, sourceArtifactId: currentDocument.artifact.id,
+ expectedRevision: currentDocument.revision, target,
+ });
+ if (requestGeneration !== generation) return false;
+ if (resolved.kind === "fragment") {
+ currentFragment = resolved.fragment;
+ mounted?.update(optionsFor(currentDocument));
+ } else if (resolved.kind === "artifact") {
+ const index = artifacts.findIndex((artifact) => artifact.id === resolved.artifact.id);
+ if (index < 0) artifacts.push(resolved.artifact);
+ else artifacts[index] = resolved.artifact;
+ return selectArtifact(resolved.artifact.id, { fragment: resolved.fragment });
+ } else if (resolved.kind === "external" && resolved.requiresUserAction) {
+ window.open(resolved.url, "_blank", "noopener,noreferrer");
+ }
+ return resolved.kind !== "inert";
+ } catch { return false; }
+ }
+
+ function close({ restore = true } = {}) {
+ if (canNavigate?.() === false) return false;
+ generation++;
+ controller?.abort();
+ controller = null;
+ mounted?.unmount();
+ mounted = null;
+ activeContext = null;
+ contextSelection = null;
+ currentDocument = null;
+ artifacts = [];
+ history = [];
+ historyIndex = -1;
+ currentFragment = "";
+ pendingRestore = null;
+ selectedArtifactId = undefined;
+ baseState = "idle";
+ refreshAgain = false;
+ renewContextQueued = false;
+ container.replaceChildren();
+ delete container.dataset.reviewContext;
+ delete container.dataset.reviewState;
+ if (restore) {
+ if (returnTarget?.isConnected) returnTarget.focus({ preventScroll: true });
+ for (const [element, offset] of returnScroll ?? []) {
+ if (element.isConnected) { element.scrollTop = offset.top; element.scrollLeft = offset.left; }
+ }
+ }
+ returnTarget = null;
+ returnScroll = null;
+ }
+
+ const review = {
+ async open(selection, recovery = {}) {
+ if (canNavigate?.() === false) return false;
+ rememberReturn();
+ contextSelection = { ...selection };
+ const requestGeneration = ++generation;
+ controller?.abort();
+ controller = new AbortController();
+ const signal = controller.signal;
+ mounted?.unmount();
+ mounted = null;
+ activeContext = null;
+ currentDocument = null;
+ artifacts = [];
+ history = [];
+ historyIndex = -1;
+ selectedArtifactId = undefined;
+ currentFragment = "";
+ pendingRestore = null;
+ baseState = "loading";
+ delete container.dataset.reviewContext;
+ container.textContent = "Loading artifact...";
+ container.dataset.reviewState = "loading";
+ try {
+ const context = await json("/api/review/context", selection, signal);
+ if (requestGeneration !== generation) return false;
+ activeContext = context;
+ selectedArtifactId = context.primaryArtifactId;
+ artifacts = context.items;
+ let cursor = context.nextCursor;
+ while (cursor) {
+ const page = await json("/api/review/artifacts", { context: context.contextId, cursor }, signal);
+ if (requestGeneration !== generation) return false;
+ artifacts.push(...page.items);
+ cursor = page.nextCursor;
+ }
+ if (recovery.relativePath) {
+ selectedArtifactId = artifacts.find((artifact) => artifact.relativePath === recovery.relativePath)?.id;
+ if (!selectedArtifactId) return showState("missing", requestGeneration);
+ }
+ const content = await json("/api/review/content", { context: context.contextId, artifactId: selectedArtifactId }, signal);
+ return await present(content, requestGeneration, { fragment: recovery.fragment });
+ } catch (error) {
+ if (requestGeneration !== generation || signal.aborted) return false;
+ await showState(failureState(error), requestGeneration);
+ return false;
+ }
+ },
+ close,
+ selectArtifact,
+ navigateHistory,
+ navigateReference,
+ refresh,
+ setConnected,
+ updateControls() { mounted?.update(optionsFor(currentDocument)); },
+ async validateClarifications(answers, commandName) {
+ if (!activeContext || !currentDocument) throw reviewError("invalid_context");
+ return json("/api/review/validate-clarifications", {}, controller?.signal, {
+ contextId: activeContext.contextId, artifactId: currentDocument.artifact.id,
+ expectedRevision: currentDocument.revision, answers, commandName,
+ });
+ },
+ get history() { return history.map((entry) => ({ ...entry })); },
+ get document() { return currentDocument; },
+ get context() { return activeContext; },
+ };
+ return review;
+}
diff --git a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/vendor/artifact-clarifications.NOTICES.txt b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/vendor/artifact-clarifications.NOTICES.txt
new file mode 100644
index 0000000..9b70b7a
--- /dev/null
+++ b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/vendor/artifact-clarifications.NOTICES.txt
@@ -0,0 +1,1268 @@
+Third-party licenses for the bundled server Markdown parser.
+
+bail@2.0.2 (MIT)
+(The MIT License)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ccount@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+character-entities@2.0.2 (MIT)
+(The MIT License)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+decode-named-character-reference@1.3.0 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+devlop@1.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2023 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+escape-string-regexp@5.0.0 (MIT)
+MIT License
+
+Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+extend@3.0.2 (MIT)
+The MIT License (MIT)
+
+Copyright (c) 2014 Stefan Thomas
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+is-plain-obj@4.1.0 (MIT)
+MIT License
+
+Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+longest-streak@3.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+markdown-table@3.0.4 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-find-and-replace@3.0.2 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-from-markdown@2.0.3 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-gfm-autolink-literal@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) 2020 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-gfm-footnote@2.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-gfm-strikethrough@2.0.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2020 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-gfm-table@2.0.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2020 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-gfm-task-list-item@2.0.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2020 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-gfm@3.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-phrasing@4.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2017 Titus Wormer
+Copyright (c) 2017 Victor Felder
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-to-markdown@2.1.2 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+mdast-util-to-string@4.0.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-core-commonmark@2.0.3 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-extension-gfm-autolink-literal@2.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2020 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-extension-gfm-footnote@2.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2021 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-extension-gfm-strikethrough@2.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2020 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-extension-gfm-table@2.1.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-extension-gfm-task-list-item@2.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2020 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-extension-gfm@3.0.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2020 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-factory-destination@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-factory-label@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-factory-space@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-factory-title@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-factory-whitespace@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-character@2.1.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-chunked@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-classify-character@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-combine-extensions@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-decode-numeric-character-reference@2.0.2 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-decode-string@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-html-tag-name@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-normalize-identifier@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-resolve-all@2.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark-util-subtokenize@2.1.0 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+micromark@4.0.2 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+remark-gfm@4.0.1 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+remark-parse@11.0.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2014 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+trough@2.2.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2016 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+unified@11.0.5 (MIT)
+(The MIT License)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+unist-util-is@6.0.1 (MIT)
+(The MIT license)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+unist-util-stringify-position@4.0.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2016 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+unist-util-visit-parents@6.0.2 (MIT)
+(The MIT License)
+
+Copyright (c) 2016 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+unist-util-visit@5.0.0 (MIT)
+(The MIT License)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+vfile-message@4.0.3 (MIT)
+(The MIT License)
+
+Copyright (c) Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+vfile@6.0.3 (MIT)
+(The MIT License)
+
+Copyright (c) 2015 Titus Wormer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/vendor/artifact-clarifications.mjs b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/vendor/artifact-clarifications.mjs
new file mode 100644
index 0000000..c3805ac
--- /dev/null
+++ b/plugins/spec-kit-copilot-sdd/extensions/sdd-canvas/vendor/artifact-clarifications.mjs
@@ -0,0 +1,18446 @@
+import minpath from "node:path";
+import minproc from "node:process";
+import { fileURLToPath as urlToPath } from "node:url";
+//#region \0rolldown/runtime.js
+var __create = Object.create;
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __getProtoOf = Object.getPrototypeOf;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
+var __exportAll = (all, no_symbols) => {
+ let target = {};
+ for (var name in all) __defProp(target, name, {
+ get: all[name],
+ enumerable: true
+ });
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
+ return target;
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
+ key = keys[i];
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
+ get: ((k) => from[k]).bind(null, key),
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
+ });
+ }
+ return to;
+};
+var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
+ value: mod,
+ enumerable: true
+}) : target, mod));
+//#endregion
+//#region node_modules/bail/index.js
+/**
+* Throw a given error.
+*
+* @param {Error|null|undefined} [error]
+* Maybe error.
+* @returns {asserts error is null|undefined}
+*/
+function bail(error) {
+ if (error) throw error;
+}
+//#endregion
+//#region node_modules/extend/index.js
+var require_extend = /* @__PURE__ */ __commonJSMin(((exports, module) => {
+ var hasOwn = Object.prototype.hasOwnProperty;
+ var toStr = Object.prototype.toString;
+ var defineProperty = Object.defineProperty;
+ var gOPD = Object.getOwnPropertyDescriptor;
+ var isArray = function isArray(arr) {
+ if (typeof Array.isArray === "function") return Array.isArray(arr);
+ return toStr.call(arr) === "[object Array]";
+ };
+ var isPlainObject = function isPlainObject(obj) {
+ if (!obj || toStr.call(obj) !== "[object Object]") return false;
+ var hasOwnConstructor = hasOwn.call(obj, "constructor");
+ var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, "isPrototypeOf");
+ if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) return false;
+ var key;
+ for (key in obj);
+ return typeof key === "undefined" || hasOwn.call(obj, key);
+ };
+ var setProperty = function setProperty(target, options) {
+ if (defineProperty && options.name === "__proto__") defineProperty(target, options.name, {
+ enumerable: true,
+ configurable: true,
+ value: options.newValue,
+ writable: true
+ });
+ else target[options.name] = options.newValue;
+ };
+ var getProperty = function getProperty(obj, name) {
+ if (name === "__proto__") {
+ if (!hasOwn.call(obj, name)) return;
+ else if (gOPD) return gOPD(obj, name).value;
+ }
+ return obj[name];
+ };
+ module.exports = function extend() {
+ var options, name, src, copy, copyIsArray, clone;
+ var target = arguments[0];
+ var i = 1;
+ var length = arguments.length;
+ var deep = false;
+ if (typeof target === "boolean") {
+ deep = target;
+ target = arguments[1] || {};
+ i = 2;
+ }
+ if (target == null || typeof target !== "object" && typeof target !== "function") target = {};
+ for (; i < length; ++i) {
+ options = arguments[i];
+ if (options != null) for (name in options) {
+ src = getProperty(target, name);
+ copy = getProperty(options, name);
+ if (target !== copy) {
+ if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
+ if (copyIsArray) {
+ copyIsArray = false;
+ clone = src && isArray(src) ? src : [];
+ } else clone = src && isPlainObject(src) ? src : {};
+ setProperty(target, {
+ name,
+ newValue: extend(deep, clone, copy)
+ });
+ } else if (typeof copy !== "undefined") setProperty(target, {
+ name,
+ newValue: copy
+ });
+ }
+ }
+ }
+ return target;
+ };
+}));
+//#endregion
+//#region node_modules/is-plain-obj/index.js
+function isPlainObject(value) {
+ if (typeof value !== "object" || value === null) return false;
+ const prototype = Object.getPrototypeOf(value);
+ return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
+}
+//#endregion
+//#region node_modules/trough/lib/index.js
+/**
+* @typedef {(error?: Error | null | undefined, ...output: Array) => void} Callback
+* Callback.
+*
+* @typedef {(...input: Array) => any} Middleware
+* Ware.
+*
+* @typedef Pipeline
+* Pipeline.
+* @property {Run} run
+* Run the pipeline.
+* @property {Use} use
+* Add middleware.
+*
+* @typedef {(...input: Array) => void} Run
+* Call all middleware.
+*
+* Calls `done` on completion with either an error or the output of the
+* last middleware.
+*
+* > 👉 **Note**: as the length of input defines whether async functions get a
+* > `next` function,
+* > it’s recommended to keep `input` at one value normally.
+
+*
+* @typedef {(fn: Middleware) => Pipeline} Use
+* Add middleware.
+*/
+/**
+* Create new middleware.
+*
+* @returns {Pipeline}
+* Pipeline.
+*/
+function trough() {
+ /** @type {Array} */
+ const fns = [];
+ /** @type {Pipeline} */
+ const pipeline = {
+ run,
+ use
+ };
+ return pipeline;
+ /** @type {Run} */
+ function run(...values) {
+ let middlewareIndex = -1;
+ /** @type {Callback} */
+ const callback = values.pop();
+ if (typeof callback !== "function") throw new TypeError("Expected function as last argument, not " + callback);
+ next(null, ...values);
+ /**
+ * Run the next `fn`, or we’re done.
+ *
+ * @param {Error | null | undefined} error
+ * @param {Array} output
+ */
+ function next(error, ...output) {
+ const fn = fns[++middlewareIndex];
+ let index = -1;
+ if (error) {
+ callback(error);
+ return;
+ }
+ while (++index < values.length) if (output[index] === null || output[index] === void 0) output[index] = values[index];
+ values = output;
+ if (fn) wrap(fn, next)(...output);
+ else callback(null, ...output);
+ }
+ }
+ /** @type {Use} */
+ function use(middelware) {
+ if (typeof middelware !== "function") throw new TypeError("Expected `middelware` to be a function, not " + middelware);
+ fns.push(middelware);
+ return pipeline;
+ }
+}
+/**
+* Wrap `middleware` into a uniform interface.
+*
+* You can pass all input to the resulting function.
+* `callback` is then called with the output of `middleware`.
+*
+* If `middleware` accepts more arguments than the later given in input,
+* an extra `done` function is passed to it after that input,
+* which must be called by `middleware`.
+*
+* The first value in `input` is the main input value.
+* All other input values are the rest input values.
+* The values given to `callback` are the input values,
+* merged with every non-nullish output value.
+*
+* * if `middleware` throws an error,
+* returns a promise that is rejected,
+* or calls the given `done` function with an error,
+* `callback` is called with that error
+* * if `middleware` returns a value or returns a promise that is resolved,
+* that value is the main output value
+* * if `middleware` calls `done`,
+* all non-nullish values except for the first one (the error) overwrite the
+* output values
+*
+* @param {Middleware} middleware
+* Function to wrap.
+* @param {Callback} callback
+* Callback called with the output of `middleware`.
+* @returns {Run}
+* Wrapped middleware.
+*/
+function wrap(middleware, callback) {
+ /** @type {boolean} */
+ let called;
+ return wrapped;
+ /**
+ * Call `middleware`.
+ * @this {any}
+ * @param {Array} parameters
+ * @returns {void}
+ */
+ function wrapped(...parameters) {
+ const fnExpectsCallback = middleware.length > parameters.length;
+ /** @type {any} */
+ let result;
+ if (fnExpectsCallback) parameters.push(done);
+ try {
+ result = middleware.apply(this, parameters);
+ } catch (error) {
+ const exception = error;
+ if (fnExpectsCallback && called) throw exception;
+ return done(exception);
+ }
+ if (!fnExpectsCallback) if (result && result.then && typeof result.then === "function") result.then(then, done);
+ else if (result instanceof Error) done(result);
+ else then(result);
+ }
+ /**
+ * Call `callback`, only once.
+ *
+ * @type {Callback}
+ */
+ function done(error, ...output) {
+ if (!called) {
+ called = true;
+ callback(error, ...output);
+ }
+ }
+ /**
+ * Call `done` with one value.
+ *
+ * @param {any} [value]
+ */
+ function then(value) {
+ done(null, value);
+ }
+}
+//#endregion
+//#region node_modules/unist-util-stringify-position/lib/index.js
+/**
+* @typedef {import('unist').Node} Node
+* @typedef {import('unist').Point} Point
+* @typedef {import('unist').Position} Position
+*/
+/**
+* @typedef NodeLike
+* @property {string} type
+* @property {PositionLike | null | undefined} [position]
+*
+* @typedef PointLike
+* @property {number | null | undefined} [line]
+* @property {number | null | undefined} [column]
+* @property {number | null | undefined} [offset]
+*
+* @typedef PositionLike
+* @property {PointLike | null | undefined} [start]
+* @property {PointLike | null | undefined} [end]
+*/
+/**
+* Serialize the positional info of a point, position (start and end points),
+* or node.
+*
+* @param {Node | NodeLike | Point | PointLike | Position | PositionLike | null | undefined} [value]
+* Node, position, or point.
+* @returns {string}
+* Pretty printed positional info of a node (`string`).
+*
+* In the format of a range `ls:cs-le:ce` (when given `node` or `position`)
+* or a point `l:c` (when given `point`), where `l` stands for line, `c` for
+* column, `s` for `start`, and `e` for end.
+* An empty string (`''`) is returned if the given value is neither `node`,
+* `position`, nor `point`.
+*/
+function stringifyPosition(value) {
+ if (!value || typeof value !== "object") return "";
+ if ("position" in value || "type" in value) return position(value.position);
+ if ("start" in value || "end" in value) return position(value);
+ if ("line" in value || "column" in value) return point$1(value);
+ return "";
+}
+/**
+* @param {Point | PointLike | null | undefined} point
+* @returns {string}
+*/
+function point$1(point) {
+ return index(point && point.line) + ":" + index(point && point.column);
+}
+/**
+* @param {Position | PositionLike | null | undefined} pos
+* @returns {string}
+*/
+function position(pos) {
+ return point$1(pos && pos.start) + "-" + point$1(pos && pos.end);
+}
+/**
+* @param {number | null | undefined} value
+* @returns {number}
+*/
+function index(value) {
+ return value && typeof value === "number" ? value : 1;
+}
+//#endregion
+//#region node_modules/vfile-message/lib/index.js
+/**
+* @import {Node, Point, Position} from 'unist'
+*/
+/**
+* @typedef {object & {type: string, position?: Position | undefined}} NodeLike
+*
+* @typedef Options
+* Configuration.
+* @property {Array | null | undefined} [ancestors]
+* Stack of (inclusive) ancestor nodes surrounding the message (optional).
+* @property {Error | null | undefined} [cause]
+* Original error cause of the message (optional).
+* @property {Point | Position | null | undefined} [place]
+* Place of message (optional).
+* @property {string | null | undefined} [ruleId]
+* Category of message (optional, example: `'my-rule'`).
+* @property {string | null | undefined} [source]
+* Namespace of who sent the message (optional, example: `'my-package'`).
+*/
+/**
+* Message.
+*/
+var VFileMessage = class extends Error {
+ /**
+ * Create a message for `reason`.
+ *
+ * > 🪦 **Note**: also has obsolete signatures.
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Options | null | undefined} [options]
+ * @returns
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Node | NodeLike | null | undefined} parent
+ * @param {string | null | undefined} [origin]
+ * @returns
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Point | Position | null | undefined} place
+ * @param {string | null | undefined} [origin]
+ * @returns
+ *
+ * @overload
+ * @param {string} reason
+ * @param {string | null | undefined} [origin]
+ * @returns
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {Node | NodeLike | null | undefined} parent
+ * @param {string | null | undefined} [origin]
+ * @returns
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {Point | Position | null | undefined} place
+ * @param {string | null | undefined} [origin]
+ * @returns
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {string | null | undefined} [origin]
+ * @returns
+ *
+ * @param {Error | VFileMessage | string} causeOrReason
+ * Reason for message, should use markdown.
+ * @param {Node | NodeLike | Options | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
+ * Configuration (optional).
+ * @param {string | null | undefined} [origin]
+ * Place in code where the message originates (example:
+ * `'my-package:my-rule'` or `'my-rule'`).
+ * @returns
+ * Instance of `VFileMessage`.
+ */
+ constructor(causeOrReason, optionsOrParentOrPlace, origin) {
+ super();
+ if (typeof optionsOrParentOrPlace === "string") {
+ origin = optionsOrParentOrPlace;
+ optionsOrParentOrPlace = void 0;
+ }
+ /** @type {string} */
+ let reason = "";
+ /** @type {Options} */
+ let options = {};
+ let legacyCause = false;
+ if (optionsOrParentOrPlace) if ("line" in optionsOrParentOrPlace && "column" in optionsOrParentOrPlace) options = { place: optionsOrParentOrPlace };
+ else if ("start" in optionsOrParentOrPlace && "end" in optionsOrParentOrPlace) options = { place: optionsOrParentOrPlace };
+ else if ("type" in optionsOrParentOrPlace) options = {
+ ancestors: [optionsOrParentOrPlace],
+ place: optionsOrParentOrPlace.position
+ };
+ else options = { ...optionsOrParentOrPlace };
+ if (typeof causeOrReason === "string") reason = causeOrReason;
+ else if (!options.cause && causeOrReason) {
+ legacyCause = true;
+ reason = causeOrReason.message;
+ options.cause = causeOrReason;
+ }
+ if (!options.ruleId && !options.source && typeof origin === "string") {
+ const index = origin.indexOf(":");
+ if (index === -1) options.ruleId = origin;
+ else {
+ options.source = origin.slice(0, index);
+ options.ruleId = origin.slice(index + 1);
+ }
+ }
+ if (!options.place && options.ancestors && options.ancestors) {
+ const parent = options.ancestors[options.ancestors.length - 1];
+ if (parent) options.place = parent.position;
+ }
+ const start = options.place && "start" in options.place ? options.place.start : options.place;
+ /**
+ * Stack of ancestor nodes surrounding the message.
+ *
+ * @type {Array | undefined}
+ */
+ this.ancestors = options.ancestors || void 0;
+ /**
+ * Original error cause of the message.
+ *
+ * @type {Error | undefined}
+ */
+ this.cause = options.cause || void 0;
+ /**
+ * Starting column of message.
+ *
+ * @type {number | undefined}
+ */
+ this.column = start ? start.column : void 0;
+ /**
+ * State of problem.
+ *
+ * * `true` — error, file not usable
+ * * `false` — warning, change may be needed
+ * * `undefined` — change likely not needed
+ *
+ * @type {boolean | null | undefined}
+ */
+ this.fatal = void 0;
+ /**
+ * Path of a file (used throughout the `VFile` ecosystem).
+ *
+ * @type {string | undefined}
+ */
+ this.file = "";
+ /**
+ * Reason for message.
+ *
+ * @type {string}
+ */
+ this.message = reason;
+ /**
+ * Starting line of error.
+ *
+ * @type {number | undefined}
+ */
+ this.line = start ? start.line : void 0;
+ /**
+ * Serialized positional info of message.
+ *
+ * On normal errors, this would be something like `ParseError`, buit in
+ * `VFile` messages we use this space to show where an error happened.
+ */
+ this.name = stringifyPosition(options.place) || "1:1";
+ /**
+ * Place of message.
+ *
+ * @type {Point | Position | undefined}
+ */
+ this.place = options.place || void 0;
+ /**
+ * Reason for message, should use markdown.
+ *
+ * @type {string}
+ */
+ this.reason = this.message;
+ /**
+ * Category of message (example: `'my-rule'`).
+ *
+ * @type {string | undefined}
+ */
+ this.ruleId = options.ruleId || void 0;
+ /**
+ * Namespace of message (example: `'my-package'`).
+ *
+ * @type {string | undefined}
+ */
+ this.source = options.source || void 0;
+ /**
+ * Stack of message.
+ *
+ * This is used by normal errors to show where something happened in
+ * programming code, irrelevant for `VFile` messages,
+ *
+ * @type {string}
+ */
+ this.stack = legacyCause && options.cause && typeof options.cause.stack === "string" ? options.cause.stack : "";
+ /**
+ * Specify the source value that’s being reported, which is deemed
+ * incorrect.
+ *
+ * @type {string | undefined}
+ */
+ this.actual = void 0;
+ /**
+ * Suggest acceptable values that can be used instead of `actual`.
+ *
+ * @type {Array | undefined}
+ */
+ this.expected = void 0;
+ /**
+ * Long form description of the message (you should use markdown).
+ *
+ * @type {string | undefined}
+ */
+ this.note = void 0;
+ /**
+ * Link to docs for the message.
+ *
+ * > 👉 **Note**: this must be an absolute URL that can be passed as `x`
+ * > to `new URL(x)`.
+ *
+ * @type {string | undefined}
+ */
+ this.url = void 0;
+ }
+};
+VFileMessage.prototype.file = "";
+VFileMessage.prototype.name = "";
+VFileMessage.prototype.reason = "";
+VFileMessage.prototype.message = "";
+VFileMessage.prototype.stack = "";
+VFileMessage.prototype.column = void 0;
+VFileMessage.prototype.line = void 0;
+VFileMessage.prototype.ancestors = void 0;
+VFileMessage.prototype.cause = void 0;
+VFileMessage.prototype.fatal = void 0;
+VFileMessage.prototype.place = void 0;
+VFileMessage.prototype.ruleId = void 0;
+VFileMessage.prototype.source = void 0;
+//#endregion
+//#region node_modules/vfile/lib/minurl.shared.js
+/**
+* Checks if a value has the shape of a WHATWG URL object.
+*
+* Using a symbol or instanceof would not be able to recognize URL objects
+* coming from other implementations (e.g. in Electron), so instead we are
+* checking some well known properties for a lack of a better test.
+*
+* We use `href` and `protocol` as they are the only properties that are
+* easy to retrieve and calculate due to the lazy nature of the getters.
+*
+* We check for auth attribute to distinguish legacy url instance with
+* WHATWG URL instance.
+*
+* @param {unknown} fileUrlOrPath
+* File path or URL.
+* @returns {fileUrlOrPath is URL}
+* Whether it’s a URL.
+*/
+function isUrl(fileUrlOrPath) {
+ return Boolean(fileUrlOrPath !== null && typeof fileUrlOrPath === "object" && "href" in fileUrlOrPath && fileUrlOrPath.href && "protocol" in fileUrlOrPath && fileUrlOrPath.protocol && fileUrlOrPath.auth === void 0);
+}
+//#endregion
+//#region node_modules/vfile/lib/index.js
+/**
+* @import {Node, Point, Position} from 'unist'
+* @import {Options as MessageOptions} from 'vfile-message'
+* @import {Compatible, Data, Map, Options, Value} from 'vfile'
+*/
+/**
+* @typedef {object & {type: string, position?: Position | undefined}} NodeLike
+*/
+/**
+* Order of setting (least specific to most), we need this because otherwise
+* `{stem: 'a', path: '~/b.js'}` would throw, as a path is needed before a
+* stem can be set.
+*/
+var order = [
+ "history",
+ "path",
+ "basename",
+ "stem",
+ "extname",
+ "dirname"
+];
+var VFile = class {
+ /**
+ * Create a new virtual file.
+ *
+ * `options` is treated as:
+ *
+ * * `string` or `Uint8Array` — `{value: options}`
+ * * `URL` — `{path: options}`
+ * * `VFile` — shallow copies its data over to the new file
+ * * `object` — all fields are shallow copied over to the new file
+ *
+ * Path related fields are set in the following order (least specific to
+ * most specific): `history`, `path`, `basename`, `stem`, `extname`,
+ * `dirname`.
+ *
+ * You cannot set `dirname` or `extname` without setting either `history`,
+ * `path`, `basename`, or `stem` too.
+ *
+ * @param {Compatible | null | undefined} [value]
+ * File value.
+ * @returns
+ * New instance.
+ */
+ constructor(value) {
+ /** @type {Options | VFile} */
+ let options;
+ if (!value) options = {};
+ else if (isUrl(value)) options = { path: value };
+ else if (typeof value === "string" || isUint8Array$1(value)) options = { value };
+ else options = value;
+ /**
+ * Base of `path` (default: `process.cwd()` or `'/'` in browsers).
+ *
+ * @type {string}
+ */
+ this.cwd = "cwd" in options ? "" : minproc.cwd();
+ /**
+ * Place to store custom info (default: `{}`).
+ *
+ * It’s OK to store custom data directly on the file but moving it to
+ * `data` is recommended.
+ *
+ * @type {Data}
+ */
+ this.data = {};
+ /**
+ * List of file paths the file moved between.
+ *
+ * The first is the original path and the last is the current path.
+ *
+ * @type {Array}
+ */
+ this.history = [];
+ /**
+ * List of messages associated with the file.
+ *
+ * @type {Array}
+ */
+ this.messages = [];
+ /**
+ * Raw value.
+ *
+ * @type {Value}
+ */
+ this.value;
+ /**
+ * Source map.
+ *
+ * This type is equivalent to the `RawSourceMap` type from the `source-map`
+ * module.
+ *
+ * @type {Map | null | undefined}
+ */
+ this.map;
+ /**
+ * Custom, non-string, compiled, representation.
+ *
+ * This is used by unified to store non-string results.
+ * One example is when turning markdown into React nodes.
+ *
+ * @type {unknown}
+ */
+ this.result;
+ /**
+ * Whether a file was saved to disk.
+ *
+ * This is used by vfile reporters.
+ *
+ * @type {boolean}
+ */
+ this.stored;
+ let index = -1;
+ while (++index < order.length) {
+ const field = order[index];
+ if (field in options && options[field] !== void 0 && options[field] !== null) this[field] = field === "history" ? [...options[field]] : options[field];
+ }
+ /** @type {string} */
+ let field;
+ for (field in options) if (!order.includes(field)) this[field] = options[field];
+ }
+ /**
+ * Get the basename (including extname) (example: `'index.min.js'`).
+ *
+ * @returns {string | undefined}
+ * Basename.
+ */
+ get basename() {
+ return typeof this.path === "string" ? minpath.basename(this.path) : void 0;
+ }
+ /**
+ * Set basename (including extname) (`'index.min.js'`).
+ *
+ * Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
+ * on windows).
+ * Cannot be nullified (use `file.path = file.dirname` instead).
+ *
+ * @param {string} basename
+ * Basename.
+ * @returns {undefined}
+ * Nothing.
+ */
+ set basename(basename) {
+ assertNonEmpty(basename, "basename");
+ assertPart(basename, "basename");
+ this.path = minpath.join(this.dirname || "", basename);
+ }
+ /**
+ * Get the parent path (example: `'~'`).
+ *
+ * @returns {string | undefined}
+ * Dirname.
+ */
+ get dirname() {
+ return typeof this.path === "string" ? minpath.dirname(this.path) : void 0;
+ }
+ /**
+ * Set the parent path (example: `'~'`).
+ *
+ * Cannot be set if there’s no `path` yet.
+ *
+ * @param {string | undefined} dirname
+ * Dirname.
+ * @returns {undefined}
+ * Nothing.
+ */
+ set dirname(dirname) {
+ assertPath(this.basename, "dirname");
+ this.path = minpath.join(dirname || "", this.basename);
+ }
+ /**
+ * Get the extname (including dot) (example: `'.js'`).
+ *
+ * @returns {string | undefined}
+ * Extname.
+ */
+ get extname() {
+ return typeof this.path === "string" ? minpath.extname(this.path) : void 0;
+ }
+ /**
+ * Set the extname (including dot) (example: `'.js'`).
+ *
+ * Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
+ * on windows).
+ * Cannot be set if there’s no `path` yet.
+ *
+ * @param {string | undefined} extname
+ * Extname.
+ * @returns {undefined}
+ * Nothing.
+ */
+ set extname(extname) {
+ assertPart(extname, "extname");
+ assertPath(this.dirname, "extname");
+ if (extname) {
+ if (extname.codePointAt(0) !== 46) throw new Error("`extname` must start with `.`");
+ if (extname.includes(".", 1)) throw new Error("`extname` cannot contain multiple dots");
+ }
+ this.path = minpath.join(this.dirname, this.stem + (extname || ""));
+ }
+ /**
+ * Get the full path (example: `'~/index.min.js'`).
+ *
+ * @returns {string}
+ * Path.
+ */
+ get path() {
+ return this.history[this.history.length - 1];
+ }
+ /**
+ * Set the full path (example: `'~/index.min.js'`).
+ *
+ * Cannot be nullified.
+ * You can set a file URL (a `URL` object with a `file:` protocol) which will
+ * be turned into a path with `url.fileURLToPath`.
+ *
+ * @param {URL | string} path
+ * Path.
+ * @returns {undefined}
+ * Nothing.
+ */
+ set path(path) {
+ if (isUrl(path)) path = urlToPath(path);
+ assertNonEmpty(path, "path");
+ if (this.path !== path) this.history.push(path);
+ }
+ /**
+ * Get the stem (basename w/o extname) (example: `'index.min'`).
+ *
+ * @returns {string | undefined}
+ * Stem.
+ */
+ get stem() {
+ return typeof this.path === "string" ? minpath.basename(this.path, this.extname) : void 0;
+ }
+ /**
+ * Set the stem (basename w/o extname) (example: `'index.min'`).
+ *
+ * Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
+ * on windows).
+ * Cannot be nullified (use `file.path = file.dirname` instead).
+ *
+ * @param {string} stem
+ * Stem.
+ * @returns {undefined}
+ * Nothing.
+ */
+ set stem(stem) {
+ assertNonEmpty(stem, "stem");
+ assertPart(stem, "stem");
+ this.path = minpath.join(this.dirname || "", stem + (this.extname || ""));
+ }
+ /**
+ * Create a fatal message for `reason` associated with the file.
+ *
+ * The `fatal` field of the message is set to `true` (error; file not usable)
+ * and the `file` field is set to the current file path.
+ * The message is added to the `messages` field on `file`.
+ *
+ * > 🪦 **Note**: also has obsolete signatures.
+ *
+ * @overload
+ * @param {string} reason
+ * @param {MessageOptions | null | undefined} [options]
+ * @returns {never}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Node | NodeLike | null | undefined} parent
+ * @param {string | null | undefined} [origin]
+ * @returns {never}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Point | Position | null | undefined} place
+ * @param {string | null | undefined} [origin]
+ * @returns {never}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {string | null | undefined} [origin]
+ * @returns {never}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {Node | NodeLike | null | undefined} parent
+ * @param {string | null | undefined} [origin]
+ * @returns {never}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {Point | Position | null | undefined} place
+ * @param {string | null | undefined} [origin]
+ * @returns {never}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {string | null | undefined} [origin]
+ * @returns {never}
+ *
+ * @param {Error | VFileMessage | string} causeOrReason
+ * Reason for message, should use markdown.
+ * @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
+ * Configuration (optional).
+ * @param {string | null | undefined} [origin]
+ * Place in code where the message originates (example:
+ * `'my-package:my-rule'` or `'my-rule'`).
+ * @returns {never}
+ * Never.
+ * @throws {VFileMessage}
+ * Message.
+ */
+ fail(causeOrReason, optionsOrParentOrPlace, origin) {
+ const message = this.message(causeOrReason, optionsOrParentOrPlace, origin);
+ message.fatal = true;
+ throw message;
+ }
+ /**
+ * Create an info message for `reason` associated with the file.
+ *
+ * The `fatal` field of the message is set to `undefined` (info; change
+ * likely not needed) and the `file` field is set to the current file path.
+ * The message is added to the `messages` field on `file`.
+ *
+ * > 🪦 **Note**: also has obsolete signatures.
+ *
+ * @overload
+ * @param {string} reason
+ * @param {MessageOptions | null | undefined} [options]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Node | NodeLike | null | undefined} parent
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Point | Position | null | undefined} place
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {Node | NodeLike | null | undefined} parent
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {Point | Position | null | undefined} place
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @param {Error | VFileMessage | string} causeOrReason
+ * Reason for message, should use markdown.
+ * @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
+ * Configuration (optional).
+ * @param {string | null | undefined} [origin]
+ * Place in code where the message originates (example:
+ * `'my-package:my-rule'` or `'my-rule'`).
+ * @returns {VFileMessage}
+ * Message.
+ */
+ info(causeOrReason, optionsOrParentOrPlace, origin) {
+ const message = this.message(causeOrReason, optionsOrParentOrPlace, origin);
+ message.fatal = void 0;
+ return message;
+ }
+ /**
+ * Create a message for `reason` associated with the file.
+ *
+ * The `fatal` field of the message is set to `false` (warning; change may be
+ * needed) and the `file` field is set to the current file path.
+ * The message is added to the `messages` field on `file`.
+ *
+ * > 🪦 **Note**: also has obsolete signatures.
+ *
+ * @overload
+ * @param {string} reason
+ * @param {MessageOptions | null | undefined} [options]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Node | NodeLike | null | undefined} parent
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {Point | Position | null | undefined} place
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {string} reason
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {Node | NodeLike | null | undefined} parent
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {Point | Position | null | undefined} place
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @overload
+ * @param {Error | VFileMessage} cause
+ * @param {string | null | undefined} [origin]
+ * @returns {VFileMessage}
+ *
+ * @param {Error | VFileMessage | string} causeOrReason
+ * Reason for message, should use markdown.
+ * @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
+ * Configuration (optional).
+ * @param {string | null | undefined} [origin]
+ * Place in code where the message originates (example:
+ * `'my-package:my-rule'` or `'my-rule'`).
+ * @returns {VFileMessage}
+ * Message.
+ */
+ message(causeOrReason, optionsOrParentOrPlace, origin) {
+ const message = new VFileMessage(causeOrReason, optionsOrParentOrPlace, origin);
+ if (this.path) {
+ message.name = this.path + ":" + message.name;
+ message.file = this.path;
+ }
+ message.fatal = false;
+ this.messages.push(message);
+ return message;
+ }
+ /**
+ * Serialize the file.
+ *
+ * > **Note**: which encodings are supported depends on the engine.
+ * > For info on Node.js, see:
+ * > .
+ *
+ * @param {string | null | undefined} [encoding='utf8']
+ * Character encoding to understand `value` as when it’s a `Uint8Array`
+ * (default: `'utf-8'`).
+ * @returns {string}
+ * Serialized file.
+ */
+ toString(encoding) {
+ if (this.value === void 0) return "";
+ if (typeof this.value === "string") return this.value;
+ return new TextDecoder(encoding || void 0).decode(this.value);
+ }
+};
+/**
+* Assert that `part` is not a path (as in, does not contain `path.sep`).
+*
+* @param {string | null | undefined} part
+* File path part.
+* @param {string} name
+* Part name.
+* @returns {undefined}
+* Nothing.
+*/
+function assertPart(part, name) {
+ if (part && part.includes(minpath.sep)) throw new Error("`" + name + "` cannot be a path: did not expect `" + minpath.sep + "`");
+}
+/**
+* Assert that `part` is not empty.
+*
+* @param {string | undefined} part
+* Thing.
+* @param {string} name
+* Part name.
+* @returns {asserts part is string}
+* Nothing.
+*/
+function assertNonEmpty(part, name) {
+ if (!part) throw new Error("`" + name + "` cannot be empty");
+}
+/**
+* Assert `path` exists.
+*
+* @param {string | undefined} path
+* Path.
+* @param {string} name
+* Dependency name.
+* @returns {asserts path is string}
+* Nothing.
+*/
+function assertPath(path, name) {
+ if (!path) throw new Error("Setting `" + name + "` requires `path` to be set too");
+}
+/**
+* Assert `value` is an `Uint8Array`.
+*
+* @param {unknown} value
+* thing.
+* @returns {value is Uint8Array}
+* Whether `value` is an `Uint8Array`.
+*/
+function isUint8Array$1(value) {
+ return Boolean(value && typeof value === "object" && "byteLength" in value && "byteOffset" in value);
+}
+//#endregion
+//#region node_modules/unified/lib/callable-instance.js
+var CallableInstance = (
+/**
+* @this {Function}
+* @param {string | symbol} property
+* @returns {(...parameters: Array) => unknown}
+*/
+function(property) {
+ const proto = this.constructor.prototype;
+ const value = proto[property];
+ /** @type {(...parameters: Array) => unknown} */
+ const apply = function() {
+ return value.apply(apply, arguments);
+ };
+ Object.setPrototypeOf(apply, proto);
+ return apply;
+});
+//#endregion
+//#region node_modules/unified/lib/index.js
+/**
+* @typedef {import('trough').Pipeline} Pipeline
+*
+* @typedef {import('unist').Node} Node
+*
+* @typedef {import('vfile').Compatible} Compatible
+* @typedef {import('vfile').Value} Value
+*
+* @typedef {import('../index.js').CompileResultMap} CompileResultMap
+* @typedef {import('../index.js').Data} Data
+* @typedef {import('../index.js').Settings} Settings
+*/
+/**
+* @typedef {CompileResultMap[keyof CompileResultMap]} CompileResults
+* Acceptable results from compilers.
+*
+* To register custom results, add them to
+* {@linkcode CompileResultMap}.
+*/
+/**
+* @template {Node} [Tree=Node]
+* The node that the compiler receives (default: `Node`).
+* @template {CompileResults} [Result=CompileResults]
+* The thing that the compiler yields (default: `CompileResults`).
+* @callback Compiler
+* A **compiler** handles the compiling of a syntax tree to something else
+* (in most cases, text) (TypeScript type).
+*
+* It is used in the stringify phase and called with a {@linkcode Node}
+* and {@linkcode VFile} representation of the document to compile.
+* It should return the textual representation of the given tree (typically
+* `string`).
+*
+* > **Note**: unified typically compiles by serializing: most compilers
+* > return `string` (or `Uint8Array`).
+* > Some compilers, such as the one configured with
+* > [`rehype-react`][rehype-react], return other values (in this case, a
+* > React tree).
+* > If you’re using a compiler that doesn’t serialize, expect different
+* > result values.
+* >
+* > To register custom results in TypeScript, add them to
+* > {@linkcode CompileResultMap}.
+*
+* [rehype-react]: https://github.com/rehypejs/rehype-react
+* @param {Tree} tree
+* Tree to compile.
+* @param {VFile} file
+* File associated with `tree`.
+* @returns {Result}
+* New content: compiled text (`string` or `Uint8Array`, for `file.value`) or
+* something else (for `file.result`).
+*/
+/**
+* @template {Node} [Tree=Node]
+* The node that the parser yields (default: `Node`)
+* @callback Parser
+* A **parser** handles the parsing of text to a syntax tree.
+*
+* It is used in the parse phase and is called with a `string` and
+* {@linkcode VFile} of the document to parse.
+* It must return the syntax tree representation of the given file
+* ({@linkcode Node}).
+* @param {string} document
+* Document to parse.
+* @param {VFile} file
+* File associated with `document`.
+* @returns {Tree}
+* Node representing the given file.
+*/
+/**
+* @typedef {(
+* Plugin, any, any> |
+* PluginTuple, any, any> |
+* Preset
+* )} Pluggable
+* Union of the different ways to add plugins and settings.
+*/
+/**
+* @typedef {Array} PluggableList
+* List of plugins and presets.
+*/
+/**
+* @template {Array} [PluginParameters=[]]
+* Arguments passed to the plugin (default: `[]`, the empty tuple).
+* @template {Node | string | undefined} [Input=Node]
+* Value that is expected as input (default: `Node`).
+*
+* * If the plugin returns a {@linkcode Transformer}, this
+* should be the node it expects.
+* * If the plugin sets a {@linkcode Parser}, this should be
+* `string`.
+* * If the plugin sets a {@linkcode Compiler}, this should be the
+* node it expects.
+* @template [Output=Input]
+* Value that is yielded as output (default: `Input`).
+*
+* * If the plugin returns a {@linkcode Transformer}, this
+* should be the node that that yields.
+* * If the plugin sets a {@linkcode Parser}, this should be the
+* node that it yields.
+* * If the plugin sets a {@linkcode Compiler}, this should be
+* result it yields.
+* @typedef {(
+* (this: Processor, ...parameters: PluginParameters) =>
+* Input extends string ? // Parser.
+* Output extends Node | undefined ? undefined | void : never :
+* Output extends CompileResults ? // Compiler.
+* Input extends Node | undefined ? undefined | void : never :
+* Transformer<
+* Input extends Node ? Input : Node,
+* Output extends Node ? Output : Node
+* > | undefined | void
+* )} Plugin
+* Single plugin.
+*
+* Plugins configure the processors they are applied on in the following
+* ways:
+*
+* * they change the processor, such as the parser, the compiler, or by
+* configuring data
+* * they specify how to handle trees and files
+*
+* In practice, they are functions that can receive options and configure the
+* processor (`this`).
+*
+* > **Note**: plugins are called when the processor is *frozen*, not when
+* > they are applied.
+*/
+/**
+* Tuple of a plugin and its configuration.
+*
+* The first item is a plugin, the rest are its parameters.
+*
+* @template {Array} [TupleParameters=[]]
+* Arguments passed to the plugin (default: `[]`, the empty tuple).
+* @template {Node | string | undefined} [Input=undefined]
+* Value that is expected as input (optional).
+*
+* * If the plugin returns a {@linkcode Transformer}, this
+* should be the node it expects.
+* * If the plugin sets a {@linkcode Parser}, this should be
+* `string`.
+* * If the plugin sets a {@linkcode Compiler}, this should be the
+* node it expects.
+* @template [Output=undefined] (optional).
+* Value that is yielded as output.
+*
+* * If the plugin returns a {@linkcode Transformer}, this
+* should be the node that that yields.
+* * If the plugin sets a {@linkcode Parser}, this should be the
+* node that it yields.
+* * If the plugin sets a {@linkcode Compiler}, this should be
+* result it yields.
+* @typedef {(
+* [
+* plugin: Plugin,
+* ...parameters: TupleParameters
+* ]
+* )} PluginTuple
+*/
+/**
+* @typedef Preset
+* Sharable configuration.
+*
+* They can contain plugins and settings.
+* @property {PluggableList | undefined} [plugins]
+* List of plugins and presets (optional).
+* @property {Settings | undefined} [settings]
+* Shared settings for parsers and compilers (optional).
+*/
+/**
+* @template {VFile} [File=VFile]
+* The file that the callback receives (default: `VFile`).
+* @callback ProcessCallback
+* Callback called when the process is done.
+*
+* Called with either an error or a result.
+* @param {Error | undefined} [error]
+* Fatal error (optional).
+* @param {File | undefined} [file]
+* Processed file (optional).
+* @returns {undefined}
+* Nothing.
+*/
+/**
+* @template {Node} [Tree=Node]
+* The tree that the callback receives (default: `Node`).
+* @callback RunCallback
+* Callback called when transformers are done.
+*
+* Called with either an error or results.
+* @param {Error | undefined} [error]
+* Fatal error (optional).
+* @param {Tree | undefined} [tree]
+* Transformed tree (optional).
+* @param {VFile | undefined} [file]
+* File (optional).
+* @returns {undefined}
+* Nothing.
+*/
+/**
+* @template {Node} [Output=Node]
+* Node type that the transformer yields (default: `Node`).
+* @callback TransformCallback
+* Callback passed to transforms.
+*
+* If the signature of a `transformer` accepts a third argument, the
+* transformer may perform asynchronous operations, and must call it.
+* @param {Error | undefined} [error]
+* Fatal error to stop the process (optional).
+* @param {Output | undefined} [tree]
+* New, changed, tree (optional).
+* @param {VFile | undefined} [file]
+* New, changed, file (optional).
+* @returns {undefined}
+* Nothing.
+*/
+/**
+* @template {Node} [Input=Node]
+* Node type that the transformer expects (default: `Node`).
+* @template {Node} [Output=Input]
+* Node type that the transformer yields (default: `Input`).
+* @callback Transformer
+* Transformers handle syntax trees and files.
+*
+* They are functions that are called each time a syntax tree and file are
+* passed through the run phase.
+* When an error occurs in them (either because it’s thrown, returned,
+* rejected, or passed to `next`), the process stops.
+*
+* The run phase is handled by [`trough`][trough], see its documentation for
+* the exact semantics of these functions.
+*
+* > **Note**: you should likely ignore `next`: don’t accept it.
+* > it supports callback-style async work.
+* > But promises are likely easier to reason about.
+*
+* [trough]: https://github.com/wooorm/trough#function-fninput-next
+* @param {Input} tree
+* Tree to handle.
+* @param {VFile} file
+* File to handle.
+* @param {TransformCallback