diff --git a/packages/mcp/build/lib/footnote-analyze.js b/packages/mcp/build/lib/footnote-analyze.js deleted file mode 100644 index 0bae93c7..00000000 --- a/packages/mcp/build/lib/footnote-analyze.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Footnote diagnostics for imported Markdown (issue #166). - * - * A PURE, fence-aware text scan (independent of the Markdown->ProseMirror - * conversion path, so it reports the same problems for `create_page`, - * `update_page` and `import_page_markdown`). It never changes the document — the - * importer still creates the page; this only surfaces footnote problems to the - * caller so an agent can fix its own markup instead of shipping broken footnotes. - * - * Detected problems: - * - danglingReferences: a `[^id]` reference with no `[^id]:` definition. - * - emptyDefinitions: a `[^id]:` whose (kept) text is empty/whitespace. - * - duplicateDefinitions: an id defined by two or more `[^id]:` lines (only the - * first is kept on import — first-wins; see extractFootnotes). - * - referencesInTables: a `[^id]` marker found in a GFM table row (heuristic: - * the line, trimmed, starts with `|`) — footnotes in table cells often do not - * render as expected. - */ -import { lexFootnoteLines, forEachFootnoteReference, } from "./footnote-lex.js"; -/** - * Analyze the footnotes in a Markdown string. Pure; safe to call on any body. - */ -export function analyzeFootnotes(markdown) { - // Distinct reference ids in first-appearance order, plus the set of ids seen - // inside a table row. - const refIds = []; - const refIdSet = new Set(); - const referencesInTables = new Set(); - const addRef = (id, inTable) => { - if (!refIdSet.has(id)) { - refIdSet.add(id); - refIds.push(id); - } - if (inTable) - referencesInTables.add(id); - }; - // Definition texts per id, in first-appearance order of the id. - const defTextsById = new Map(); - // Same lexer the importer uses, so the analysis matches exactly what import - // keeps/strips (#166): fenced lines are inert, definition lines are pulled. - for (const tok of lexFootnoteLines(markdown)) { - if (tok.inFence) - continue; - if (tok.definition) { - const { id, text } = tok.definition; - const arr = defTextsById.get(id); - if (arr) - arr.push(text); - else - defTextsById.set(id, [text]); - // A definition's TEXT can itself reference another footnote (`[^a]: see - // [^b]`); count those so such a `[^b]` is not falsely reported dangling. - forEachFootnoteReference(text, (rid) => addRef(rid, false)); - continue; - } - const inTable = tok.line.trimStart().startsWith("|"); - forEachFootnoteReference(tok.line, (id) => addRef(id, inTable)); - } - const danglingReferences = refIds.filter((id) => !defTextsById.has(id)); - const duplicateDefinitions = []; - const emptyDefinitions = []; - for (const [id, texts] of defTextsById) { - if (texts.length >= 2) - duplicateDefinitions.push(id); - // First-wins: the kept definition is the first one; flag it if it is blank. - if ((texts[0] ?? "").trim().length === 0) - emptyDefinitions.push(id); - } - const tableRefs = [...referencesInTables]; - const warnings = []; - const list = (ids) => ids.map((id) => `[^${id}]`).join(", "); - if (danglingReferences.length > 0) { - warnings.push(`Footnote reference(s) with no matching definition: ${list(danglingReferences)} (each will render as an empty footnote in the editor).`); - } - if (emptyDefinitions.length > 0) { - warnings.push(`Footnote definition(s) with empty text: ${list(emptyDefinitions)}.`); - } - if (duplicateDefinitions.length > 0) { - warnings.push(`Footnote id(s) defined more than once (only the first definition was kept): ${list(duplicateDefinitions)}.`); - } - if (tableRefs.length > 0) { - warnings.push(`Footnote marker(s) inside a table row (footnotes in table cells may not render as expected): ${list(tableRefs)}.`); - } - return { - danglingReferences, - emptyDefinitions, - duplicateDefinitions, - referencesInTables: tableRefs, - warnings, - }; -} -/** - * The optional `footnoteWarnings` field for a page-write tool result: present - * (with the warning lines) only when `markdown` has footnote problems, omitted - * otherwise. One helper so all three call sites (create/update/import) attach the - * field identically. Spread into the result: `{ ...result, ...footnoteWarningsField(text) }`. - */ -export function footnoteWarningsField(markdown) { - const { warnings } = analyzeFootnotes(markdown); - return warnings.length > 0 ? { footnoteWarnings: warnings } : {}; -} diff --git a/packages/mcp/build/lib/footnote-lex.js b/packages/mcp/build/lib/footnote-lex.js deleted file mode 100644 index 3c22d149..00000000 --- a/packages/mcp/build/lib/footnote-lex.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Shared, fence-aware line lexer for footnote markdown (MCP-internal). - * - * Both the importer (`extractFootnotes` in collaboration.ts, which strips - * definition lines and rebuilds a footnotes section) and the diagnostics - * (`analyzeFootnotes` in footnote-analyze.ts) must agree EXACTLY on which lines - * are definitions and which lines are inert (inside a code fence). Sharing one - * lexer makes "the analyzer sees what the importer leaves" a structural property - * instead of two hand-kept copies that can drift (#166 review). - * - * NOTE: this is deliberately NOT shared with editor-ext's - * `extractFootnoteDefinitions` — that lives in a different package and the - * decoupling between the editor and the MCP mirror is intentional. - */ -/** A footnote DEFINITION line: `[^id]: text` (id + text captured). */ -export const FOOTNOTE_DEF_RE = /^\[\^([^\]\s]+)\]:[ \t]*(.*)$/; -/** Every footnote REFERENCE `[^id]` in a line (global; id captured). */ -export const FOOTNOTE_REF_RE_G = /\[\^([^\]\s]+)\]/g; -/** Opening/closing code fence marker (``` or ~~~). */ -const FENCE_RE = /^(\s*)(`{3,}|~{3,})/; -/** Classify every line of `markdown`, tracking fenced-code state. Pure. */ -export function lexFootnoteLines(markdown) { - const out = []; - let fence = null; - for (const line of markdown.split("\n")) { - const fenceMatch = FENCE_RE.exec(line); - if (fenceMatch) { - const marker = fenceMatch[2][0]; - if (fence === null) - fence = marker; // opening fence - else if (marker === fence) - fence = null; // matching closing fence - out.push({ line, inFence: true, definition: null }); - continue; - } - if (fence !== null) { - out.push({ line, inFence: true, definition: null }); - continue; - } - const m = FOOTNOTE_DEF_RE.exec(line); - out.push({ - line, - inFence: false, - definition: m ? { id: m[1], text: m[2] } : null, - }); - } - return out; -} -/** Scan a line for every `[^id]` reference, invoking `onRef(id)` for each. */ -export function forEachFootnoteReference(line, onRef) { - FOOTNOTE_REF_RE_G.lastIndex = 0; - let m; - while ((m = FOOTNOTE_REF_RE_G.exec(line)) !== null) - onRef(m[1]); -} diff --git a/packages/mcp/node_modules/y-prosemirror b/packages/mcp/node_modules/y-prosemirror deleted file mode 120000 index 16997d1b..00000000 --- a/packages/mcp/node_modules/y-prosemirror +++ /dev/null @@ -1 +0,0 @@ -../../../node_modules/.pnpm/y-prosemirror@1.3.7_prosemirror-model@1.25.1_prosemirror-state@1.4.3_prosemirror-view@1_0ad6648b7e1f6d6f3287a40e0e62139b/node_modules/y-prosemirror \ No newline at end of file