diff --git a/.claude/rules/filters/cross-project-consumers.md b/.claude/rules/filters/cross-project-consumers.md
new file mode 100644
index 00000000000..34ed7d9124e
--- /dev/null
+++ b/.claude/rules/filters/cross-project-consumers.md
@@ -0,0 +1,70 @@
+---
+paths:
+ - "src/resources/filters/**"
+ - "package/src/common/prepare-dist.ts"
+ - "package/src/common/package-filters.ts"
+---
+
+# Filters Consumed by Other Projects (do not delete without checking)
+
+Some Lua filters in `src/resources/filters/` are invoked **standalone** (as bare
+`pandoc --lua-filter` / `--defaults` passes, outside Quarto's `render` pipeline)
+by **other repositories** that ship or embed Quarto. "Nothing in quarto-cli
+references this file" is therefore **not** sufficient proof that a filter is dead
+code. Always run the checklist below before removing or renaming anything under
+`src/resources/filters/`.
+
+## Cross-project consumers
+
+Both consumers build the crossref/xref index used for visual-editor autocomplete
+by calling `pandoc` directly against Quarto's installed share tree
+(`QUARTO_SHARE_PATH`), loading these filters:
+
+- `filters/quarto-init/quarto-init.lua`
+- `filters/crossref/crossref.lua`
+- `filters/qmd-reader.lua` (used as the `--from` reader when present)
+
+with `QUARTO_FILTER_PARAMS` set to `{ "crossref-index-file": ..., "crossref-input-type": "qmd" }`.
+
+| Consumer | File | What it does |
+|----------|------|--------------|
+| Visual editor (`quarto-dev/quarto`) | `packages/editor-server/src/core/xref.ts` (`indexSourceFile`) | Writes a `defaults.yml` listing `quarto-init.lua` + `crossref.lua`, runs pandoc to emit `index.json`. |
+| RStudio (`rstudio/rstudio`) | `src/cpp/session/modules/quarto/SessionQuartoXRefs.cpp` | Same defaults + filters, from the C++ side. |
+
+The visual editor also bundles its **own** unrelated filters
+(`parser.lua`, `sourcepos.lua`, `heading-ids.lua` in `editor-server`) — those are
+not ours and are not relevant here.
+
+## Why "no in-repo reference" is not enough
+
+The installer does **not** ship `src/resources/filters/` verbatim. In
+`package/src/common/prepare-dist.ts`:
+
+1. The copied `share/filters` source tree is **deleted** (`pathsToClean`).
+2. `inlineFilters()` rebuilds `share/filters` from a **fixed list**
+ (`filtersToInline`: `main, pagebreak, quarto-init, crossref, customwriter,
+ qmd-reader, llms, leveloneanalysis`) plus the `modules/` directory.
+ `buildFilter` (`package-filters.ts`) inlines every `-- [import]` into one
+ self-contained file.
+
+So the shipped `crossref/crossref.lua` is already a **standalone, fully-inlined**
+bundle — the build auto-generates the standalone crossref filter from
+`crossref.lua`. A hand-maintained "standalone" duplicate is redundant. Anything
+**not** in the inline list (and not under `modules/`) is never shipped and cannot
+be a cross-project dependency via the share path.
+
+## Removal checklist
+
+Before deleting/renaming a filter under `src/resources/filters/`:
+
+1. Is it in the `filtersToInline` list (or reachable via `modules/`) in
+ `prepare-dist.ts`? If **not**, it was never shipped — confirm it is also unused
+ at render time before removing.
+2. Is it (or its basename) referenced by `quarto-dev/quarto` or
+ `rstudio/rstudio`? Grep both for the basename and for `filters/
/`.
+ See `dev-docs/cross-project-dependencies.md`.
+3. A GitHub-wide code search for the basename catches extension-ecosystem usage.
+
+Precedent: `crossref-standalone.lua` and `quarto-pre/quarto-pre.lua` were removed
+after confirming (a) neither was in the inline list, so neither was ever shipped,
+and (b) both consumers use `crossref.lua`/`quarto-init.lua`, not the removed files.
diff --git a/dev-docs/cross-project-dependencies.md b/dev-docs/cross-project-dependencies.md
new file mode 100644
index 00000000000..c2223edcf93
--- /dev/null
+++ b/dev-docs/cross-project-dependencies.md
@@ -0,0 +1,74 @@
+# Cross-Project Dependencies
+
+Other Posit repositories embed or ship Quarto and reach directly into
+quarto-cli's installed resources and commands. These couplings are **not**
+visible from within quarto-cli — nothing here references the consumer — so a
+change that looks internal can break the visual editor or RStudio. This file
+records the known touch-points so they can be checked before changing the
+surfaces involved.
+
+The two consumers are:
+
+- **`quarto-dev/quarto`** — the visual editor / VS Code + Positron tooling
+ monorepo (`packages/editor-server`, `packages/quarto-core`, `apps/lsp`, ...).
+- **`rstudio/rstudio`** — the RStudio IDE (visual editor backend in
+ `src/cpp/session/modules/`).
+
+> When you change one of the surfaces below, grep the consumer repo(s) for the
+> path/basename and coordinate a companion change if needed.
+
+## 1. Standalone crossref/xref filter invocation
+
+To power visual-editor cross-reference autocomplete, both consumers run `pandoc`
+directly against Quarto's installed share tree (`QUARTO_SHARE_PATH`) with a
+`--defaults` file that loads these filters standalone (outside `quarto render`):
+
+- `filters/quarto-init/quarto-init.lua`
+- `filters/crossref/crossref.lua`
+- `filters/qmd-reader.lua` (used as `--from` when present)
+
+Environment: `QUARTO_FILTER_PARAMS` = `{ "crossref-index-file": "index.json",
+"crossref-input-type": "qmd" }`, `QUARTO_SHARE_PATH` = the resource path.
+
+| Consumer | File |
+|----------|------|
+| `quarto-dev/quarto` | `packages/editor-server/src/core/xref.ts` (`indexSourceFile`) |
+| `rstudio/rstudio` | `src/cpp/session/modules/quarto/SessionQuartoXRefs.cpp` |
+
+**Contract:** the names/relative paths of these filters, their filter-params
+protocol, and the `index.json` output shape are a de-facto public API. Renaming,
+removing, or moving any of them, or dropping them from the packaging inline list
+(see below), breaks both consumers. See
+`.claude/rules/filters/cross-project-consumers.md`.
+
+## 2. What actually ships in `share/filters`
+
+`src/resources/filters/` is **not** shipped verbatim. `package/src/common/prepare-dist.ts`
+deletes the copied `share/filters` and rebuilds it via `inlineFilters()` from a
+fixed list (`filtersToInline`) plus the `modules/` directory; `buildFilter`
+(`package-filters.ts`) inlines each `-- [import]` into one self-contained file.
+A filter that is **not** in the inline list and **not** under `modules/` is never
+installed, so it cannot be a cross-project dependency via the share path — but it
+also means "grep finds no reference" does not prove a *shipped* filter is unused.
+
+## 3. Editor support commands
+
+`quarto-core` / the editor tooling also shell out to `quarto` subcommands
+(e.g. `quarto editor-support crossref`, implemented in
+`src/command/editor-support/crossref.ts`). Changing the CLI surface or output
+format of editor-support commands is a cross-project change.
+
+## Checklist when touching a shared surface
+
+1. Identify which consumer(s) use it (tables above).
+2. Grep the consumer repo for the path/basename and the CLI/protocol shape.
+3. For filters: confirm inline-list membership in `prepare-dist.ts`.
+4. Coordinate a companion PR in the consumer if the contract changes.
+
+## History
+
+- Removing `crossref-standalone.lua` and `quarto-pre/quarto-pre.lua`: verified
+ safe because neither was in the packaging inline list (never shipped) and both
+ consumers load `crossref.lua`/`quarto-init.lua`, not the removed files. This
+ doc and the scoped rule were written as part of that investigation so the check
+ does not have to be re-derived.
diff --git a/src/resources/filters/crossref/crossref-standalone.lua b/src/resources/filters/crossref/crossref-standalone.lua
deleted file mode 100644
index 47762e0438d..00000000000
--- a/src/resources/filters/crossref/crossref-standalone.lua
+++ /dev/null
@@ -1,65 +0,0 @@
--- crossref-standalone.lua
--- Copyright (C) 2020-2023 Posit Software, PBC
-
--- required version
-PANDOC_VERSION:must_be_at_least '2.13'
-
-
--- [import]
-function import(script)
- local path = PANDOC_SCRIPT_FILE:match("(.*[/\\])")
- dofile(path .. script)
-end
-
--- FIXME: needs updating for float-reftargets branch.
-
-import("../mainstateinit.lua")
-import("index.lua")
-import("preprocess.lua")
-import("sections.lua")
-import("figures.lua")
-import("tables.lua")
-import("equations.lua")
-import("theorems.lua")
-import("qmd.lua")
-import("refs.lua")
-import("meta.lua")
-import("format.lua")
-import("options.lua")
-import("../common/lunacolors.lua")
-import("../common/log.lua")
-import("../common/pandoc.lua")
-import("../common/format.lua")
-import("../common/base64.lua")
-import("../common/options.lua")
-import("../common/refs.lua")
-import("../common/filemetadata.lua")
-import("../common/figures.lua")
-import("../common/tables.lua")
-import("../common/theorems.lua")
-import("../common/meta.lua")
-import("../common/table.lua")
-import("../common/string.lua")
-import("../common/debug.lua")
--- [/import]
-
-initCrossrefIndex()
-
--- chain of filters
-return {
- init_crossref_options(),
- crossref_mark_subfloats(),
- crossref_preprocess_theorems(),
- combineFilters({
- file_metadata(),
- qmd(),
- sections(),
- crossref_figures(),
- crossref_tables(),
- equations(),
- crossref_theorems(),
- }),
- resolveRefs(),
- crossrefMetaInject(),
- writeIndex()
-}
\ No newline at end of file
diff --git a/src/resources/filters/quarto-pre/quarto-pre.lua b/src/resources/filters/quarto-pre/quarto-pre.lua
deleted file mode 100644
index 6f02e02d053..00000000000
--- a/src/resources/filters/quarto-pre/quarto-pre.lua
+++ /dev/null
@@ -1,155 +0,0 @@
--- quarto-pre.lua
--- Copyright (C) 2020-2022 Posit Software, PBC
-
--- required version
-PANDOC_VERSION:must_be_at_least '2.13'
-
--- [import]
-function import(script)
- local path = PANDOC_SCRIPT_FILE:match("(.*[/\\])")
- dofile(path .. script)
-end
-
-import("../mainstateinit.lua")
-import("../common/colors.lua")
-import("../common/error.lua")
-import("../common/base64.lua")
-import("../common/latex.lua")
-import("../common/meta.lua")
-import("../common/options.lua")
-import("../common/table.lua")
-import("../common/pandoc.lua")
-import("../common/filemetadata.lua")
-import("../common/layout.lua")
-import("../common/refs.lua")
-import("../common/figures.lua")
-import("../common/tables.lua")
-import("../common/theorems.lua")
-import("../common/debug.lua")
-import("../common/format.lua")
-import("../common/string.lua")
-import("../common/list.lua")
-import("../common/lunacolors.lua")
-import("../common/log.lua")
-import("../common/url.lua")
-import("../common/paths.lua")
-import("results.lua")
-import("options.lua")
-import("code-filename.lua")
-import("shortcodes-handlers.lua")
-import("outputs.lua")
-import("figures.lua")
-import("table-rawhtml.lua")
-import("table-captions.lua")
-import("table-colwidth.lua")
-import("theorems.lua")
-import("resourcefiles.lua")
-import("bibliography-formats.lua")
-import("book-numbering.lua")
-import("book-links.lua")
-import("meta.lua")
-import("code.lua")
-import("engine-escape.lua")
-import("panel-sidebar.lua")
-import("panel-input.lua")
-import("panel-layout.lua")
-import("hidden.lua")
-import("line-numbers.lua")
-import("output-location.lua")
-import("include-paths.lua")
-import("input-traits.lua")
-import("project-paths.lua")
-
-import("../customnodes/shortcodes.lua")
-import("../customnodes/content-hidden.lua")
-import("../customnodes/callout.lua")
-import("../customnodes/decoratedcodeblock.lua")
-import("../customnodes/panel-tabset.lua")
-
--- [/import]
-
-initShortcodeHandlers()
-
-local filterList = {
-
- { name = "flags", filter = compute_flags() },
-
- -- https://github.com/quarto-dev/quarto-cli/issues/5031
- -- recompute options object in case user filters have changed meta
- -- this will need to change in the future; users will have to indicate
- -- when they mutate options
- { name = "pre-read-options-again", filter = init_options() },
-
- { name = "pre-parse-pandoc3-figures",
- filter = parse_pandoc3_figures(),
- flags = { "has_pandoc3_figure" }
- },
-
- { name = "pre-bibliography-formats", filter = bibliography_formats() },
-
- { name = "pre-shortcodes-filter",
- filter = shortcodes_filter(),
- flags = { "has_shortcodes" } },
-
- { name = "pre-hidden",
- filter = hidden(),
- flags = { "has_hidden" } },
-
- { name = "pre-content-hidden",
- filter = content_hidden(),
- flags = { "has_conditional_content" } },
-
- { name = "pre-table-captions",
- filter = table_captions(),
- flags = { "has_table_captions" } },
-
- { name = "pre-longtable-no-caption-fixup",
- filter = longtable_no_caption_fixup(),
- flags = { "has_longtable_no_caption_fixup" } },
-
- { name = "pre-code-annotations",
- filter = code_annotations(),
- flags = { "has_code_annotations" } },
-
- { name = "pre-code-annotations-meta", filter = code_meta() },
-
- { name = "pre-unroll-cell-outputs",
- filter = unroll_cell_outputs(),
- flags = { "needs_output_unrolling" } },
-
- { name = "pre-output-location",
- filter = output_location()
- },
-
- { name = "pre-scope-resolution",
- filter = resolve_scoped_elements()
- },
-
- { name = "pre-combined-figures-theorems-etc", filter = combineFilters({
- file_metadata(),
- index_book_file_targets(),
- book_numbering(),
- include_paths(),
- resource_files(),
- quarto_pre_figures(),
- quarto_pre_theorems(),
- docx_callout_and_table_fixup(),
- -- code_filename(),
- line_numbers(),
- engine_escape(),
- bootstrap_panel_input(),
- bootstrap_panel_layout(),
- bootstrap_panel_sidebar(),
- table_respecify_gt_css(),
- -- table_colwidth(),
- table_classes(),
- input_traits(),
- resolve_book_file_targets(),
- project_paths()
- }) },
-
- { name = "pre-quarto-pre-meta-inject", filter = quarto_pre_meta_inject() },
- { name = "pre-write-results", filter = write_results() },
-}
-
-return filterList