Repository navigation
hub-mcp Phase 4: Quarto-specific intelligence (bd-738mbhpj) - #797
Open
shikokuchuo wants to merge 9 commits into
Open
shikokuchuo wants to merge 9 commits into
shikokuchuo wants to merge 9 commits into
Conversation
Contributor
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
shikokuchuo
added this pull request to stack #792
October 7, 2026 06:54
…1; bd-738mbhpj)
The crate no longer compiled on main for any target (build-wasm.yml is
workflow_dispatch-only, so the rot was invisible; CI gap filed bd-s0tm9sdq):
- tree-sitter 0.26 compiles tree-sitter-language's wasm/src/*.c into
libtree_sitter.rlib, colliding with the crate's local c_shim.rs
(duplicate strong symbols). Apply the established repo pattern the
crate was missing: [patch.crates-io] tree-sitter-language ->
tree-sitter-language-wasm-shim (empty C stubs; 2026-04-20
wasm-shim-merge plan).
- The patch leaves snprintf undefined; the stale local c_shim.rs
predated the merged superset. Delete it and take the shared
wasm-c-shim crate (strict superset; same as wasm-quarto-hub-client
and pampa's wasm tests).
- The workspace root's panic=unwind rustflags leak in via cargo's
config-array concatenation and break the link without build-std.
Trailing -C panic=abort in the crate's .cargo/config.toml (rustc
last-wins) keeps this crate Lua-free and rust-src-free.
Spike S-1 decision recorded in the plan: CAP-11's parser is
wasm-qmd-parser --target nodejs staged as an external package in the
hub-mcp dist-bundle (keyring pattern), not a napi-rs module. Release
wasm 4.61 MB raw / 1.24 MB gzip; init 3.8 ms; warm parse 0.11 ms
(1 KB) / 28 ms (34 KB). napi-rs rejected: new per-platform build
axis, size multiplies per target, no latency win at MCP scales.
Verified: wasm-pack build --target nodejs and --target web (dev +
release) with committed config alone; Node require/ESM named imports
work; parse_qmd returns {success, ast:<json>} with byte-offset
locations. Full workspace suite green (15487/15487).
…(CAP-11; bd-738mbhpj) Structural reads and edits for qmd files, on the S-1 spike's parser (wasm-qmd-parser --target nodejs, staged into dist-bundle as an external package — the keyring pattern): - get_outline: the heading tree of a .qmd file with level/title/id and 1-based line ranges (section ends before the next same-or-higher heading). Read-only surface. - read_file gains a section selector returning exactly one section (heading included); mutually exclusive with offset/limit; max_bytes still caps. patch_file gains a section arm replacing exactly the range read_file shows — empty new_string deletes the section, the heading rides new_string so renames work, expected_hash still guards. - The splice is line-range math against the current text applied through updateFileContent, so a collaborator's concurrent edit outside the section survives (pinned by test). The sync-client getFileAst/updateFileAst path was rejected: writeQmd re-serializes the whole document (clobbering + normalization risk) for no gain. - All section math is line-based, never byte offsets (automerge text is UTF-16); headings inside divs are out of scope for v1. Build wiring: scripts/qmd-parser-pkg.mjs builds the pkg with a git-state freshness stamp (no stale-parser trap, no-op when fresh); bundle.mjs marks the package external and stages it into dist-bundle/node_modules/wasm-qmd-parser (fail-closed, unit-tested); vitest builds the pkg in globalSetup and points QUARTO_QMD_PARSER_SPEC at it per-worker. Lazy singleton load — the 4.6 MB wasm never loads on the plain-CRUD path (~4 ms init when it does). Tests: 38 new (AST units against the real parser, get_outline, read/patch section arms incl. the CRDT-merge case, staging units); suite 462 passed + 3 skips. Tool budget: 20/24 data tools (23/24 with auth). Instructions + docs/tools/q2-mcp.qmd updated; conformance, hub-mcp, and inspector-smoke pins updated for the new surface.
… tier (CAP-11; bd-738mbhpj) The parser pkg needs a wasm32 toolchain (wasm-pack + clang), which two bundle-consuming environments deliberately lack: - CI workspace-ts-suites job (documented as Rust/WASM-free): the vitest global setup now treats the build as best-effort — on failure it leaves a marker file and the 38 parser-tier tests skipIf, the same gated-tier pattern as the network-gated live tests. The tier runs in the toolchain-rich test-suite job instead (new wasm-pack step + explicit vitest invocation), keeping full CI coverage on both OS legs. - Release pipeline: the wasm is platform-independent, so web-payloads builds it once and every bundle leg (per-target incl. Windows/musl, plus the standalone hub-mcp-bundle job, which gained needs: web-payloads + artifact download) stages it without rebuilding. The git-state freshness stamp now excludes the pkg dirs from its dirty scan so a downloaded artifact reads as fresh, not as untracked dirt (status is conversion-normalized, so CRLF checkouts stay clean). The hub-mcp-bundle verify step now fails closed unless the parser files are staged AND parse a document on the runner. Runbook updated for the new bundle content. Full hub-mcp suite green (462 passed + 3 skips); skip path verified end-to-end by simulating a toolchain-less leg (pkg absent, no wasm-pack -> marker -> 38 skips).
…ender (CAP-12; bd-738mbhpj) One opt-in tool (ERG-5 rule (a): the plan's render_project/render_file pair consolidates to render with an optional path). Materializes the project from the live sync state to a temp dir, runs q2 render --json-errors, and returns structured diagnostics (Q- codes, file/line/column relativized out of the temp dir) plus the produced outputs; the temp dir is always removed. - Three render modes, discovered against the real binary: q2 render . without _quarto.yml is Q-7-7 and bare q2 render is Q-7-3, so loose projects render as an explicit .qmd list (mode 'files', capped at 500) while project dirs get the true directory render. - Security: rendering executes project code, so the tool exists only under --allow-render and never with --read-only (the stricter gate wins); annotations honest (readOnlyHint false, openWorldHint true); timeout SIGTERM->SIGKILL, streams capped, path-escape entries refused at materialization, client cancel kills the child. - The launcher injects QUARTO_Q2_PATH=current_exe (user env wins, same pattern as BP-10's version injection); standalone falls back to q2 on PATH with an actionable ENOENT error naming the variable. Tests: 15 new — fake-q2 wire cases (both NDJSON envelope shapes, timeout, outputs, spawn error), materialization + escape guard, the three listing gates, and the plan-spec real-binary test (Q-17-2 with file/line/column, gated on target/debug/q2). Launcher: 3 injection units. Suite 477 passed + 3 skips. Instructions + docs page updated (security model documented).
…; bd-738mbhpj)
The docs tool answers Quarto usage questions from the corpus embedded
in the q2 binary — the same pages q2 docs llms serves, with no project
connection and no network. One tool, two arms (ERG-5(a)): query ranks
pages (title hits outrank body; multi-word queries tokenize so
AND-matches float up; snippets pick the highest-term-density line),
page fetches one page's markdown by any accepted href spelling with
near-miss suggestions.
Seam: one subprocess per process lifetime — q2 docs llms --full via
QUARTO_Q2_PATH (the CAP-12 launcher injection doubles as the CAP-13
seam; PATH fallback for standalone). The corpus's per-page
---\ntitle:/url: markers make attribution exact, so everything after
the first fetch serves from the in-process cache. Chosen over
launcher-extracted doc dirs and bundle staging (no second code path,
no ~1 MB duplication).
bd-b6cocsxw ships with it: q2 docs llms --list --json emits
[{href,title}] and --embed-info --json emits
{placeholder,commit,dirty,pages}, gated by a clap jsonable group; unit
+ CLI integration tests in both embed states. docs/guides/embedded-docs
documents the flag.
Tests: 19 new on the TS side (corpus units, fake-q2 tool cases incl.
placeholder-embed error and read-only listing, conformance golden with
a stubbed corpus) + 4 Rust. Suite 496 passed + 3 skips. bd-dn81ol95 and
bd-b6cocsxw closed. Tool budget: 21/24 data, 24/24 with auth — at the
ceiling.
It deletes shared index state that drives other collaborators' preview engines (the Q-3 invisible-shared-mutation class); the CAP-12 render loop never touches hub-side captures; CAP-2's get_project_info surface already lets the agent observe and advise. No tool.
…k (bd-738mbhpj) Three new eval tasks: outline-and-section-edit (CAP-11 loop), docs-lookup (CAP-13, requiresQ2), fix-render-error (CAP-12 loop, requiresQ2). The runner opts its server into --allow-render, injects QUARTO_Q2_PATH=target/debug/q2 when built, and skips q2-dependent tasks when absent. The first run exposed a real deployment gap: the tsc dist/ build (used by the eval, dev runs, and the CI smoke check) has no staged parser — only dist-bundle/ had one — so CAP-11 tools degraded to the error path (the agent recovered via read_file + old_string, exactly the designed fallback). qmd-parser.ts now resolves env-spec -> staged package -> repo-relative pkg-nodejs fallback, so in-repo dist runs work with zero staging; the bundle path is unchanged. Clean full run: 15/15 PASS, median 5 turns (Phases 2/3: 5 — no regression), zero isError except the DESIGNED stale-hash refusal in stale-write-recovery. Transcripts: eval/results/2026-10-06T23-22-28/. The outline rerun shows the intended sequence: get_outline -> read_file section -> patch_file section (expected_hash) -> verify.
Three fixes the real-binary e2e surfaced: 1. read_file's section arm reported truncation against the FILE, so a complete section read claimed truncated: true with a continuation into the next section. Truncation is now reported against the section (complete => truncated: false, next_offset: null); a max_bytes-cut section points its continuation inside the section. windowLines exposes its end line; two regression tests added. 2. The loose-files render mode passed every .qmd in one invocation — but q2 refuses multiple paths without a project (Q-7-4). It now runs one q2 render per file under a shared deadline, unioning diagnostics/outputs and reporting the first nonzero exit. The fake-q2 stub is argv-aware (fails only for a named file); a two-file union test pins the batch behavior. 3. q2 docs llms --json alone was accepted: clap's group-requires does not propagate through the tuple-variant subcommand, so the pairing rule is enforced in DocsLlmsArgs::mode() instead. The cargo xtask verify run caught this after the local run masked it. Also cargo fmt drift from heredoc-appended tests. Phase 4 completion record added to the plan: e2e transcript summary (22 tools with --allow-render; outline/section/docs/render all exercised against the real binary with output inspected), eval table (15/15 PASS, median 5 turns, zero unintended isError), and the phase gate (verify 14/14, suite 498 passed + 3 skips). Tool budget noted as at-ceiling (24/24 with auth).
…egs (bd-7tk3i1q3) f0fd2b8 made workspace-ts-suites deliberately toolchain-free (marker + skipIf for the 38 qmd-* parser-tier tests, tier re-run in the toolchain-rich test-suite job), but two parser consumers escaped the gate and failed both OS legs of run 37584171133: - conformance.test.ts: the golden-result loop called get_outline unconditionally; without the parser the tool errors by design, so the assertion failed. The golden case now carries requiresParser and the execution loop skips such cases when PARSER_UNAVAILABLE (registration coverage is unaffected). - bundle.test.ts: beforeAll runs bundle.mjs, which stages the wasm-qmd-parser pkg and hard-fails without a wasm32 toolchain. The suite is now describe.skipIf(PARSER_UNAVAILABLE) with hooks scoped inside the suite. Both files join the test-suite job's parser-tier vitest invocation so the gated coverage (including the bundle smoke test) still gates CI on both OS legs. Verified both directions: toolchain-less simulation (restricted PATH, pkg absent) reproduces the exact CI errors then goes green with bundle.test.ts skipped; with the toolchain, the full suite passes 55 files / 498 tests and the updated CI command passes 5 files / 75 tests.
shikokuchuo
force-pushed
the
braid/bd-738mbhpj-hub-mcp-phase-4-quarto-intelligence
branch
from
October 7, 2026 13:45
9ae3097 to
5517e68
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 4 of the hub-mcp elevation plan (bd-738mbhpj), stacked on #794. The MCP server gains Quarto-specific tools: structural qmd editing, rendering, and the embedded documentation. Design and full record:
claude-notes/plans/2026-10-05-elevate-quarto-hub-mcp.md.New tools
get_outlinereturns the heading tree of a.qmdfile with 1-based line ranges (CAP-11).read_fileandpatch_filetake asectionselector that reads or replaces exactly one section (CAP-11). A section patch touches only the section's lines, so a collaborator's edits elsewhere survive. The replaced range is exactly the rangeread_fileshows, heading included. An emptynew_stringdeletes the section.rendermaterializes the project to a temporary directory and runsq2 render --json-errors(CAP-12). It returns structured diagnostics withQ-codes and source locations. It removes the directory afterwards. The tool exists only with--allow-render, because rendering runs project code. It never appears with--read-only.docsanswers Quarto usage questions from the documentation embedded in theq2binary (CAP-13).querysearches titles and full text.pagereturns one page's markdown. No project connection or network is needed.CLI additions
q2 docs llms --list --jsonprints the page list as a JSON array (bd-b6cocsxw).q2 docs llms --embed-info --jsonprints the embed provenance as JSON (bd-b6cocsxw).Build and CI
wasm-qmd-parserwas broken on main for every target. It builds again, and its local C shim is replaced by the sharedwasm-c-shimcrate.nodejsbuild, staged into the MCP bundle like the keyring addon. The server and the web preview now use the same parser.q2 mcplauncher injectsQUARTO_Q2_PATH, so the server can find the binary for renders and docs.Decisions worth knowing
clear_capturestays human-only (Q-4). It mutates shared state that drives other collaborators' preview engines.Verification
cargo xtask verify: 14 of 14 legs pass. The MCP suite: 498 passed, 3 skipped.Q-17-2with file, line, and column.