From 50effd691bd4dbecd8ccf1a81ce682f72cb2f324 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 20 Sep 2026 17:35:05 +0000 Subject: [PATCH] Remove the docs MCP server from the default overlay Ship empty mcp.json ({"mcpServers":{}}) on the kit and fork overlay. Delete the launchers, optional Hub JSON, PATH shim, and Cloud allowlist/start. Grounding is .ai/ plus reference source; docs_mcp_server.py --query stays as an optional CLI fallback. Co-authored-by: ale93.moro --- .cursor/cloud-install.sh | 6 +- .cursor/commands/scaffold.md | 18 +- .cursor/commands/search-docs.md | 14 +- .cursor/environment.json | 7 - .cursor/install-docs-mcp.sh | 48 ----- .cursor/mcp-diffusers-docs.py | 97 ---------- .cursor/mcp-diffusers-docs.sh | 5 - .cursor/mcp.json | 13 +- .cursor/rules/00-conventions.mdc | 5 +- .cursor/skills/search-docs/SKILL.md | 12 +- .github/workflows/convention-gate.yml | 4 +- AGENTS.md | 5 +- Makefile | 37 ++-- README.md | 45 +++-- docs/ARCHITECTURE.md | 4 +- docs/CURSOR_PROMPTS.md | 34 ++-- docs/DECISIONS.md | 8 +- docs/LIVE_DEMO.md | 18 +- docs/SCORECARD.md | 2 +- docs/TALK_TRACK.md | 25 ++- knowledge/diffusers-docs/philosophy.md | 2 +- overlay/OVERLAY.md | 11 +- overlay/environment.json | 7 - overlay/mcp.json | 13 +- overlay/mcp.optional.json | 15 -- overlay/scaffold.md | 16 +- overlay/search-docs.md | 8 +- overlay/search-docs.skill.md | 7 +- projections/devops/ci-gate.yml | 4 +- tests/test_tooling.py | 241 +++++++++++++------------ tools/attach_library.py | 29 ++- tools/build_projections.py | 14 +- tools/demo_contribute.py | 4 +- tools/docs_mcp_server.py | 27 +-- 34 files changed, 310 insertions(+), 495 deletions(-) delete mode 100755 .cursor/install-docs-mcp.sh delete mode 100755 .cursor/mcp-diffusers-docs.py delete mode 100755 .cursor/mcp-diffusers-docs.sh delete mode 100644 overlay/mcp.optional.json diff --git a/.cursor/cloud-install.sh b/.cursor/cloud-install.sh index ff993e9..8d86c0c 100755 --- a/.cursor/cloud-install.sh +++ b/.cursor/cloud-install.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Cloud Agent snapshot bootstrap (idempotent). # Torch stays out of requirements.txt so local `make check` stays pyyaml-only. +# No docs MCP server is installed or allowlisted. set -euo pipefail export PATH="${HOME}/.local/bin:${PATH}" ROOT="$(cd "$(dirname "$0")/.." && pwd)" @@ -9,8 +10,7 @@ cd "$ROOT" python3 -m pip install --user -r requirements.txt python3 -m pip install --user --index-url https://download.pytorch.org/whl/cpu torch python3 -m pip install --user diffusers -bash "$ROOT/.cursor/install-docs-mcp.sh" -python3 "$ROOT/tools/docs_mcp_server.py" --selftest +python3 "$ROOT/tools/docs_mcp_server.py" --query "set_timesteps" >/dev/null python3 - <<'PY' import torch import diffusers @@ -18,6 +18,4 @@ import diffusers print("torch", torch.__version__, "cuda", torch.cuda.is_available()) print("diffusers", diffusers.__version__) PY -test -x "${HOME}/.local/bin/diffusers-docs-mcp" -echo "shim: ${HOME}/.local/bin/diffusers-docs-mcp" echo "cloud-install: ok" diff --git a/.cursor/commands/scaffold.md b/.cursor/commands/scaffold.md index 349bde2..eb3d382 100644 --- a/.cursor/commands/scaffold.md +++ b/.cursor/commands/scaffold.md @@ -5,15 +5,25 @@ Usage: `/scaffold ` - `$1` = component. Today the registry ships `scheduler`. Adding `model` or `pipeline` is a data change (see `templates/README.md`), not a new command. - `$2` = PascalCase name **without** the type suffix. Example: `EulerLite` → class `EulerLiteScheduler`, file `scheduling_euler_lite.py`. If those files already exist, pick a new unused `$2`. -Done is what `conventions/rules.yaml` checks **and**, on a real library checkout, what the library's own CI checks. Copy the templates. Do not start from a blank file, from library scheduler source, or from a docs search. +Done is what `conventions/rules.yaml` checks **and**, on a real library checkout, what the library's own CI checks. Copy the templates. Do not start from a blank file or from library scheduler source. -## 0. Ground in the registry +## 0. Ground in the registry (and the library guide) Read `conventions/rules.yaml`. Blocking ids for this contribution: SCHED001, SCHED002, SCHED003, REPRO001, DEVICE001, DEPR001, MUT001, TEST001, TEST002. -The templates already satisfy them. Stay inside `.cursorignore`. Do not read or -copy `examples/candidate_scheduler/` (known-bad fixture). +On a library checkout, also read root `AGENTS.md` and `.ai/` (do not overwrite +them) plus the reference source those files name. Optional keyword search: + +```bash +python3 tools/docs_mcp_server.py --query "set_timesteps" +``` + +That CLI is a fallback, not a deliverable. Default `.cursor/mcp.json` has no +servers. + +The templates already satisfy the registry. Stay inside `.cursorignore`. Do not +read or copy `examples/candidate_scheduler/` (known-bad fixture). ## 1. Name the files diff --git a/.cursor/commands/search-docs.md b/.cursor/commands/search-docs.md index fc569df..7903fab 100644 --- a/.cursor/commands/search-docs.md +++ b/.cursor/commands/search-docs.md @@ -1,13 +1,10 @@ -# /search-docs — grounded diffusers docs (MCP fallback) +# /search-docs — optional keyword search of bundled / checkout docs Usage: `/search-docs ` -Cloud Agents **do not load** project `.cursor/mcp.json`, and Cloud stdio -**does not expand** `${workspaceFolder}` (that is why the tool goes missing). -If `search_docs` is not in your tool list, this command is the connection. -The Cloud dropdown paste is `python3 -u .cursor/mcp-diffusers-docs.py`. - -Run this exactly (do not invent an answer from memory): +Prefer `conventions/rules.yaml` and, on the library, root `AGENTS.md` + `.ai/` +plus the reference source those files name. Default `.cursor/mcp.json` has no +servers. ```bash python3 tools/docs_mcp_server.py --query $1 @@ -22,6 +19,3 @@ python3 tools/docs_mcp_server.py --query "scheduler set_timesteps step Scheduler Then cite the provenance line (`bundled snapshot` vs a real checkout) and the matching section. If the snippets miss the contract, also read `conventions/rules.yaml` — the **gate** is authoritative. - -If the `diffusers-docs` / `search_docs` MCP tool **is** available, call that -instead of the CLI; it is the same server. diff --git a/.cursor/environment.json b/.cursor/environment.json index 879e7b9..62ec886 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -1,13 +1,6 @@ { "install": "bash .cursor/cloud-install.sh", - "start": "bash .cursor/install-docs-mcp.sh", "repositoryDependencies": [ "github.com/alex-16moro/diffusers" - ], - "mcpServerAllowlist": [ - { - "name": "diffusers-docs", - "command": "diffusers-docs-mcp" - } ] } diff --git a/.cursor/install-docs-mcp.sh b/.cursor/install-docs-mcp.sh deleted file mode 100755 index 818e80c..0000000 --- a/.cursor/install-docs-mcp.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -# Put `diffusers-docs-mcp` on PATH and copy the relative launcher into Cloud -# workspace roots so `python3 -u .cursor/mcp-diffusers-docs.py` resolves when -# stdio is spawned from /agent or /workspace (not the git repo). -set -euo pipefail -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -SRC="$ROOT/.cursor/mcp-diffusers-docs.py" -chmod +x "$SRC" "$ROOT/.cursor/mcp-diffusers-docs.sh" "$ROOT/.cursor/install-docs-mcp.sh" - -write_shim() { - local dest="$1" - mkdir -p "$(dirname "$dest")" - cat > "$dest" </dev/null || return 0 - cp "$SRC" "$dest_dir/mcp-diffusers-docs.py" 2>/dev/null || return 0 - chmod +x "$dest_dir/mcp-diffusers-docs.py" 2>/dev/null || true -} - -write_shim "$HOME/.local/bin/diffusers-docs-mcp" -path_line='export PATH="$HOME/.local/bin:$PATH"' -for rc in "$HOME/.profile" "$HOME/.bashrc"; do - if [ -f "$rc" ] && grep -q '\.local/bin' "$rc" 2>/dev/null; then - continue - fi - echo "$path_line" >> "$rc" -done -if mkdir -p /usr/local/bin 2>/dev/null && [ -w /usr/local/bin ]; then - write_shim /usr/local/bin/diffusers-docs-mcp || true -fi - -for ws in /agent /workspace "${CURSOR_WORKSPACE:-}" "${CURSOR_PROJECT_DIR:-}"; do - if [ -n "$ws" ] && [ -d "$ws" ]; then - copy_workspace_launcher "$ws/.cursor" || true - fi -done - -echo "docs MCP launcher: $SRC" -echo "PATH command: ${HOME}/.local/bin/diffusers-docs-mcp" -echo "Cloud dropdown: diffusers-docs-mcp" -echo " (or: python3 -u .cursor/mcp-diffusers-docs.py)" diff --git a/.cursor/mcp-diffusers-docs.py b/.cursor/mcp-diffusers-docs.py deleted file mode 100755 index 9da6c29..0000000 --- a/.cursor/mcp-diffusers-docs.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python3 -"""Launch diffusers-docs MCP from any cwd. - -Cloud Agent stdio cannot set `cwd` and does not expand `${workspaceFolder}`. -The dashboard command is often `python3 -u .cursor/mcp-diffusers-docs.py`, -spawned from the workspace root (e.g. `/agent`), not the git repo. This -launcher walks cwd, git root, `/agent/repos/*`, `/workspace`, and its own -path until it finds `tools/docs_mcp_server.py`, then execs it. - -Desktop and Cloud share: `python3 -u .cursor/mcp-diffusers-docs.py`. -Cloud PATH fallback after install: `diffusers-docs-mcp`. -""" -from __future__ import annotations - -import os -import subprocess -import sys -from pathlib import Path - - -def _children(base: Path): - try: - if not base.is_dir(): - return - for child in base.iterdir(): - if child.is_dir(): - yield child - except OSError: - return - - -def _candidates(): - out: list[Path] = [] - here = Path(__file__).resolve().parent - out.append(here.parent) # repo root when this file lives in .cursor/ - out.append(here.parent / "ramp-kit") - cwd = Path.cwd().resolve() - out.extend([cwd, cwd / "ramp-kit", *cwd.parents]) - for key in ("CURSOR_PROJECT_DIR", "CURSOR_WORKSPACE", "WORKSPACE"): - val = os.environ.get(key) - if val: - out.append(Path(val)) - out.append(Path(val) / "ramp-kit") - for base in ( - Path("/agent"), - Path("/agent/repos"), - Path("/workspace"), - Path("/workspace/repos"), - here.parent / "repos", - cwd / "repos", - ): - out.append(base) - for child in _children(base): - out.append(child) - out.append(child / "ramp-kit") - try: - top = subprocess.check_output( - ["git", "rev-parse", "--show-toplevel"], - stderr=subprocess.DEVNULL, - text=True, - ).strip() - if top: - out.append(Path(top)) - out.append(Path(top) / "ramp-kit") - except (OSError, subprocess.CalledProcessError): - pass - seen: set[Path] = set() - for raw in out: - try: - path = raw.resolve() - except OSError: - continue - if path in seen: - continue - seen.add(path) - yield path - - -def main(argv: list[str] | None = None) -> int: - extra = [a for a in (argv if argv is not None else sys.argv[1:]) if a != "--serve"] - for root in _candidates(): - script = root / "tools" / "docs_mcp_server.py" - if script.is_file(): - os.chdir(root) - sys.argv = [str(script), "--serve", *extra] - os.execv(sys.executable, [sys.executable, "-u", str(script), "--serve", *extra]) - sys.stderr.write( - "diffusers-docs MCP: could not find tools/docs_mcp_server.py " - f"(cwd={Path.cwd()} file={Path(__file__).resolve()}). " - "Install the PATH shim: bash .cursor/install-docs-mcp.sh " - "then spawn `diffusers-docs-mcp`.\n" - ) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.cursor/mcp-diffusers-docs.sh b/.cursor/mcp-diffusers-docs.sh deleted file mode 100755 index 852a350..0000000 --- a/.cursor/mcp-diffusers-docs.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -# Thin wrapper around the Python launcher (Cloud dashboard can use either). -set -euo pipefail -DIR="$(cd "$(dirname "$0")" && pwd)" -exec python3 -u "$DIR/mcp-diffusers-docs.py" --serve "$@" diff --git a/.cursor/mcp.json b/.cursor/mcp.json index cbf2bc8..8c3bf0d 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -1,12 +1 @@ -{ - "mcpServers": { - "diffusers-docs": { - "type": "stdio", - "command": "python3", - "args": ["-u", ".cursor/mcp-diffusers-docs.py"], - "env": { - "PYTHONUNBUFFERED": "1" - } - } - } -} +{"mcpServers":{}} diff --git a/.cursor/rules/00-conventions.mdc b/.cursor/rules/00-conventions.mdc index 83d8b8f..de6ad12 100644 --- a/.cursor/rules/00-conventions.mdc +++ b/.cursor/rules/00-conventions.mdc @@ -14,8 +14,9 @@ code that violates a **block** rule will fail the build. The first contribution is what `conventions/rules.yaml` checks. Copy the templates (`/scaffold`) and stop at 0 blocking findings from `tools/convention_check.py` on the new files. Do not add files the -registry does not scan. Docs search (`search_docs` / `/search-docs`) is -optional and is not a deliverable. +registry does not scan. Ground in this registry and, on the library, +`.ai/` plus reference source. Optional: +`python3 tools/docs_mcp_server.py --query "..."` — not a deliverable. Do not pull in code or context from outside this repo's approved boundaries (see `.cursorignore`). diff --git a/.cursor/skills/search-docs/SKILL.md b/.cursor/skills/search-docs/SKILL.md index 525f062..5aeeea6 100644 --- a/.cursor/skills/search-docs/SKILL.md +++ b/.cursor/skills/search-docs/SKILL.md @@ -1,14 +1,13 @@ --- name: search-docs -description: Ground answers in this repo's diffusers docs (same corpus as the search_docs MCP tool). Use before scaffolding or answering how the library does X, especially when the search_docs MCP tool is missing. +description: Optional keyword search of this repo's diffusers docs via docs_mcp_server.py --query. Prefer the registry, .ai/, and reference source. Use before scaffolding or answering how the library does X. --- # Search bundled diffusers docs -Cloud Agents often do not load project `.cursor/mcp.json`, so `search_docs` may -be absent until the Cloud MCP dropdown is `diffusers-docs-mcp` (or -`python3 -u .cursor/mcp-diffusers-docs.py`). This skill is the same grounding -path. +Default `.cursor/mcp.json` has no servers. Ground first in +`conventions/rules.yaml` and, on the library, `AGENTS.md` + `.ai/` plus +reference source. This skill is an optional CLI fallback. Run (do not invent the contract from memory): @@ -24,6 +23,3 @@ python3 tools/docs_mcp_server.py --query "scheduler set_timesteps step Scheduler Cite the provenance line (`bundled snapshot` vs a real checkout). If snippets miss the contract, read `conventions/rules.yaml` — the **gate** is authoritative. - -If the MCP tool `search_docs` **is** in your tool list, call that instead; it is -the same server (`python3 -u .cursor/mcp-diffusers-docs.py` / dashboard stdio). diff --git a/.github/workflows/convention-gate.yml b/.github/workflows/convention-gate.yml index 8b1fddc..215c00a 100644 --- a/.github/workflows/convention-gate.yml +++ b/.github/workflows/convention-gate.yml @@ -19,8 +19,8 @@ jobs: run: python tools/build_projections.py && git diff --exit-code - name: Scheduler contract re-verify run: python tools/verify_scheduler_contract.py - - name: MCP doc-server self-test - run: python tools/docs_mcp_server.py --selftest + - name: Docs CLI --query fallback + run: python tools/docs_mcp_server.py --query "set_timesteps" - name: Contract tests run: python -m unittest discover -s tests -t . - name: Machine-readable report diff --git a/AGENTS.md b/AGENTS.md index 4f91566..f3c8585 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,8 +7,9 @@ This repo uses convention-as-code. The authoritative rules live in `conventions/rules.yaml` and are enforced by `tools/convention_check.py`. Before opening a PR, run `make check` and fix every blocking finding. The first contribution is the registry + templates, not a docs page or -a copied library scheduler. Optional docs search: `search_docs` MCP, -`/search-docs`, or `python3 tools/docs_mcp_server.py --query "..."`. +a copied library scheduler. Grounding is the registry and (on the +library) `.ai/` plus reference source. Optional docs CLI: +`python3 tools/docs_mcp_server.py --query "..."`. ## Conventions diff --git a/Makefile b/Makefile index ceaecfe..8c754a0 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,8 @@ help: @echo " make check Run the convention gate on the whole repo (sets exit code)" @echo " make check-json Same, machine-readable (CI / dashboards)" @echo " make test Run the contract tests (zero third-party installs needed)" - @echo " make mcp Self-test the diffusers-docs MCP server (Cursor handshake)" - @echo " make demo Catch the bad scheduler, pass the good one, MCP, tests" + @echo " make mcp Optional docs CLI: docs_mcp_server.py --query set_timesteps" + @echo " make demo Catch the bad scheduler, pass the good one, docs CLI, tests" @echo " make demo-contribute First-contribution journey (KEEP=1 leaves files)" @echo " make demo-contribute-clean Remove the EulerLite contribution files" @echo " make demo-maintain Prove req #4: add a rule, rebuild, watch it propagate" @@ -28,22 +28,25 @@ doctor: @command -v $(PYTHON) >/dev/null || (echo "Need python3 on PATH"; exit 1) @$(PYTHON) -c "import yaml" 2>/dev/null || (echo "Need PyYAML: pip install -r requirements.txt"; exit 1) @test -f .cursor/hooks.json && test -f .cursor/mcp.json && test -f .cursor/commands/scaffold.md && test -f .cursorignore \ - && test -f .cursor/mcp-diffusers-docs.py && test -f .cursor/mcp-diffusers-docs.sh \ + && test ! -e .cursor/mcp-diffusers-docs.py && test ! -e .cursor/mcp-diffusers-docs.sh \ + && test ! -e .cursor/install-docs-mcp.sh && test ! -e overlay/mcp.optional.json \ && test -f .cursor/commands/search-docs.md && test -f .cursor/skills/search-docs/SKILL.md \ - || (echo "Missing Cursor wiring (hooks, mcp launcher, scaffold, search-docs, .cursorignore)"; exit 1) - @! grep -q workspaceFolder .cursor/mcp.json || (echo "mcp.json must not use workspaceFolder vars (Cloud stdio does not expand them)"; exit 1) - @test -f tools/attach_library.py && test -f overlay/OVERLAY.md \ - && test -f overlay/mcp.json && test -f overlay/mcp.optional.json \ + || (echo "Missing Cursor wiring or leftover docs-MCP launchers still present"; exit 1) + @$(PYTHON) -c "import json, pathlib; c=json.loads(pathlib.Path('.cursor/mcp.json').read_text()); assert c.get('mcpServers')=={}, c" \ + || (echo ".cursor/mcp.json must be {\"mcpServers\":{}}"; exit 1) + @test -f tools/attach_library.py && test -f overlay/OVERLAY.md && test -f overlay/mcp.json \ || (echo "Missing overlay attach tooling"; exit 1) - @$(PYTHON) -c "import json, pathlib; c=json.loads(pathlib.Path('overlay/mcp.json').read_text()); s=c['mcpServers']; assert 'diffusers-docs' in s and 'huggingface' not in s, c; assert s['diffusers-docs']['args']==['-u','.cursor/mcp-diffusers-docs.py'], s" \ - || (echo "overlay/mcp.json must be stdio diffusers-docs only (no Hub HTTP)"; exit 1) - @$(PYTHON) -c "import json, pathlib; s=json.loads(pathlib.Path('overlay/mcp.optional.json').read_text())['mcpServers']; assert 'diffusers-docs' in s and 'huggingface' in s, s" \ - || (echo "overlay/mcp.optional.json must list opt-in servers"; exit 1) - @$(PYTHON) tools/docs_mcp_server.py --selftest >/dev/null + @$(PYTHON) -c "import json, pathlib; c=json.loads(pathlib.Path('overlay/mcp.json').read_text()); assert c.get('mcpServers')=={}, c" \ + || (echo "overlay/mcp.json must be {\"mcpServers\":{}}"; exit 1) + @$(PYTHON) -c "import json, pathlib; e=json.loads(pathlib.Path('.cursor/environment.json').read_text()); assert 'mcpServerAllowlist' not in e and 'start' not in e, e" \ + || (echo "kit environment.json must not allowlist or start a docs MCP"; exit 1) + @$(PYTHON) -c "import json, pathlib; e=json.loads(pathlib.Path('overlay/environment.json').read_text()); assert 'mcpServerAllowlist' not in e and 'start' not in e, e" \ + || (echo "overlay environment.json must not allowlist or start a docs MCP"; exit 1) + @$(PYTHON) tools/docs_mcp_server.py --query "set_timesteps" >/dev/null @test -f tools/grokbot_sim.py && test -f tools/verify_scheduler_contract.py \ && test -f agents/grokbot-profiles.md && test -f .cursor/agents/grokbot-qa.md \ || (echo "Missing GrokBot / contract-verify tooling"; exit 1) - @echo "doctor OK: $(PYTHON) + PyYAML + Cursor files + MCP self-test + overlay" + @echo "doctor OK: $(PYTHON) + PyYAML + empty mcpServers + docs CLI --query + overlay" attach: $(PYTHON) tools/attach_library.py --target $(or $(TARGET),../diffusers) @@ -77,18 +80,18 @@ test: $(PYTHON) -m unittest discover -s tests -t . -v mcp: - $(PYTHON) tools/docs_mcp_server.py --selftest + $(PYTHON) tools/docs_mcp_server.py --query "set_timesteps" # The live-demo sequence: gate catches the from-memory scheduler (nonzero exit, -# shown), then passes the scaffolded one, then the MCP grounds an answer, then +# shown), then passes the scaffolded one, then the optional docs CLI, then # the contract tests are green. demo: @echo "\n========== 1. A new engineer's first-cut scheduler ==========" -$(PYTHON) tools/convention_check.py examples/candidate_scheduler @echo "\n========== 2. The scaffolded, convention-correct version ==========" $(PYTHON) tools/convention_check.py examples/scaffolded_scheduler - @echo "\n========== 3. Grounded doc-search via the MCP server ==========" - $(PYTHON) tools/docs_mcp_server.py --selftest + @echo "\n========== 3. Optional docs CLI fallback (--query) ==========" + $(PYTHON) tools/docs_mcp_server.py --query "set_timesteps" @echo "\n========== 4. Contract tests (zero install) ==========" $(PYTHON) -m unittest discover -s tests -t . -v @echo "\n========== 5. GrokBot QA simulation (read-side, does not gate) ==========" diff --git a/README.md b/README.md index 359d919..41eef72 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ takes: plan → design → build → review → test → deploy.** library ([alex-16moro/diffusers](https://github.com/alex-16moro/diffusers)). The live Cloud Agent demo launches **on the fork** after you show **this kit**. Install clones this kit to `ramp-kit/` and the gate/scaffold write into the real -`src/diffusers/schedulers/` tree. Grounding is the two scheduler source files -plus the file-scoped gate — overlay `.cursor/mcp.json` is empty by default -(opt-in: `overlay/mcp.optional.json`). Do not PR huggingface/diffusers. Fork -PRs are titled `[fork demo — not for upstream]`. See `overlay/OVERLAY.md`. +`src/diffusers/schedulers/` tree. Grounding is `.ai/` plus reference source +plus the file-scoped gate — overlay `.cursor/mcp.json` is empty by default. +Optional docs CLI: `python3 tools/docs_mcp_server.py --query "..."`. Do not PR +huggingface/diffusers. Fork PRs are titled `[fork demo — not for upstream]`. +See `overlay/OVERLAY.md`. --- @@ -74,7 +75,7 @@ machinery. | Convention registry | correctness / consistency | agent + reviewer | adding rows tagged `component:` | | Gate (`convention_check.py`) | catch-early + CI | QA + DevOps | reusing existing check types | | `/scaffold ` | build (first contribution) | engineer | dropping a template file | -| Doc-search **MCP** (`docs_mcp_server.py`) | discovery / grounding | engineer + PM | pointing at more docs | +| Docs CLI (`docs_mcp_server.py --query`) | discovery / grounding | engineer + PM | pointing at more docs | | Projections | plan / review / deploy | PM / QA / DevOps | fixed audience set | Concretely: each rule carries a `component:` tag, and `make build` emits one @@ -87,15 +88,15 @@ as it grows. | # | Requirement | How this kit meets it | |---|-------------|-----------------------| -| 1 | Scaffold a correct first contribution | `/scaffold ` command + per-component `.cursor/rules/10-*.mdc` + a correct reference (`examples/scaffolded_scheduler/`) + the `diffusers-docs` MCP for grounding | +| 1 | Scaffold a correct first contribution | `/scaffold ` command + per-component `.cursor/rules/10-*.mdc` + a correct reference (`examples/scaffolded_scheduler/`) + grounding in `.ai/` / reference source (optional `docs_mcp_server.py --query`) | | 2 | Catch mistakes early, strengthen tests | `tools/convention_check.py` (AST + regex) including **TEST002** weak-test / determinism / shape-dtype; in-editor hook; GrokBot QA sim reads `--json` | | 3 | Fit CI, stay in approved boundaries | Generated `.github/workflows/convention-gate.yml`: same gate + **projection drift** + **contract re-verify** + tests. `.cursorignore`. Overlay MCP empty by default. File-scoped on the fork. | | 4 | Stay maintainable as the library evolves | One `rules.yaml` with `owner:` tags; `make build` / `make demo-maintain`; `verify_scheduler_contract.py` vs fork source; build fails if a check or owner is missing | | 5 | Work for PM, QA, DevOps too | Same registry. Projections **group by owner**. GrokBot specs + iPhone/desktop profiles generated per around-engineering role; `grokbot_sim.py --role qa` is the reproducible briefing, not a second gate. Paste pack: `make grokbot-pack` / `docs/GROKBOT.md` | Built for **Cursor**: rules (`.cursor/rules/*.mdc`), a command -(`.cursor/commands/scaffold.md`), an edit hook (`.cursor/hooks.json`), an MCP -server (`.cursor/mcp.json` → `tools/docs_mcp_server.py`), and boundaries +(`.cursor/commands/scaffold.md`), an edit hook (`.cursor/hooks.json`), empty +default MCP (`.cursor/mcp.json` → `{"mcpServers":{}}`), and boundaries (`.cursorignore`) — all native Cursor primitives, checked into this repo. The enforcement itself is plain Python, so nothing is locked to Cursor. @@ -104,13 +105,13 @@ The enforcement itself is plain Python, so nothing is locked to Cursor. ```bash pip install -r requirements.txt # PyYAML; the only required dependency -make doctor # python3 + PyYAML + Cursor files present +make doctor # python3 + PyYAML + empty mcp.json + Cursor files present make build # regenerate every audience surface from the registry -make demo # catch the bad scheduler, pass the good one, MCP, tests +make demo # catch the bad scheduler, pass the good one, docs CLI, tests make demo-contribute # first-contribution journey (KEEP=1 leaves the new files) make check # run the gate on the whole repo (exit code = # blocking) make test # contract tests — zero third-party installs needed -make mcp # self-test the diffusers-docs MCP server (NDJSON handshake) +make mcp # optional docs CLI: docs_mcp_server.py --query set_timesteps make demo-maintain # add a rule, rebuild, watch it propagate to every surface (req #4) make grokbot ROLE=qa # role briefing from sample gate JSON (does not gate) make grokbot-pack # paste-ready Grok Bot iPhone/desktop profiles @@ -122,8 +123,8 @@ The 90-second demo (`make demo`) shows: gate catches **8 blocking + 2 warnings**, each with a rule id and a fix. 2. The scaffolded, convention-correct version (`examples/scaffolded_scheduler/`) — **0 findings**. -3. The `diffusers-docs` MCP server answers a grounded query over the library's - docs (newline-delimited JSON-RPC, the stdio framing Cursor uses). +3. Optional docs CLI: `python3 tools/docs_mcp_server.py --query "set_timesteps"` + over the library's docs (keyword search; not a Cursor MCP server). 4. Contract tests green, with numeric determinism skipped cleanly when torch isn't installed. @@ -133,10 +134,10 @@ is the customer simulation: **kit first** (catch the bad cut), then **fork** ## What I deliberately scoped OUT (and why) -- **Embeddings-based ranking / Hub HTTP MCP.** Kit Desktop and the fork overlay - ship stdio MCP (`tools/docs_mcp_server.py`, NDJSON). Cloud dropdown: - `diffusers-docs-mcp`. Hub HTTP stays out of the default (OAuth, wrong corpus). - Embeddings stay out: keyword over curated docs is the debuggable baseline. +- **Embeddings-based ranking / a live docs MCP.** Default overlay MCP is empty + (`{"mcpServers":{}}`). Optional CLI: `python3 tools/docs_mcp_server.py --query`. + Hub HTTP stays out (OAuth, wrong corpus). Embeddings stay out: keyword over + curated docs is the debuggable baseline. - **A tool per SDLC step.** Plan/build/review/test/CI are projections of one YAML. A sixth "deploy" capability would fragment the kit. - **Model/pipeline scaffolds.** I built the scheduler path end-to-end rather than @@ -183,18 +184,16 @@ Each upstream rule cites what it was checked against in its `source:` field. conventions/rules.yaml the single source of truth (component-tagged) tools/convention_check.py the runnable gate (AST + regex) tools/build_projections.py renders every audience surface (per-component .mdc) -tools/docs_mcp_server.py the diffusers-docs MCP server (NDJSON JSON-RPC) +tools/docs_mcp_server.py optional docs CLI (`--query`; leftover `--serve`) tools/demo_contribute.py CLI twin of the live `/scaffold` contribution -knowledge/diffusers-docs/ seed doc corpus for the MCP (override with a real checkout) +knowledge/diffusers-docs/ seed doc corpus for the CLI (override with a real checkout) templates/ scaffold templates + "add a component" guide src/diffusers/schedulers/ stand-in path `/scaffold` writes to .cursor/rules/*.mdc agent conventions (generated; 00-core + 10-) .cursor/commands/scaffold.md /scaffold guided first task .cursor/hooks.json afterFileEdit → the same gate CI runs -.cursor/mcp.json kit Desktop MCP (stdio; no ${workspaceFolder}) -overlay/mcp.json fork overlay default (stdio diffusers-docs, no Hub HTTP) -overlay/mcp.optional.json opt-in Hub HTTP + stdio for Desktop only -.cursor/mcp-diffusers-docs.py cwd-independent stdio launcher (Cloud-safe) +.cursor/mcp.json empty default (`{"mcpServers":{}}`) +overlay/mcp.json fork overlay default (same empty servers) .cursorignore approved context boundary (req. 3) AGENTS.md tool-agnostic mirror (generated) examples/candidate_scheduler/ the "from memory" first cut (fails the gate) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a1fbc02..cee5ee0 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -42,7 +42,7 @@ fork source (`tools/verify_scheduler_contract.py`). | **AST for structural rules** | Class bases, method presence, `@register_to_config`, mutable defaults, docstrings are *structure*. AST doesn't false-positive on a comment or a reformat the way grep does. Regex is used only for line-level lexical patterns. | | **`.cursorignore` for boundaries** | Requirement 3 says "don't pull in context from outside approved boundaries." `.cursorignore` is exactly that mechanism — it removes paths from the agent's context window, so a scaffold can't copy the known-bad fixture (or, in a customer repo, vendored/secret/generated code) toward prod. | | **`.cursor/hooks.json` `afterFileEdit`** | Runs the gate on the edited file the moment the agent touches it — the same script CI runs — so feedback is instant and CI surprises are rare. The hook reads Cursor's stdin JSON (`file_path`, `edits`). | -| **MCP NDJSON stdio** | Cursor's local MCP transport is JSON-RPC as newline-delimited JSON. `tools/docs_mcp_server.py` writes NDJSON (it still *reads* Content-Length). Cloud stdio cannot set `cwd` or expand `${workspaceFolder}`. The launcher plus `install-docs-mcp.sh` make `python3 -u .cursor/mcp-diffusers-docs.py` resolve from `/agent`. Hub HTTP MCP is Hub search + OAuth — it is **not** in the default overlay. Cloud dropdown: `diffusers-docs-mcp`. | +| **Empty default MCP + docs CLI** | `.cursor/mcp.json` is `{"mcpServers":{}}`. Grounding is `.ai/` plus reference source. Optional: `python3 tools/docs_mcp_server.py --query`. `--serve` remains in that file as leftover implementation, not a Cursor default. | | **One script, `--json` + exit code** | Exit code = number of blocking findings makes it a drop-in CI gate anywhere; `--json` feeds dashboards / PR annotations. No GPU, no downloads, no network → cheapest runner. | | **Tests in stdlib `unittest`** | Runs on a fresh clone with zero installs (structural contract), and layers in numeric determinism when torch is present. The demo can't fail because a wheel didn't download. | @@ -63,7 +63,7 @@ registry is component-agnostic; adding a `models.md` rule set is additive. | Build | Agent rules + `/scaffold ` produce a correct first cut | | Review | Gate blocks the machine-checkable items; QA checklist focuses humans on judgment | | Test | Generated contract test (structural + behavioral); TEST001 requires the file; TEST002 requires assertions, same-seed determinism, and shape/dtype | -| CI / Release gate | Identical gate in `.github/workflows/convention-gate.yml` + MCP self-test + tests; `.cursorignore` keeps changes in the agent's context boundary. Green gate = merge-eligible (release clearance). I stop here — no customer deploy env or auth model, so I don't fake "deploy". | +| CI / Release gate | Identical gate in `.github/workflows/convention-gate.yml` + docs CLI `--query` + tests; `.cursorignore` keeps changes in the agent's context boundary. Green gate = merge-eligible (release clearance). I stop here — no customer deploy env or auth model, so I don't fake "deploy". | ## Failure modes I designed around diff --git a/docs/CURSOR_PROMPTS.md b/docs/CURSOR_PROMPTS.md index d7b2f36..9ba311d 100644 --- a/docs/CURSOR_PROMPTS.md +++ b/docs/CURSOR_PROMPTS.md @@ -7,18 +7,17 @@ before launching a Cloud Agent. **Primary live write:** Cloud Agent on **Runbook:** `docs/LIVE_DEMO.md`. -Default overlay MCP is **stdio `diffusers-docs`**, not Hub HTTP. Cloud dropdown: -`diffusers-docs-mcp` or `python3 -u .cursor/mcp-diffusers-docs.py`. The first -PR still copies templates and runs the file-scoped gate; `search_docs` is -available, not a deliverable. Extra servers live in `overlay/mcp.optional.json`. +Default overlay MCP is **empty** (`{"mcpServers":{}}`). Grounding is root +`AGENTS.md`, `.ai/`, and reference source. Optional: +`python3 tools/docs_mcp_server.py --query "..."`. The first PR still copies +templates and runs the file-scoped gate. --- ## Prompt A — first contribution ON THE FORK (paste this) -Launch a **new** Cloud Agent on `alex-16moro/diffusers`, branch **`main`**. Enable -stdio MCP (`diffusers-docs-mcp` or `python3 -u .cursor/mcp-diffusers-docs.py`). -Do **not** enable Hub HTTP MCP. The draft PR **base must be `main`** so overlay +Launch a **new** Cloud Agent on `alex-16moro/diffusers`, branch **`main`**. Do +**not** enable a docs MCP dropdown. The draft PR **base must be `main`** so overlay CI (`ramp-kit-overlay` / `overlay-gate`) actually runs. Do not stack onto a `cursor/…` topic branch. @@ -78,15 +77,11 @@ primary; `make demo-contribute KEEP=1` as fallback. ## Pre-flight (20 seconds, so nothing stalls live) -- **Doctor:** `make doctor` — python3, PyYAML, Cursor files, MCP self-test, - overlay stdio `diffusers-docs` (no Hub HTTP). -- **Kit Desktop MCP:** Cursor → Settings → **MCP** → enable `diffusers-docs`. - Config is `.cursor/mcp.json` (`python3 -u .cursor/mcp-diffusers-docs.py`). - It must **not** use `${workspaceFolder}`. Cloud dropdown: `diffusers-docs-mcp`. -- **Fork / Cloud Agents:** overlay `.cursor/mcp.json` is stdio `diffusers-docs` - only. Project MCP is **not** auto-loaded on Cloud — paste - `diffusers-docs-mcp` (or `python3 -u .cursor/mcp-diffusers-docs.py`) in the - MCP dropdown. Do **not** add Hugging Face HTTP MCP (Hub search + OAuth). +- **Doctor:** `make doctor` — python3, PyYAML, Cursor files, empty `mcpServers`, + docs CLI `--query`. +- **Default MCP:** `.cursor/mcp.json` is `{"mcpServers":{}}` on the kit and the + fork overlay. Ground in `AGENTS.md` / `.ai/` / reference source. Optional: + `python3 tools/docs_mcp_server.py --query "set_timesteps"`. - **Confirm rules loaded:** the Agent sidebar should show `00-conventions` active; `10-scheduler` auto-attaches once a scheduler file is open. - **Torch is optional.** The gate and the structural/signature tests still pass @@ -155,9 +150,10 @@ the tooling. Keep it minimal; do not implement a real model. - **Kit first, then fork:** catch-early on this repo; the write lands on the real library tree. -- **Grounding:** the agent reads `ramp-kit/conventions/rules.yaml` and copies - the overlay templates. Euler/DDPM source is how *we* verified the YAML, not - the first-PR recipe. MCP is opt-in, not the demo. +- **Grounding:** the agent reads `ramp-kit/conventions/rules.yaml`, root + `AGENTS.md` / `.ai/`, and copies the overlay templates. Euler/DDPM source is + how *we* verified the YAML, not the first-PR recipe. Optional docs CLI is + not the demo. - **Catch-early:** you run this on the **kit**, not inside Prompt A. - **Correct scaffold:** scheduler + matching test with no leftover placeholders; registered in inits/dummies; file-scoped gate 0 blocking; `TODO(engineer)` diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 6307a6a..908442e 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -22,9 +22,9 @@ These were made before this submission pass. Do not silently reopen them. `.github/workflows/ramp-kit-overlay.yml` (attached), not the kit's `convention-gate.yml`. -5. **Default fork MCP is stdio `diffusers-docs` only.** Never Hub HTTP (OAuth). - Cloud dropdown: `diffusers-docs-mcp` or `python3 -u .cursor/mcp-diffusers-docs.py`. - The launcher must resolve from the workspace root (`/agent`), not only the - git repo. CLI `--query` remains a fallback. +5. **Default overlay MCP is empty.** `.cursor/mcp.json` is `{"mcpServers":{}}`. + Grounding is `.ai/` plus reference source. Optional CLI: + `python3 tools/docs_mcp_server.py --query "..."`. No Cloud dropdown + command is required. 6. **Scaffold the contract, not the sampler.** Leave `TODO(engineer)` in `step()`. diff --git a/docs/LIVE_DEMO.md b/docs/LIVE_DEMO.md index 298f1cd..911b581 100644 --- a/docs/LIVE_DEMO.md +++ b/docs/LIVE_DEMO.md @@ -50,9 +50,9 @@ Pre-flight (before they sit): - **Kit (this repo):** `make doctor`. Catch-early fixture is here. - **Cloud Agent:** launch on **`alex-16moro/diffusers`**, branch `main` (not this - kit). Overlay `.cursor/mcp.json` is stdio `diffusers-docs` only. Enable that - in the Cloud MCP dropdown (`diffusers-docs-mcp` or - `python3 -u .cursor/mcp-diffusers-docs.py`). Do **not** enable Hub HTTP. + kit). Overlay `.cursor/mcp.json` is empty (`{"mcpServers":{}}`). Grounding is + root `AGENTS.md`, `.ai/`, and reference source. Optional: + `python3 ramp-kit/tools/docs_mcp_server.py --query "set_timesteps"`. The paste is Prompt A: copy templates, file-scoped gate. Catch-early stays on this kit, not in the paste. - Paste **Prompt A (fork)** from `docs/CURSOR_PROMPTS.md`. @@ -95,7 +95,7 @@ Optional 2-minute add-on if Grok Bot is on a phone in the room: open into **Edit Profile**, send the first message. The Bot is a reader of the same gate JSON you just produced. -If they ask for a docs search without MCP: +If they ask for a docs search: ```bash python3 ramp-kit/tools/docs_mcp_server.py --query "scheduler set_timesteps step" @@ -114,7 +114,7 @@ topic branch. ```bash make doctor -make demo # catch-early, scaffolded 0 findings, MCP, tests, GrokBot QA, contract re-verify +make demo # catch-early, scaffolded 0 findings, docs CLI, tests, GrokBot QA, contract re-verify make demo-contribute KEEP=1 # leaves EulerLite on disk # walk the printed steps with them make grokbot ROLE=qa # same sim from candidate_scheduler --json @@ -159,17 +159,17 @@ Two files, two launch targets: | Launch on | File | What install / start do | |-----------|------|-------------------| -| **This kit** | `.cursor/environment.json` | `install`: `.cursor/cloud-install.sh` (pyyaml, CPU torch, diffusers, `diffusers-docs-mcp` PATH shim, MCP `--selftest`). `start`: re-run the shim only (snapshot boots skip `install`). Allowlist command: `diffusers-docs-mcp`. Declares the fork as a repo dependency. | -| **The fork** | `overlay/environment.json` (copied to fork `.cursor/environment.json` by `make attach`) | Clones this kit to `ramp-kit/` if missing, then the same `cloud-install.sh`. `start` refreshes `diffusers-docs-mcp`. Fork `.cursor/mcp.json` is stdio `diffusers-docs` only (no Hub HTTP). | +| **This kit** | `.cursor/environment.json` | `install`: `.cursor/cloud-install.sh` (pyyaml, CPU torch, diffusers, docs CLI `--query` smoke). No `start`, no `mcpServerAllowlist`. Declares the fork as a repo dependency. | +| **The fork** | `overlay/environment.json` (copied to fork `.cursor/environment.json` by `make attach`) | Clones this kit to `ramp-kit/` if missing, then the same `cloud-install.sh`. Fork `.cursor/mcp.json` is `{"mcpServers":{}}`. | Dry-run locally (does not boot a Cloud VM): ```bash python3 -c "import json; print(json.dumps(json.load(open('overlay/environment.json')), indent=2))" python3 -c "import json; print(json.dumps(json.load(open('.cursor/environment.json')), indent=2))" -make attach TARGET=../diffusers # copies stdio mcp.json; does not touch AGENTS.md / .ai/ +make attach TARGET=../diffusers # copies empty mcp.json; does not touch AGENTS.md / .ai/ python3 -c "import json; print(json.load(open('../diffusers/.cursor/mcp.json')))" -# expected: mcpServers.diffusers-docs stdio, no huggingface +# expected: {"mcpServers": {}} ``` If a Cloud Agent is already on the fork, the boot install is that JSON. You do not re-run attach live unless `.cursor/` is missing. Clearance is the file-scoped overlay gate **plus** library `make style` / `make quality`. Inherited jobs that need Hugging Face runners or size labels may still be red on this personal fork; that is hosting, not a reason to skip `make quality`. diff --git a/docs/SCORECARD.md b/docs/SCORECARD.md index c44f8c7..50a0daa 100644 --- a/docs/SCORECARD.md +++ b/docs/SCORECARD.md @@ -7,7 +7,7 @@ missing. Cosmetic means it only restates the same facts in another file. |---|-------------|---------|----------|--------------------------| | 1 | Scaffold a correct first contribution without reading the whole library | **MET** | `/scaffold` copies overlay templates; file-scoped gate on the new scheduler + matching test. Prompt A / overlay scaffold stop at `rules.yaml` blocking ids — they do not send the engineer into Euler/DDPM source, docs pages, or public exports. `verify_scheduler_contract.py` still checks fork source when *we* refresh the YAML. | Load-bearing: a scaffold that fails SCHED001–003 cannot ship. | | 2 | Catch mistakes early; strengthen tests (deprecated APIs, anti-patterns, **missing or weak tests**) | **MET** | Existing gate (DEPR001, DEVICE001, MUT001, TEST001). **TEST002** (`test_adequacy`): empty `test_*` functions, same-seed determinism, shape **and** dtype. Verify: zero-assertion tempfile is flagged; `examples/scaffolded_scheduler` is 0 findings. | Load-bearing: TEST002 is `severity: block`. | -| 3 | Fit CI; stay in approved context boundaries | **MET** | Kit CI: `.github/workflows/convention-gate.yml` (`--all` **in-kit only**) + projection drift + contract re-verify. Fork CI: attach copies **`overlay/ramp-kit-overlay.yml`** (file-scoped `convention_check` on changed scheduler/test files; never `--all`; does not delete inherited HF workflows). Overlay `mcp.json` is stdio `diffusers-docs` only (no Hub HTTP). Cloud dropdown: `diffusers-docs-mcp`. `.cursorignore` hides the known-bad fixture. | Load-bearing: kit drift job is red on hand-edited generated files; fork overlay job is the customer check-run; PATH/workspace launcher is the Cloud-safe stdio spawn. | +| 3 | Fit CI; stay in approved context boundaries | **MET** | Kit CI: `.github/workflows/convention-gate.yml` (`--all` **in-kit only**) + projection drift + contract re-verify. Fork CI: attach copies **`overlay/ramp-kit-overlay.yml`** (file-scoped `convention_check` on changed scheduler/test files; never `--all`; does not delete inherited HF workflows). Overlay `mcp.json` is `{"mcpServers":{}}`. `.cursorignore` hides the known-bad fixture. | Load-bearing: kit drift job is red on hand-edited generated files; fork overlay job is the customer check-run; empty default MCP is the Cloud-safe overlay. | | 4 | Maintainable without the author | **MET** | `owner` on every rule; `make build` / `make demo-maintain`; `tools/verify_scheduler_contract.py` checks fork reference source vs SCHED001–003 (passes today; renamed `set_timesteps` reports DRIFT). Adding a component remains registry + template, not new machinery. | Load-bearing: missing `owner` or unimplemented `check` fails `build_projections.py`. | | 5 | One solution, multiple audiences (PM, QA, DevOps) | **MET** | Same registry, `owner` tags, generated specs. **GrokBot sim fuses a role-native change-context** (`examples/change_context.example.json`) with gate JSON: DevOps leads CI/drift/inherited workflows, PM leads DoD state + merge-eligibility + issue (status-view, not an authored gate), QA leads test-adequacy + residual math risk. Every claim is tagged `[gate]`/`[ci]`/`[issue]`/`[drift]`; each role has a **Cannot see** section. `grokbot_sim.py --role {qa,pm,devops} --context …` never gates. iOS steps: `docs/GROKBOT.md`. | Load-bearing: role-native sim + grounding tags + owner field. Specs without the sim would be cosmetic. | diff --git a/docs/TALK_TRACK.md b/docs/TALK_TRACK.md index c5cae9b..7207ee1 100644 --- a/docs/TALK_TRACK.md +++ b/docs/TALK_TRACK.md @@ -69,8 +69,8 @@ open on the fork until they have seen the registry and the gate. Do not run kit `KEEP=1` before the fork agent if you want those files to appear live on the fork. -MCP is **stdio `diffusers-docs`**, not Hub HTTP. Cloud dropdown: -`diffusers-docs-mcp`. If they ask how docs were searched without the tool: CLI +Default `.cursor/mcp.json` is empty. Grounding is `AGENTS.md`, `.ai/`, and +reference source. Optional CLI: `python3 ramp-kit/tools/docs_mcp_server.py --query "..."`. ### 22–30 min · Multi-audience + boundaries @@ -87,17 +87,15 @@ MCP is **stdio `diffusers-docs`**, not Hub HTTP. Cloud dropdown: ### 30–38 min · Judgment: the design that scales, and what I skipped - "It's five fixed primitives driven by data — registry, gate, scaffold, docs - search (CLI / optional MCP), projections. Adding a component (model, pipeline) + CLI (`--query`), projections. Adding a component (model, pipeline) is registry rows + a template, not a new command. That's `make build` regenerating a `10-.mdc` from a tag — I can show that live in under a minute." - "I did not grow a capability per SDLC step. That is how these kits fragment." -- "The fork overlay ships **stdio `diffusers-docs`**, not Hub HTTP. Cloud Agents - skip project `mcp.json`; paste `diffusers-docs-mcp` in the dropdown. Stdio - cannot set `cwd` or expand `${workspaceFolder}` — the launcher plus PATH shim - fix that. Hub HTTP MCP is Hub search + OAuth, not library source. Keyword - MCP/`--query` is the same server. I skipped *embeddings* inside that search — - keyword over curated docs first." +- "The fork overlay ships **empty** `mcp.json`. Grounding is `.ai/` plus + reference source. Optional CLI: `docs_mcp_server.py --query`. Hub HTTP is + Hub search + OAuth, not library source. I skipped *embeddings* — keyword + over curated docs first." - "I went deep on schedulers, not shallow on all three components, because schedulers have the crispest enforceable contract — the best proof." - "No auto-fix: on a numerical library, auto-fix is where you inject silent wrong @@ -182,10 +180,11 @@ This turns the single biggest risk to the thesis into its strongest proof. > I'd find out' is the posture I want on your account too." **"Why not MCP / embeddings / a tool per step?"** -> "Grounding that survives a skeptical reviewer is the two scheduler source -> files plus the gate. MCP on Cloud failed discovery and Hub OAuth is the wrong -> corpus. Embeddings add a model I don't need for a 45-minute proof. A tool per -> SDLC step is how the kit would fragment the moment you asked for deploy." +> "Grounding that survives a skeptical reviewer is `.ai/` plus the two scheduler +> source files plus the gate. A live docs MCP on Cloud failed discovery and Hub +> OAuth is the wrong corpus. Embeddings add a model I don't need for a 45-minute +> proof. A tool per SDLC step is how the kit would fragment the moment you asked +> for deploy." ### If asked to extend it live (they said you'll reuse this) - Add a rule to `rules.yaml` (e.g. a new deprecated import), `make build`, diff --git a/knowledge/diffusers-docs/philosophy.md b/knowledge/diffusers-docs/philosophy.md index 4b4cfbd..c623293 100644 --- a/knowledge/diffusers-docs/philosophy.md +++ b/knowledge/diffusers-docs/philosophy.md @@ -1,5 +1,5 @@ diff --git a/overlay/OVERLAY.md b/overlay/OVERLAY.md index e1bfa6f..e9c00cd 100644 --- a/overlay/OVERLAY.md +++ b/overlay/OVERLAY.md @@ -31,13 +31,12 @@ The first contribution: Do not copy `ramp-kit/examples/candidate_scheduler/`. -`.cursor/mcp.json` ships **stdio `diffusers-docs` only** (`python3 -u -.cursor/mcp-diffusers-docs.py`). Do not enable Hub HTTP MCP (OAuth). Cloud -dropdown: `diffusers-docs-mcp` (PATH shim from `install-docs-mcp.sh`) or the -same python3 command. The launcher finds `ramp-kit/tools/docs_mcp_server.py` -from `/agent` as well as the repo root. +Default `.cursor/mcp.json` is `{"mcpServers":{}}`. Ground in root `AGENTS.md`, +`.ai/`, and the reference source those files name. Optional keyword search: -Hub HTTP and extra servers stay in `.cursor/mcp.optional.json`. +```bash +python3 ramp-kit/tools/docs_mcp_server.py --query "set_timesteps" +``` ## Demo (Cloud Agent) diff --git a/overlay/environment.json b/overlay/environment.json index 6c58945..8b5a0d1 100644 --- a/overlay/environment.json +++ b/overlay/environment.json @@ -1,13 +1,6 @@ { "install": "if [ ! -d ramp-kit/.git ]; then git clone --depth 1 https://github.com/alex-16moro/diffuser_agent.git ramp-kit; fi && bash ramp-kit/.cursor/cloud-install.sh", - "start": "bash ramp-kit/.cursor/install-docs-mcp.sh", "repositoryDependencies": [ "github.com/alex-16moro/diffuser_agent" - ], - "mcpServerAllowlist": [ - { - "name": "diffusers-docs", - "command": "diffusers-docs-mcp" - } ] } diff --git a/overlay/mcp.json b/overlay/mcp.json index cbf2bc8..8c3bf0d 100644 --- a/overlay/mcp.json +++ b/overlay/mcp.json @@ -1,12 +1 @@ -{ - "mcpServers": { - "diffusers-docs": { - "type": "stdio", - "command": "python3", - "args": ["-u", ".cursor/mcp-diffusers-docs.py"], - "env": { - "PYTHONUNBUFFERED": "1" - } - } - } -} +{"mcpServers":{}} diff --git a/overlay/mcp.optional.json b/overlay/mcp.optional.json deleted file mode 100644 index 9080a64..0000000 --- a/overlay/mcp.optional.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mcpServers": { - "diffusers-docs": { - "type": "stdio", - "command": "python3", - "args": ["-u", ".cursor/mcp-diffusers-docs.py"], - "env": { - "PYTHONUNBUFFERED": "1" - } - }, - "huggingface": { - "url": "https://huggingface.co/mcp" - } - } -} diff --git a/overlay/scaffold.md b/overlay/scaffold.md index 110ce7f..e7a0f63 100644 --- a/overlay/scaffold.md +++ b/overlay/scaffold.md @@ -26,9 +26,19 @@ Read `ramp-kit/conventions/rules.yaml`. Blocking ids for this contribution: SCHED001, SCHED002, SCHED003, REPRO001, DEVICE001, DEPR001, MUT001, TEST001, TEST002. -Read root `AGENTS.md` (and `.ai/` only as that guide directs). The templates -already satisfy the overlay registry. Library CI (`make style` / `make quality` -/ copies / dummies) is also required before the PR. +Read root `AGENTS.md` and `.ai/` (library guide / review-rules / skills). Do +not overwrite them. Also read the reference source those files name. Optional +keyword search of this checkout's docs: + +```bash +python3 ramp-kit/tools/docs_mcp_server.py --query "set_timesteps" +``` + +That CLI is a fallback, not a deliverable. Default `.cursor/mcp.json` has no +servers. + +The templates already satisfy the overlay registry. Library CI (`make style` / +`make quality` / copies / dummies) is also required before the PR. Stay inside `.cursorignore`. Do not read or copy `ramp-kit/examples/candidate_scheduler/`. diff --git a/overlay/search-docs.md b/overlay/search-docs.md index 92b618b..f8ed8cc 100644 --- a/overlay/search-docs.md +++ b/overlay/search-docs.md @@ -1,9 +1,9 @@ -# /search-docs — library docs in THIS checkout (optional) +# /search-docs — optional keyword search of THIS checkout's docs Usage: `/search-docs ` -Prefer `ramp-kit/conventions/rules.yaml` + the gate. MCP is stdio -`diffusers-docs` (Cloud dropdown: `diffusers-docs-mcp`). No Hub HTTP. +Prefer `ramp-kit/conventions/rules.yaml`, root `AGENTS.md`, `.ai/`, and the +reference source those files name. Default `.cursor/mcp.json` has no servers. ```bash python3 ramp-kit/tools/docs_mcp_server.py --query $1 @@ -14,5 +14,3 @@ If `$1` is empty: ```bash python3 ramp-kit/tools/docs_mcp_server.py --query "scheduler set_timesteps step SchedulerMixin register_to_config" ``` - -If the MCP tool `search_docs` is in your list, call that instead. diff --git a/overlay/search-docs.skill.md b/overlay/search-docs.skill.md index ebcd8c2..a776a8e 100644 --- a/overlay/search-docs.skill.md +++ b/overlay/search-docs.skill.md @@ -1,6 +1,6 @@ --- name: search-docs -description: Optional docs CLI for this diffusers checkout. Prefer the gate; MCP is stdio search_docs. +description: Optional docs CLI for this diffusers checkout. Prefer AGENTS.md, .ai/, and the gate. Default mcp.json has no servers. --- # Search this library's docs @@ -10,6 +10,5 @@ python3 ramp-kit/tools/docs_mcp_server.py --query "" ``` Cite provenance (`diffusers checkout` vs bundled snapshot). -`ramp-kit/conventions/rules.yaml` is the gate. -`.cursor/mcp.json` is stdio `diffusers-docs` only (no Hub HTTP). -Cloud dropdown: `diffusers-docs-mcp`. +`ramp-kit/conventions/rules.yaml` is the gate. Root `AGENTS.md` and `.ai/` +are the library guide. Default `.cursor/mcp.json` has no servers. diff --git a/projections/devops/ci-gate.yml b/projections/devops/ci-gate.yml index 8b1fddc..215c00a 100644 --- a/projections/devops/ci-gate.yml +++ b/projections/devops/ci-gate.yml @@ -19,8 +19,8 @@ jobs: run: python tools/build_projections.py && git diff --exit-code - name: Scheduler contract re-verify run: python tools/verify_scheduler_contract.py - - name: MCP doc-server self-test - run: python tools/docs_mcp_server.py --selftest + - name: Docs CLI --query fallback + run: python tools/docs_mcp_server.py --query "set_timesteps" - name: Contract tests run: python -m unittest discover -s tests -t . - name: Machine-readable report diff --git a/tests/test_tooling.py b/tests/test_tooling.py index 697508a..b61c40d 100644 --- a/tests/test_tooling.py +++ b/tests/test_tooling.py @@ -112,96 +112,32 @@ def test_env_docs_root_wins(self): class TestMcpFraming(unittest.TestCase): - def test_selftest_emits_ndjson(self): + def test_query_set_timesteps(self): proc = subprocess.run( - [sys.executable, str(ROOT / "tools" / "docs_mcp_server.py"), "--selftest"], + [sys.executable, str(ROOT / "tools" / "docs_mcp_server.py"), + "--query", "set_timesteps"], cwd=ROOT, capture_output=True, text=True, check=False, ) self.assertEqual(proc.returncode, 0, proc.stderr + proc.stdout) - self.assertNotIn("Content-Length", proc.stdout) - self.assertIn("NDJSON", proc.stdout) - self.assertIn("search_docs", proc.stdout) - - -class TestOverlayMcpDefaults(unittest.TestCase): - def test_overlay_mcp_json_is_stdio_docs_only(self): - raw = (ROOT / "overlay" / "mcp.json").read_text() - self.assertNotIn("${workspaceFolder}", raw) - cfg = json.loads(raw) - servers = cfg["mcpServers"] - self.assertIn("diffusers-docs", servers) - self.assertNotIn("huggingface", servers) - stdio = servers["diffusers-docs"] - self.assertEqual(stdio.get("type"), "stdio") - self.assertEqual(stdio["command"], "python3") - self.assertEqual(stdio["args"], ["-u", ".cursor/mcp-diffusers-docs.py"]) - - def test_overlay_mcp_optional_lists_opt_in_servers(self): - raw = (ROOT / "overlay" / "mcp.optional.json").read_text() - self.assertNotIn("${workspaceFolder}", raw) - cfg = json.loads(raw) - servers = cfg["mcpServers"] - self.assertIn("diffusers-docs", servers) - self.assertIn("huggingface", servers) - stdio = servers["diffusers-docs"] - self.assertEqual(stdio.get("type"), "stdio") - self.assertEqual(stdio["command"], "python3") - self.assertEqual(stdio["args"], ["-u", ".cursor/mcp-diffusers-docs.py"]) - self.assertEqual(servers["huggingface"].get("url"), "https://huggingface.co/mcp") - - def test_attach_copies_stdio_default_and_optional(self): - src = (ROOT / "tools" / "attach_library.py").read_text() - self.assertIn('shutil.copy2(overlay / "mcp.json", cursor / "mcp.json")', src) - self.assertIn( - 'shutil.copy2(overlay / "mcp.optional.json", cursor / "mcp.optional.json")', - src, - ) - + self.assertTrue(proc.stdout.strip(), proc.stdout) -class TestMcpLauncher(unittest.TestCase): - def test_project_mcp_json_has_no_workspace_folder_var(self): - raw = (ROOT / ".cursor" / "mcp.json").read_text() - self.assertNotIn("${workspaceFolder}", raw) - self.assertNotIn("${workspaceFolderBasename}", raw) - cfg = json.loads(raw) - server = cfg["mcpServers"]["diffusers-docs"] - self.assertEqual(server.get("type"), "stdio") - self.assertEqual(server["command"], "python3") - self.assertEqual(server["args"], ["-u", ".cursor/mcp-diffusers-docs.py"]) - self.assertTrue((ROOT / ".cursor" / "mcp-diffusers-docs.py").is_file()) - self.assertTrue((ROOT / ".cursor" / "mcp-diffusers-docs.sh").is_file()) - self.assertTrue((ROOT / ".cursor" / "commands" / "search-docs.md").is_file()) - self.assertTrue((ROOT / ".cursor" / "skills" / "search-docs" / "SKILL.md").is_file()) - - def test_launcher_serves_from_unrelated_cwd(self): - """Cloud stdio has no cwd; the launcher must still find the server.""" - sys.path.insert(0, str(ROOT / "tools")) - from docs_mcp_server import _frame # noqa: WPS433 - - reqs = [ - {"jsonrpc": "2.0", "id": 1, "method": "initialize", - "params": {"protocolVersion": "2025-03-26", "capabilities": {}}}, - {"jsonrpc": "2.0", "method": "notifications/initialized"}, - {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}, - ] + def test_selftest_emits_ndjson(self): proc = subprocess.run( - [sys.executable, "-u", str(ROOT / ".cursor" / "mcp-diffusers-docs.py")], - cwd=tempfile.gettempdir(), - input=b"".join(_frame(r) for r in reqs), + [sys.executable, str(ROOT / "tools" / "docs_mcp_server.py"), "--selftest"], + cwd=ROOT, capture_output=True, - timeout=10, + text=True, check=False, ) - self.assertEqual(proc.returncode, 0, proc.stderr.decode("utf-8", "replace")) - self.assertIn(b"search_docs", proc.stdout) - self.assertNotIn(b"Content-Length", proc.stdout) - self.assertNotIn(b"${workspaceFolder}", proc.stdout) + self.assertEqual(proc.returncode, 0, proc.stderr + proc.stdout) + self.assertNotIn("Content-Length", proc.stdout) + self.assertIn("NDJSON", proc.stdout) - def test_ndjson_client_handshake_lists_search_docs(self): - """Cursor Cloud writes NDJSON requests, not Content-Length.""" + def test_serve_handshake_still_works(self): + """Leftover --serve implementation; not wired by default mcp.json.""" reqs = [ {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-11-25", "capabilities": {}}}, @@ -223,39 +159,92 @@ def test_ndjson_client_handshake_lists_search_docs(self): lines = [json.loads(ln) for ln in proc.stdout.decode().splitlines() if ln.strip()] self.assertEqual(lines[0]["result"]["protocolVersion"], "2025-11-25") self.assertNotIn("resources", lines[0]["result"]["capabilities"]) - self.assertEqual(lines[1]["result"]["tools"][0]["name"], "search_docs") self.assertEqual(lines[2]["result"]["resourceTemplates"], []) - def test_launcher_finds_kit_from_workspace_root(self): - """Cloud stdio cwd is often /agent, not the git repo.""" - with tempfile.TemporaryDirectory() as td: - ws = Path(td) - kit = ws / "repos" / "diffuser_agent" - (kit / "tools").mkdir(parents=True) - (kit / ".cursor").mkdir(parents=True) - shutil.copy2(ROOT / "tools" / "docs_mcp_server.py", kit / "tools" / "docs_mcp_server.py") - shutil.copy2(ROOT / "tools" / "library_paths.py", kit / "tools" / "library_paths.py") - shutil.copy2(ROOT / ".cursor" / "mcp-diffusers-docs.py", kit / ".cursor" / "mcp-diffusers-docs.py") - (kit / "knowledge" / "diffusers-docs").mkdir(parents=True) - launcher = ws / ".cursor" / "mcp-diffusers-docs.py" - launcher.parent.mkdir(parents=True) - shutil.copy2(ROOT / ".cursor" / "mcp-diffusers-docs.py", launcher) - reqs = [ - {"jsonrpc": "2.0", "id": 1, "method": "initialize", - "params": {"protocolVersion": "2025-03-26", "capabilities": {}}}, - {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}, - ] - payload = "".join(json.dumps(r, separators=(",", ":")) + "\n" for r in reqs) - proc = subprocess.run( - [sys.executable, "-u", str(launcher)], - cwd=str(ws), - input=payload.encode("utf-8"), - capture_output=True, - timeout=10, - check=False, - ) - self.assertEqual(proc.returncode, 0, proc.stderr.decode("utf-8", "replace")) - self.assertIn(b"search_docs", proc.stdout) + +class TestEmptyDefaultMcp(unittest.TestCase): + _STALE = ( + "mcp.optional.json", + "mcp-diffusers-docs.py", + "mcp-diffusers-docs.sh", + "mcp_stdio_boot.py", + "install-docs-mcp.sh", + ) + _LIVE_NEEDLES = ( + "diffusers-docs-mcp", + "mcp-diffusers-docs", + "mcp.optional", + "search_docs MCP", + "`search_docs` MCP", + "MCP tool `search_docs`", + "MCP tool search_docs", + ) + + def test_kit_and_overlay_mcp_json_are_empty(self): + for path in ( + ROOT / ".cursor" / "mcp.json", + ROOT / "overlay" / "mcp.json", + ): + raw = path.read_text() + self.assertNotIn("${workspaceFolder}", raw) + cfg = json.loads(raw) + self.assertEqual(cfg.get("mcpServers"), {}, path) + + def test_no_docs_mcp_launchers_or_optional(self): + for name in self._STALE: + self.assertFalse((ROOT / ".cursor" / name).exists(), name) + self.assertFalse((ROOT / "overlay" / name).exists(), name) + + def test_environment_has_no_mcp_allowlist_or_start(self): + for path in ( + ROOT / ".cursor" / "environment.json", + ROOT / "overlay" / "environment.json", + ): + env = json.loads(path.read_text()) + self.assertNotIn("mcpServerAllowlist", env, path) + self.assertNotIn("start", env, path) + + def test_docs_and_specs_do_not_advertise_live_search_docs_mcp(self): + roots = [ + ROOT / "docs", + ROOT / "agents", + ROOT / "overlay", + ROOT / ".cursor" / "commands", + ROOT / ".cursor" / "skills", + ROOT / ".cursor" / "rules", + ROOT / ".cursor" / "agents", + ROOT / "projections", + ROOT / ".github", + ] + files = [ + ROOT / "README.md", + ROOT / "AGENTS.md", + ] + for root in roots: + if root.is_dir(): + files.extend( + p for p in root.rglob("*") + if p.is_file() and p.suffix in {".md", ".mdc", ".yml", ".yaml", ".json", ".html"} + ) + hits = [] + for path in files: + text = path.read_text(encoding="utf-8", errors="replace") + for needle in self._LIVE_NEEDLES: + if needle in text: + hits.append(f"{path.relative_to(ROOT)}: {needle}") + self.assertEqual(hits, []) + + +class TestOverlayMcpDefaults(unittest.TestCase): + def test_attach_copies_empty_mcp_json(self): + src = (ROOT / "tools" / "attach_library.py").read_text() + self.assertIn('shutil.copy2(overlay / "mcp.json", cursor / "mcp.json")', src) + self.assertNotIn( + 'shutil.copy2(overlay / "mcp.optional.json", cursor / "mcp.optional.json")', + src, + ) + self.assertIn("mcp-diffusers-docs.py", src) # unlink list + class TestAfterFileEditHook(unittest.TestCase): @@ -666,11 +655,22 @@ def test_rebuild_is_idempotent(self): class TestAttachEmptyMcp(unittest.TestCase): - def test_attach_writes_stdio_docs_mcp(self): + def test_attach_writes_empty_mcp_and_unlinks_stale(self): with tempfile.TemporaryDirectory() as td: fake = Path(td) / "diffusers" (fake / "src" / "diffusers" / "schedulers").mkdir(parents=True) (fake / ".gitignore").write_text("# Cursor\n.cursor\n") + cursor = fake / ".cursor" + cursor.mkdir() + stale = ( + "mcp.optional.json", + "mcp-diffusers-docs.py", + "mcp-diffusers-docs.sh", + "mcp_stdio_boot.py", + "install-docs-mcp.sh", + ) + for name in stale: + (cursor / name).write_text("stale\n") proc = subprocess.run( [sys.executable, str(ROOT / "tools" / "attach_library.py"), "--target", str(fake)], @@ -681,13 +681,12 @@ def test_attach_writes_stdio_docs_mcp(self): ) self.assertEqual(proc.returncode, 0, proc.stderr + proc.stdout) cfg = json.loads((fake / ".cursor" / "mcp.json").read_text()) - self.assertIn("diffusers-docs", cfg.get("mcpServers", {})) - self.assertNotIn("huggingface", cfg.get("mcpServers", {})) - self.assertEqual( - cfg["mcpServers"]["diffusers-docs"]["args"], - ["-u", ".cursor/mcp-diffusers-docs.py"], - ) - self.assertTrue((fake / ".cursor" / "mcp.optional.json").is_file()) + self.assertEqual(cfg.get("mcpServers"), {}) + env = json.loads((fake / ".cursor" / "environment.json").read_text()) + self.assertNotIn("mcpServerAllowlist", env) + self.assertNotIn("start", env) + for name in stale: + self.assertFalse((fake / ".cursor" / name).exists(), name) self.assertNotIn("AGENTS.md", [p.name for p in fake.iterdir()]) wf = (fake / ".github" / "workflows" / "ramp-kit-overlay.yml").read_text() self.assertIn("ramp-kit-overlay", wf) @@ -700,7 +699,9 @@ def test_attach_writes_stdio_docs_mcp(self): self.assertIn("make quality", scaffold) self.assertIn("check_dummies.py", scaffold) self.assertIn("AGENTS.md", scaffold) - self.assertNotIn("docs_mcp_server.py", scaffold) + self.assertIn(".ai/", scaffold) + self.assertIn("docs_mcp_server.py --query", scaffold) + self.assertNotIn("search_docs", scaffold) self.assertNotIn("Those files are the contract", scaffold) self.assertNotIn("those two files only", scaffold) @@ -751,6 +752,7 @@ def test_prompt_a_stays_inside_registry_checked_files(self): self.assertNotIn("docs_mcp_server.py", paste) self.assertNotIn("docs/source", paste) self.assertNotIn("search_docs", paste) + self.assertNotIn("diffusers-docs-mcp", paste) self.assertNotIn("grokbot_sim", paste) self.assertIn("Never convention_check.py --all", paste) @@ -761,8 +763,11 @@ def test_overlay_scaffold_copies_templates_not_library_source(self): self.assertIn("make style", text) self.assertIn("make quality", text) self.assertIn("AGENTS.md", text) + self.assertIn(".ai/", text) + self.assertIn("docs_mcp_server.py --query", text) self.assertNotIn("Those files are the contract", text) - self.assertNotIn("docs_mcp_server.py", text) + self.assertNotIn("search_docs", text) + self.assertNotIn("diffusers-docs-mcp", text) self.assertNotIn("scheduling_euler_discrete.py", text) self.assertNotIn("convention_check.py --all", text) self.assertNotIn("those two files only", text) diff --git a/tools/attach_library.py b/tools/attach_library.py index 8be2b88..4e77d89 100755 --- a/tools/attach_library.py +++ b/tools/attach_library.py @@ -2,8 +2,8 @@ """Copy the Ramp Kit overlay into a huggingface/diffusers checkout. Does NOT replace the library's AGENTS.md / .ai/ — those stay upstream. -Adds Cursor rules, the gate hook, MCP launcher, and Cloud environment install -that clones this kit as `ramp-kit/` at VM boot. +Adds Cursor rules, the gate hook, empty default MCP, and Cloud environment +install that clones this kit as `ramp-kit/` at VM boot. Usage: python3 tools/attach_library.py --target /path/to/diffusers @@ -18,6 +18,16 @@ KIT = Path(__file__).resolve().parent.parent +# Leftover docs-MCP launchers from older overlays. Unlink on attach so the +# fork default stays empty even when those files were copied previously. +_STALE_MCP = ( + "mcp.optional.json", + "mcp-diffusers-docs.py", + "mcp-diffusers-docs.sh", + "mcp_stdio_boot.py", + "install-docs-mcp.sh", +) + def _is_library(root: Path) -> bool: return (root / "src" / "diffusers" / "schedulers").is_dir() @@ -46,11 +56,13 @@ def copy_overlay(target: Path) -> None: shutil.copytree(src, dst) shutil.copy2(overlay / "mcp.json", cursor / "mcp.json") - shutil.copy2(overlay / "mcp.optional.json", cursor / "mcp.optional.json") - for name in ("hooks.json", "mcp-diffusers-docs.py", "mcp-diffusers-docs.sh"): - src = KIT / ".cursor" / name - if src.exists(): - shutil.copy2(src, cursor / name) + for name in _STALE_MCP: + path = cursor / name + if path.exists(): + path.unlink() + hooks = KIT / ".cursor" / "hooks.json" + if hooks.exists(): + shutil.copy2(hooks, cursor / "hooks.json") shutil.copy2(overlay / "environment.json", cursor / "environment.json") shutil.copy2(overlay / "cursorignore", target / ".cursorignore") @@ -88,7 +100,8 @@ def copy_overlay(target: Path) -> None: print(f"overlay attached at {target}") print(" Cloud Agent: launch ON this fork (main), install clones ramp-kit/") - print(" Default MCP: stdio diffusers-docs (no Hub HTTP). Cloud dropdown: diffusers-docs-mcp") + print(" Default MCP: empty ({mcpServers: {}}). Grounding is .ai/ + source") + print(" Optional docs CLI: python3 ramp-kit/tools/docs_mcp_server.py --query") print(" Fork PRs: draft, title [fork demo — not for upstream]") print(" Overlay CI: .github/workflows/ramp-kit-overlay.yml (file-scoped, never --all)") print(" Library CI: invoke make style / make quality / check_copies / check_dummies") diff --git a/tools/build_projections.py b/tools/build_projections.py index 89a50d6..55aa336 100644 --- a/tools/build_projections.py +++ b/tools/build_projections.py @@ -284,8 +284,9 @@ def build_cursor_core(): "The first contribution is what `conventions/rules.yaml` checks. Copy the", "templates (`/scaffold`) and stop at 0 blocking findings from", "`tools/convention_check.py` on the new files. Do not add files the", - "registry does not scan. Docs search (`search_docs` / `/search-docs`) is", - "optional and is not a deliverable.", + "registry does not scan. Ground in this registry and, on the library,", + "`.ai/` plus reference source. Optional:", + '`python3 tools/docs_mcp_server.py --query "..."` — not a deliverable.', "", "Do not pull in code or context from outside this repo's approved", "boundaries (see `.cursorignore`).", @@ -349,8 +350,9 @@ def build_agents_md(): "`conventions/rules.yaml` and are enforced by `tools/convention_check.py`.", "Before opening a PR, run `make check` and fix every blocking finding.", "The first contribution is the registry + templates, not a docs page or", - "a copied library scheduler. Optional docs search: `search_docs` MCP,", - "`/search-docs`, or `python3 tools/docs_mcp_server.py --query \"...\"`.", + "a copied library scheduler. Grounding is the registry and (on the", + "library) `.ai/` plus reference source. Optional docs CLI:", + '`python3 tools/docs_mcp_server.py --query "..."`.', "", "## Conventions", ] @@ -476,8 +478,8 @@ def _ci_gate_yml(): " run: python tools/build_projections.py && git diff --exit-code", " - name: Scheduler contract re-verify", " run: python tools/verify_scheduler_contract.py", - " - name: MCP doc-server self-test", - " run: python tools/docs_mcp_server.py --selftest", + " - name: Docs CLI --query fallback", + ' run: python tools/docs_mcp_server.py --query "set_timesteps"', " - name: Contract tests", " run: python -m unittest discover -s tests -t .", " - name: Machine-readable report", diff --git a/tools/demo_contribute.py b/tools/demo_contribute.py index bd9ff40..aad1cc1 100644 --- a/tools/demo_contribute.py +++ b/tools/demo_contribute.py @@ -2,7 +2,7 @@ """Narrated first-contribution demo — the same steps `/scaffold` tells an engineer. This is the CLI twin of the live Cursor journey. Use it to rehearse, as a -fallback if slash-commands/MCP stall, or in CI. It does not invent math. +fallback if slash-commands stall, or in CI. It does not invent math. python3 tools/demo_contribute.py # create, prove, remove python3 tools/demo_contribute.py --keep # leave files for the walkthrough @@ -56,7 +56,7 @@ def ground() -> None: from docs_mcp_server import _format_hits, search_docs # noqa: WPS433 query = "scheduler set_timesteps step SchedulerMixin register_to_config" - print(f"Grounding (same corpus the MCP search_docs tool uses):\n query: {query}\n") + print(f"Grounding (optional docs CLI --query):\n query: {query}\n") print(_format_hits(search_docs(query, k=2))) print( "\nSay: grounding is advisory. The registry + gate are authoritative " diff --git a/tools/docs_mcp_server.py b/tools/docs_mcp_server.py index 871bcfe..ea650d3 100644 --- a/tools/docs_mcp_server.py +++ b/tools/docs_mcp_server.py @@ -1,30 +1,23 @@ #!/usr/bin/env python3 """ -docs_mcp_server.py — a real MCP server that gives the agent GROUNDED search over -the library's own docs, so "how does diffusers do X" is answered from THIS repo -at THIS version, not from stale training memory. +docs_mcp_server.py — optional keyword search over the library's own docs. WHY THIS IS A PRIMITIVE (not per-task tooling): discovery is component-agnostic. -The same server serves scheduler, model, and pipeline questions — you scale it +The same CLI serves scheduler, model, and pipeline questions — you scale it by pointing it at more docs, never by adding new machinery. It complements the convention gate: the gate stops WRONG code; doc-search improves DISCOVERY. -TRANSPORT: MCP stdio — JSON-RPC 2.0 as newline-delimited JSON (what Cursor -expects on stdio). The reader still accepts Content-Length (LSP-style) as -well as NDJSON, so older clients keep working. Cursor Desktop launches it -from .cursor/mcp.json via .cursor/mcp-diffusers-docs.py (workspace-relative -arg; no ${workspaceFolder} — Cloud stdio does not expand it and cannot set -cwd). Implemented with the standard library only, so it runs on a fresh -clone / GPU-less CI with zero installs (a hard PyPI-free constraint we -actually hit while building this). +Default Cursor overlay does **not** wire this as an MCP server +(`.cursor/mcp.json` is `{"mcpServers":{}}`). The supported path is `--query`. +`--serve` / `--selftest` remain as leftover implementation. -Methods implemented: initialize, notifications/initialized, ping, tools/list, -tools/call (tool: search_docs). +Implemented with the standard library only, so it runs on a fresh clone / +GPU-less CI with zero installs. Run modes: - python3 tools/docs_mcp_server.py --serve # MCP stdio (what Cursor uses) - python3 tools/docs_mcp_server.py --query "..." # CLI, for offline testing - python3 tools/docs_mcp_server.py --selftest # simulate the MCP handshake + python3 tools/docs_mcp_server.py --query "..." # CLI fallback (supported) + python3 tools/docs_mcp_server.py --serve # leftover MCP stdio + python3 tools/docs_mcp_server.py --selftest # leftover handshake check """ from __future__ import annotations