Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .cursor/commands/scaffold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# /scaffold — first contribution on the REAL library checkout

Usage: `/scaffold <component> <Name>`

This workspace is `huggingface/diffusers` (fork). Overlay kit is `ramp-kit/`
(cloned from alex-16moro/diffuser_agent). Do not overwrite `.ai/` or root `AGENTS.md`.

`$1` = component (`scheduler`). `$2` = PascalCase name without suffix (`EulerLite`
→ `EulerLiteScheduler`, `scheduling_euler_lite.py`).

## 0. Ground first

Prefer **this checkout's source** over memory and over MCP:

- `src/diffusers/schedulers/scheduling_euler_discrete.py`
- `src/diffusers/schedulers/scheduling_ddpm.py`

Those files are the contract. `ramp-kit/conventions/rules.yaml` / the gate is
authoritative if anything disagrees (the philosophy doc is stale).

Optional docs CLI (MCP is opt-in; `.cursor/mcp.json` is empty by default):

```bash
python3 ramp-kit/tools/docs_mcp_server.py --query "scheduler set_timesteps step SchedulerMixin register_to_config"
```

Cite provenance if you run that. Do not read or copy
`ramp-kit/examples/candidate_scheduler/` into the new files.

## 1. Paths (library layout, not the kit stand-in)

- Implementation: `src/diffusers/schedulers/scheduling_euler_lite.py`
- Test: `tests/schedulers/test_scheduling_euler_lite.py`

## 2. Copy from the overlay templates

- `ramp-kit/templates/scheduler/scheduling_TEMPLATE.py` → implementation
- Rename `TemplateScheduler` → `$2Scheduler`
- Leave `TODO(engineer)` in `step`. Do not invent Euler math.

## 3. TEST001

Copy `ramp-kit/tests/_templates/scheduler_test.py`. Set `TARGET` and `CLASS`.
The test must mention `set_timesteps` and `step`, and (TEST002) include
assertions, same-seed determinism, and shape/dtype checks.

## 4. Gate (file-scoped — do not `--all` this library)

```bash
python3 ramp-kit/tools/convention_check.py ramp-kit/examples/candidate_scheduler
python3 ramp-kit/tools/convention_check.py src/diffusers/schedulers/scheduling_euler_lite.py
python3 -m unittest tests.schedulers.test_scheduling_euler_lite -v
```

Catch-early uses the kit fixture path. Stop at 0 blocking. Do not fabricate numerics.
Do not open a PR against huggingface/diffusers — PR this fork.
24 changes: 24 additions & 0 deletions .cursor/commands/search-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# /search-docs — library docs in THIS checkout (optional)

Usage: `/search-docs <query>`

Prefer source + gate first (MCP is opt-in; `.cursor/mcp.json` is empty):

- `src/diffusers/schedulers/scheduling_euler_discrete.py`
- `src/diffusers/schedulers/scheduling_ddpm.py`
- `ramp-kit/conventions/rules.yaml`

Then, optional CLI (same server as MCP, no OAuth):

```bash
python3 ramp-kit/tools/docs_mcp_server.py --query $1
```

If `$1` is empty:

```bash
python3 ramp-kit/tools/docs_mcp_server.py --query "scheduler set_timesteps step SchedulerMixin register_to_config"
```

Provenance should say `diffusers checkout`, not `bundled snapshot`. If the
MCP tool `search_docs` is in your list, call that instead.
6 changes: 6 additions & 0 deletions .cursor/environment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"install": "if [ ! -d ramp-kit/.git ]; then git clone --depth 1 https://github.com/alex-16moro/diffuser_agent.git ramp-kit; fi && pip install -r ramp-kit/requirements.txt && python3 ramp-kit/tools/docs_mcp_server.py --selftest",
"repositoryDependencies": [
"github.com/alex-16moro/diffuser_agent"
]
}
11 changes: 11 additions & 0 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 1,
"hooks": {
"afterFileEdit": [
{
"command": ".cursor/hooks/convention-gate.sh",
"timeout": 20
}
]
}
}
7 changes: 7 additions & 0 deletions .cursor/hooks/convention-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Cursor afterFileEdit hook. Project hooks run from the repo root.
# Reads the afterFileEdit JSON payload on stdin and runs the same
# convention gate as CI (`tools/convention_check.py`).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
exec python3 "$ROOT/.cursor/hooks/convention_gate.py"
61 changes: 61 additions & 0 deletions .cursor/hooks/convention_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""afterFileEdit hook — run the same convention gate CI uses.

Cursor sends JSON on stdin:
{"file_path": "<absolute path>", "edits": [...]}

This is a notification (the write already happened). Findings are printed
to stderr so the agent can fix them immediately. We always exit 0 so a
red gate never crashes the editor loop; blocking findings still show up.
"""
from __future__ import annotations

import json
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
_kit_tools = None
for _cand in (ROOT / "ramp-kit" / "tools", ROOT / "tools"):
if (_cand / "convention_check.py").is_file():
_kit_tools = _cand
break
sys.path.insert(0, str(_kit_tools or (ROOT / "tools")))

from convention_check import check_file, load_rules, render_human # noqa: E402

# Don't fire the gate on the kit's own machinery or the teaching fixture
# (the fixture is scanned explicitly by `make demo`).
_SKIP_SUBSTR = (
"/tools/",
"/.cursor/",
"/_templates/",
"/templates/",
"/__pycache__/",
"/ramp-kit/examples/candidate_scheduler/",
)


def main() -> int:
raw = sys.stdin.read()
if not raw.strip():
return 0
try:
payload = json.loads(raw)
except json.JSONDecodeError:
return 0
file_path = payload.get("file_path") or ""
path = Path(file_path)
if path.suffix != ".py" or not path.is_file():
return 0
posix = path.as_posix()
if any(s in posix for s in _SKIP_SUBSTR):
return 0
findings = check_file(path, load_rules())
sys.stderr.write(render_human(findings, 1))
sys.stderr.flush()
return 0


if __name__ == "__main__":
raise SystemExit(main())
69 changes: 69 additions & 0 deletions .cursor/mcp-diffusers-docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Launch diffusers-docs MCP from any cwd.

Cloud Agent stdio cannot set `cwd` and does not expand `${workspaceFolder}`.
This launcher walks cwd, git root, /workspace, and its own path until it finds
`tools/docs_mcp_server.py`, then execs it. Desktop and Cloud then share one
command: `python3 -u .cursor/mcp-diffusers-docs.py`.
"""
from __future__ import annotations

import os
import subprocess
import sys
from pathlib import Path


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("/workspace"))
try:
top = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
stderr=subprocess.DEVNULL,
text=True,
).strip()
if top:
out.append(Path(top))
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]
# runpy would work; exec keeps stdin/stdout as the MCP pipes.
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()}). From the repo root run: "
"python3 -u .cursor/mcp-diffusers-docs.py\n"
)
return 1


if __name__ == "__main__":
raise SystemExit(main())
5 changes: 5 additions & 0 deletions .cursor/mcp-diffusers-docs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/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 "$@"
3 changes: 3 additions & 0 deletions .cursor/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mcpServers": {}
}
15 changes: 15 additions & 0 deletions .cursor/mcp.optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"mcpServers": {
"diffusers-docs": {
"type": "stdio",
"command": "python3",
"args": ["-u", "/workspace/.cursor/mcp-diffusers-docs.py"],
"env": {
"PYTHONUNBUFFERED": "1"
}
},
"huggingface": {
"url": "https://huggingface.co/mcp"
}
}
}
38 changes: 38 additions & 0 deletions .cursor/rules/00-conventions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
description: diffusers contribution conventions enforced by the Ramp Kit. Applied to all edits.
alwaysApply: true
---
<!-- GENERATED from conventions/rules.yaml by tools/build_projections.py. DO NOT EDIT. Run `make build`. -->

# diffusers conventions (registry v0.7.0)

You are contributing to `huggingface/diffusers`. Follow these conventions.
They mirror the project's own `.ai/` rules and are enforced by
`tools/convention_check.py` — the same gate that runs in CI, so producing
code that violates a **block** rule will fail the build.

Use the `diffusers-docs` MCP tool (`search_docs`) to ground answers in the
library's current docs before scaffolding. If that tool is **not** in your
tool list (Cloud Agents skip project `.cursor/mcp.json` unless the launch
MCP dropdown has stdio `python3 -u .cursor/mcp-diffusers-docs.py`), run
`/search-docs <query>`, the `search-docs` skill, or
`python3 tools/docs_mcp_server.py --query "..."` — same server.
Do not pull in code or context from outside this repo's approved
boundaries (see `.cursorignore`).

## Blocking conventions (must satisfy)
- **REPRO001 — Randomness threads through a generator, never global RNG.** Use `randn_tensor(shape, generator=generator, device=..., dtype=...)` and thread `generator` through the call chain.
- **DEVICE001 — No hardcoded CUDA/device placement.** Respect the module's existing device: `.to(sample.device)` or accept a `device` argument.
- **DEPR001 — No deprecated/moved import paths.** Use the current import path shown in the check output.
- **MUT001 — No mutable default arguments.** Default to None and initialise inside the body.

## Advisory conventions (should satisfy)
- **DEPR002 — Avoid deprecated kwargs (verify per release).** Prefer the newer kwarg, but confirm against your pinned diffusers version.
- **COPY001 — # Copied from markers are well-formed.** Format: `# Copied from diffusers.<module.path>.<Symbol> with A->B`.
- **LOG001 — Library code logs, it does not print().** Use `logger = logging.get_logger(__name__)` and `logger.info(...)`.
- **DOC001 — Public methods have docstrings.** Add a Google-style docstring with Args/Returns.
- **CUST001 — No debugging leftovers committed.** Remove breakpoint()/pdb before committing.

Component-specific rules auto-attach when you open a matching file
(e.g. a scheduler). Prefer copying an existing in-repo example with a
`# Copied from` marker over inventing a new pattern.
35 changes: 35 additions & 0 deletions .cursor/rules/10-scheduler.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
description: Extra diffusers conventions for schedulers.
globs: ["src/diffusers/schedulers/scheduling_*.py", "examples/**/scheduling_*.py", "tests/schedulers/test_scheduling_*.py"]
alwaysApply: false
---
<!-- GENERATED from conventions/rules.yaml by tools/build_projections.py. DO NOT EDIT. Run `make build`. -->

# Scheduler conventions (auto-attached to scheduler files)

### SCHED001 — Schedulers inherit SchedulerMixin and ConfigMixin (block, owner: architect)
Schedulers are swappable via ConfigMixin.from_config and serializable via SchedulerMixin. A scheduler that skips these can't be loaded, saved, or swapped like every other scheduler — it breaks the pipeline contract.
*How:* Declare `class XScheduler(SchedulerMixin, ConfigMixin):`.

### SCHED002 — Schedulers implement the step() / set_timesteps() contract (block, owner: architect)
Every denoising loop calls set_timesteps(...) once, then step(...) each iteration. VERIFIED against current source: DDPMScheduler and EulerDiscreteScheduler both define `set_timesteps(self, num_inference_steps, device=None, ...)` and `step(self, model_output, timestep, sample, generator=None, return_dict=True)`. The philosophy doc's older "set_num_inference_steps" name is stale; the code is canonical.
*How:* Implement `set_timesteps(self, num_inference_steps, device=None)` and `step(self, model_output, timestep, sample, generator=None, return_dict=True) -> SchedulerOutput`.

### SCHED003 — Scheduler __init__ is decorated with @register_to_config (block, owner: architect)
@register_to_config captures constructor args into the config so from_pretrained / save_pretrained round-trip correctly. Without it, config is empty and the scheduler cannot be reconstructed from the Hub.
*How:* Add `@register_to_config` directly above `def __init__`.

### IMPORT001 — Schedulers stay self-contained (no heavy util imports) (warn, owner: architect)
The single-file policy keeps schedulers readable and swappable. Pulling in pipeline internals or deep util chains couples them to the rest of the library and violates the philosophy reviewers enforce.
*How:* Copy the small helper in-file with a `# Copied from` marker instead of importing deep internals.

### TEST001 — New schedulers/models ship with a matching test file (block, owner: qa)
"No quality testing = no merge." A new scheduler with no test cannot move toward deployment. Presence is not enough: the test must actually exercise set_timesteps and step, otherwise a dummy file would satisfy the gate.
*How:* Generate a contract test from tests/_templates/scheduler_test.py that asserts set_timesteps and step.

### TEST002 — Scheduler tests assert behaviour, not just presence (block, owner: qa)
Presence of a test file is not enough. A test_* function with zero assertions is a dummy. New scheduler tests must include a same-seed determinism assertion and a shape/dtype assertion so CI actually pins the contract TEST001 only names.
*How:* Each test_* function must assert. Include same-seed determinism (torch.equal) and shape plus dtype checks (see tests/_templates/scheduler_test.py).

Reference implementation: `examples/scaffolded_scheduler/scheduling_ddpm_lite.py`.
Start from the scaffold: `/scaffold scheduler <Name>` — do not start from a blank file.
15 changes: 15 additions & 0 deletions .cursor/skills/search-docs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
name: search-docs
description: Optional docs CLI for this diffusers checkout. Prefer scheduler source + the gate; MCP is opt-in.
---

# Search this library's docs

```bash
python3 ramp-kit/tools/docs_mcp_server.py --query "<the question>"
```

Cite provenance (`diffusers checkout` vs bundled snapshot). Prefer reading
`src/diffusers/schedulers/scheduling_euler_discrete.py` and
`scheduling_ddpm.py` first. `ramp-kit/conventions/rules.yaml` is the gate.
`.cursor/mcp.json` is empty by default.
10 changes: 10 additions & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Approved context boundary for the overlay agent.

# Known-bad teaching fixture (in the cloned kit). Gate it; do not copy it.
ramp-kit/examples/candidate_scheduler/

.git/
**/__pycache__/
**/*.pyc
.venv/
ramp-kit/.git/
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ dmypy.json
.vs
.vscode

# Cursor
.cursor
# Cursor — upstream ignored this; overlay tracks .cursor (OVERLAY.md)

# Pycharm
.idea
Expand Down Expand Up @@ -185,4 +184,6 @@ wandb

# AI agent generated symlinks
/.agents/skills
/.claude/skills
/.claude/skills
# Ramp Kit overlay clone (see OVERLAY.md)
ramp-kit/
Loading
Loading