comfy-draftsman 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- comfy_draftsman-0.15.0/.github/workflows/ci.yml +59 -0
- comfy_draftsman-0.15.0/.github/workflows/release.yml +140 -0
- comfy_draftsman-0.15.0/.gitignore +38 -0
- comfy_draftsman-0.15.0/AGENTS.md +99 -0
- comfy_draftsman-0.15.0/CHANGELOG.md +927 -0
- comfy_draftsman-0.15.0/CLAUDE.md +1 -0
- comfy_draftsman-0.15.0/LICENSE +21 -0
- comfy_draftsman-0.15.0/PKG-INFO +311 -0
- comfy_draftsman-0.15.0/README.md +282 -0
- comfy_draftsman-0.15.0/docs/ARCHITECTURE.md +958 -0
- comfy_draftsman-0.15.0/docs/PERMISSIONS.md +89 -0
- comfy_draftsman-0.15.0/docs/images/sample-render.jpg +0 -0
- comfy_draftsman-0.15.0/docs/images/showcase-closeup.png +0 -0
- comfy_draftsman-0.15.0/docs/images/showcase-overview.png +0 -0
- comfy_draftsman-0.15.0/pyproject.toml +84 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/__init__.py +3 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/__init__.py +0 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/catalog.py +254 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/client.py +413 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/progress.py +131 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/registry.py +104 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/config.py +53 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/__init__.py +0 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/annotate.py +769 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/knobs.py +172 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/layout.py +370 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/lint.py +280 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/model.py +1274 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/port.py +254 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/spend.py +54 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/subgraph.py +261 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/validate.py +757 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/graph/widgets.py +651 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/imaging.py +57 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/__init__.py +428 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/chroma.yaml +40 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/flux.yaml +124 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/krea2.yaml +58 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/ltx.yaml +27 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/qwen_image.yaml +37 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/sd15.yaml +58 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/sd35.yaml +33 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/sdxl.yaml +124 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/wan.yaml +47 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/techniques.yaml +34 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/server.py +2862 -0
- comfy_draftsman-0.15.0/src/comfy_draftsman/session.py +105 -0
- comfy_draftsman-0.15.0/tests/conftest.py +28 -0
- comfy_draftsman-0.15.0/tests/fixtures/object_info_trimmed.json +4645 -0
- comfy_draftsman-0.15.0/tests/fixtures/sdxl_simple_example.json +1346 -0
- comfy_draftsman-0.15.0/tests/fixtures/subgraph_real_template.json +1071 -0
- comfy_draftsman-0.15.0/tests/test_annotate.py +271 -0
- comfy_draftsman-0.15.0/tests/test_catalog.py +117 -0
- comfy_draftsman-0.15.0/tests/test_client.py +165 -0
- comfy_draftsman-0.15.0/tests/test_dynamic_combo.py +207 -0
- comfy_draftsman-0.15.0/tests/test_hardware_fit.py +211 -0
- comfy_draftsman-0.15.0/tests/test_integration_live.py +264 -0
- comfy_draftsman-0.15.0/tests/test_knowledge.py +145 -0
- comfy_draftsman-0.15.0/tests/test_layout.py +224 -0
- comfy_draftsman-0.15.0/tests/test_lint.py +82 -0
- comfy_draftsman-0.15.0/tests/test_mcp_e2e.py +179 -0
- comfy_draftsman-0.15.0/tests/test_mcp_offline.py +550 -0
- comfy_draftsman-0.15.0/tests/test_model.py +323 -0
- comfy_draftsman-0.15.0/tests/test_output_relocation.py +269 -0
- comfy_draftsman-0.15.0/tests/test_packaging.py +103 -0
- comfy_draftsman-0.15.0/tests/test_port.py +115 -0
- comfy_draftsman-0.15.0/tests/test_registry.py +76 -0
- comfy_draftsman-0.15.0/tests/test_round10_subgraphs.py +1266 -0
- comfy_draftsman-0.15.0/tests/test_round12_api_fixes.py +310 -0
- comfy_draftsman-0.15.0/tests/test_round13_fixes.py +192 -0
- comfy_draftsman-0.15.0/tests/test_round14_layout_queue.py +318 -0
- comfy_draftsman-0.15.0/tests/test_round15_cowork.py +223 -0
- comfy_draftsman-0.15.0/tests/test_round16_find_workflow.py +245 -0
- comfy_draftsman-0.15.0/tests/test_round17_audit.py +355 -0
- comfy_draftsman-0.15.0/tests/test_round18_tokens.py +362 -0
- comfy_draftsman-0.15.0/tests/test_round19_primitives_and_types.py +655 -0
- comfy_draftsman-0.15.0/tests/test_round1_fixes.py +203 -0
- comfy_draftsman-0.15.0/tests/test_round20_v3_types_and_subgraph_edits.py +440 -0
- comfy_draftsman-0.15.0/tests/test_round21_widget_flags_and_autogrow.py +380 -0
- comfy_draftsman-0.15.0/tests/test_round22_optional_source_and_lookalikes.py +59 -0
- comfy_draftsman-0.15.0/tests/test_round23_family_and_layout.py +183 -0
- comfy_draftsman-0.15.0/tests/test_round23_force_socket.py +179 -0
- comfy_draftsman-0.15.0/tests/test_round23_knob_cards.py +149 -0
- comfy_draftsman-0.15.0/tests/test_round23_layout_ops.py +221 -0
- comfy_draftsman-0.15.0/tests/test_round23_note_honesty.py +137 -0
- comfy_draftsman-0.15.0/tests/test_round23_reader_priority.py +232 -0
- comfy_draftsman-0.15.0/tests/test_round23_resolution_alignment.py +158 -0
- comfy_draftsman-0.15.0/tests/test_round23_sources.py +128 -0
- comfy_draftsman-0.15.0/tests/test_round45_fixes.py +262 -0
- comfy_draftsman-0.15.0/tests/test_round6_feedback.py +201 -0
- comfy_draftsman-0.15.0/tests/test_round7_fixes.py +109 -0
- comfy_draftsman-0.15.0/tests/test_round8_execution.py +588 -0
- comfy_draftsman-0.15.0/tests/test_round9_feedback.py +258 -0
- comfy_draftsman-0.15.0/tests/test_server_save.py +186 -0
- comfy_draftsman-0.15.0/tests/test_session.py +43 -0
- comfy_draftsman-0.15.0/tests/test_spend_gate.py +453 -0
- comfy_draftsman-0.15.0/tests/test_validate.py +306 -0
- comfy_draftsman-0.15.0/uv.lock +1302 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
# 15-min ceiling: the full suite runs in <60s on every matrix cell
|
|
12
|
+
# we've measured. Capping the job also surfaces GitHub-hosted
|
|
13
|
+
# runner-acquisition failures ("not acquired even after multiple
|
|
14
|
+
# attempts") as a fast, clear failure rather than a 15-minute hang
|
|
15
|
+
# that burns CI minutes before the runner ever starts.
|
|
16
|
+
timeout-minutes: 15
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
os: [ubuntu-latest, windows-latest]
|
|
20
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: astral-sh/setup-uv@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
# --frozen: fail if the committed uv.lock is stale rather than silently
|
|
27
|
+
# resolving something else than what a user installing from it would get
|
|
28
|
+
- name: Install
|
|
29
|
+
run: uv sync --frozen --group dev
|
|
30
|
+
- name: Lint
|
|
31
|
+
run: uv run ruff check .
|
|
32
|
+
- name: Typecheck
|
|
33
|
+
run: uv run mypy src
|
|
34
|
+
- name: Test
|
|
35
|
+
run: uv run pytest
|
|
36
|
+
|
|
37
|
+
# The package ships non-Python data (knowledge/families/*.yaml). Nothing in the
|
|
38
|
+
# test suite proves those land in the wheel - a build that quietly dropped them
|
|
39
|
+
# would still pass every test, then fail on a user's first get_model_guidance.
|
|
40
|
+
package:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v4
|
|
44
|
+
- uses: astral-sh/setup-uv@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.11"
|
|
47
|
+
- name: Build wheel
|
|
48
|
+
run: uv build
|
|
49
|
+
- name: Knowledge data is importable from the built wheel
|
|
50
|
+
shell: bash
|
|
51
|
+
run: |
|
|
52
|
+
wheel=$(ls dist/*.whl)
|
|
53
|
+
uv run --isolated --no-project --with "$wheel" python -c "
|
|
54
|
+
import comfy_draftsman, comfy_draftsman.knowledge as k
|
|
55
|
+
families = k.list_families()
|
|
56
|
+
print(comfy_draftsman.__version__, families)
|
|
57
|
+
assert 'flux' in families and 'sdxl' in families, families
|
|
58
|
+
assert k.get_guidance('flux')['sampling'], 'guidance YAML did not ship'
|
|
59
|
+
"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via Trusted Publishing (OIDC) - no API token is ever stored
|
|
4
|
+
# in this repo. The `pypi` / `testpypi` GitHub environments and the matching
|
|
5
|
+
# pending publishers on (test.)pypi.org are configured once, by hand; see
|
|
6
|
+
# README.md -> "Publishing a release".
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
inputs:
|
|
12
|
+
target:
|
|
13
|
+
description: "Where to publish"
|
|
14
|
+
required: true
|
|
15
|
+
default: testpypi
|
|
16
|
+
type: choice
|
|
17
|
+
options: [testpypi, pypi]
|
|
18
|
+
|
|
19
|
+
# Least privilege by default: only the two jobs that genuinely need a token
|
|
20
|
+
# widen this, and each widens it to exactly one scope. Without a top-level
|
|
21
|
+
# block, jobs inherit whatever the repository's default is - which on an older
|
|
22
|
+
# repo is still read/write on every scope, handed to third-party actions.
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
build:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
timeout-minutes: 15
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
- uses: astral-sh/setup-uv@v5
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.11"
|
|
35
|
+
|
|
36
|
+
# A tag that disagrees with comfy_draftsman.__version__ publishes a
|
|
37
|
+
# surprise version - and PyPI never lets a version number be reused, so
|
|
38
|
+
# the mistake is permanent. Fail before the build instead.
|
|
39
|
+
- name: Tag matches __version__
|
|
40
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
41
|
+
shell: bash
|
|
42
|
+
run: |
|
|
43
|
+
version=$(grep -oP '(?<=^__version__ = ")[^"]+' src/comfy_draftsman/__init__.py)
|
|
44
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
45
|
+
echo "tag=$tag package=$version"
|
|
46
|
+
test "$tag" = "$version" || {
|
|
47
|
+
echo "::error::tag v$tag does not match __version__ $version"
|
|
48
|
+
exit 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
- name: Build
|
|
52
|
+
run: uv build
|
|
53
|
+
|
|
54
|
+
# Same assertion as CI's `package` job: the wheel ships non-Python data
|
|
55
|
+
# (knowledge/families/*.yaml) that no unit test can prove landed in it.
|
|
56
|
+
- name: Knowledge data is importable from the built wheel
|
|
57
|
+
shell: bash
|
|
58
|
+
run: |
|
|
59
|
+
wheel=$(ls dist/*.whl)
|
|
60
|
+
uv run --isolated --no-project --with "$wheel" python -c "
|
|
61
|
+
import comfy_draftsman, comfy_draftsman.knowledge as k
|
|
62
|
+
families = k.list_families()
|
|
63
|
+
print(comfy_draftsman.__version__, families)
|
|
64
|
+
assert 'flux' in families and 'sdxl' in families, families
|
|
65
|
+
assert k.get_guidance('flux')['sampling'], 'guidance YAML did not ship'
|
|
66
|
+
"
|
|
67
|
+
|
|
68
|
+
- uses: actions/upload-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: dist
|
|
71
|
+
path: dist/
|
|
72
|
+
|
|
73
|
+
publish:
|
|
74
|
+
needs: build
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
timeout-minutes: 15
|
|
77
|
+
# A tag push always goes to the real index; workflow_dispatch picks.
|
|
78
|
+
environment: ${{ github.event_name == 'workflow_dispatch' && inputs.target || 'pypi' }}
|
|
79
|
+
permissions:
|
|
80
|
+
id-token: write # Trusted Publishing supplies the credential via OIDC
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/download-artifact@v4
|
|
83
|
+
with:
|
|
84
|
+
name: dist
|
|
85
|
+
path: dist/
|
|
86
|
+
# Two steps rather than a computed `repository-url`: the action defaults
|
|
87
|
+
# to the real index, and an empty string is not the same as "unset".
|
|
88
|
+
- name: Publish to TestPyPI
|
|
89
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
90
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
91
|
+
with:
|
|
92
|
+
# no API token is passed at all - the credential comes from OIDC
|
|
93
|
+
repository-url: https://test.pypi.org/legacy/
|
|
94
|
+
- name: Publish to PyPI
|
|
95
|
+
if: github.event_name != 'workflow_dispatch' || inputs.target == 'pypi'
|
|
96
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
97
|
+
|
|
98
|
+
# A tag is not a Release: GitHub creates one only when asked. Without this job
|
|
99
|
+
# "Watch -> Releases" notifies nobody, which is the whole mechanism users have
|
|
100
|
+
# for learning that a new version exists.
|
|
101
|
+
github-release:
|
|
102
|
+
needs: publish
|
|
103
|
+
# BOTH conditions: "Use workflow from" lets a workflow_dispatch run start
|
|
104
|
+
# from a tag ref, so a TestPyPI dry run would otherwise cut a real GitHub
|
|
105
|
+
# Release - notifying every watcher about a version that only reached
|
|
106
|
+
# TestPyPI, and leaving the later real tag push to fail on "release already
|
|
107
|
+
# exists" after PyPI publishing had irreversibly succeeded.
|
|
108
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
timeout-minutes: 15
|
|
111
|
+
permissions:
|
|
112
|
+
contents: write
|
|
113
|
+
steps:
|
|
114
|
+
- uses: actions/checkout@v4
|
|
115
|
+
- uses: actions/download-artifact@v4
|
|
116
|
+
with:
|
|
117
|
+
name: dist
|
|
118
|
+
path: dist/
|
|
119
|
+
- name: Release notes from CHANGELOG
|
|
120
|
+
shell: bash
|
|
121
|
+
run: |
|
|
122
|
+
version="${GITHUB_REF_NAME#v}"
|
|
123
|
+
# the '## <version> - title' block, up to the next '## ' heading
|
|
124
|
+
awk -v v="$version" '
|
|
125
|
+
$0 ~ "^## " v "([^0-9.]|$)" { inside = 1; next }
|
|
126
|
+
inside && /^## / { exit }
|
|
127
|
+
inside { print }
|
|
128
|
+
' CHANGELOG.md > notes.md
|
|
129
|
+
if [ ! -s notes.md ]; then
|
|
130
|
+
echo "No CHANGELOG entry for $version." > notes.md
|
|
131
|
+
fi
|
|
132
|
+
cat notes.md
|
|
133
|
+
- name: Create the release
|
|
134
|
+
env:
|
|
135
|
+
GH_TOKEN: ${{ github.token }}
|
|
136
|
+
run: |
|
|
137
|
+
gh release create "$GITHUB_REF_NAME" dist/* \
|
|
138
|
+
--title "$GITHUB_REF_NAME" \
|
|
139
|
+
--notes-file notes.md \
|
|
140
|
+
--verify-tag
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Test ComfyUI environment (cloned/installed locally, never committed)
|
|
2
|
+
.comfyui-test/
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# Tooling caches
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
|
|
20
|
+
# Session workspaces created at runtime
|
|
21
|
+
.draftsman-sessions/
|
|
22
|
+
|
|
23
|
+
# Local agent worklogs / evidence (never committed or published)
|
|
24
|
+
.omo/
|
|
25
|
+
.claude/
|
|
26
|
+
|
|
27
|
+
# OS / editor
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
32
|
+
|
|
33
|
+
# Internal work plans / agent worklogs (keep local; public TODOs live in docs/ARCHITECTURE.md)
|
|
34
|
+
docs/superpowers/
|
|
35
|
+
docs/plans/
|
|
36
|
+
|
|
37
|
+
# Secrets (API keys, credentials)
|
|
38
|
+
.env*
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# AGENTS.md — comfy-draftsman
|
|
2
|
+
|
|
3
|
+
A local-first MCP server that drafts, organizes, validates, and runs ComfyUI workflows. Delivers clean, labeled, annotated UI-format graphs that humans can read — computed layout, colored stage groups, titled nodes, green-highlighted knobs, and generated guidance notes. Python ≥3.11, hatchling build, httpx + websockets + mcp stack.
|
|
4
|
+
|
|
5
|
+
**Deep reference: `docs/ARCHITECTURE.md` (read it before engine/graph/validation changes)**
|
|
6
|
+
|
|
7
|
+
## Current state
|
|
8
|
+
|
|
9
|
+
_Last verified: 2026-08-22_
|
|
10
|
+
|
|
11
|
+
- **Status:** in development, at v0.15.0 (`src/comfy_draftsman/__init__.py` is the single source of truth for the version). CI green. **PyPI release pipeline is wired** (`.github/workflows/release.yml`, Trusted Publishing) but the first tag has not been pushed — see README → "Publishing a release" for the one-time manual setup.
|
|
12
|
+
- **Works:** the full draft → organize → validate → run → save loop against a live ComfyUI instance; schema 0.4 and 1.0 graphs including subgraph flatten/rebuild; V3 dynamic combos, autogrow inputs and match types round-tripping through the API's dotted-key form; `organize_workflow`'s labeled stage groups, knob cards and guidance notes; workflow import straight from ComfyUI's browser (`list_workflows` + `import_workflow(name=…)`); the per-family knowledge floor plus a persistent learned overlay written by `record_learning`.
|
|
13
|
+
- **In progress:** nothing half-built — each round lands complete. The most recent round (0.15.0) added a VRAM fit verdict, a partner-node spend gate, and the PyPI pipeline; `CHANGELOG.md` records what each round changed and, importantly, what was deliberately *not* changed.
|
|
14
|
+
- **Known gaps / next steps:** the first PyPI tag is unpushed (one-time PyPI/TestPyPI account setup pending — README → “Publishing a release”); `api_node` detection and a real elicitation round-trip are unverified against a live instance; six families carry no VRAM data and correctly stay silent; `COMFY_DYNAMICSLOT_V3` is classified but never exercised (no live instance declares one — do not implement it speculatively); widget-backed custom-JS inputs stay a deliberate loud stop rather than a silently-wrong emit. All written up in full under `docs/ARCHITECTURE.md` → "Remaining TODOs".
|
|
15
|
+
- **Deep docs:** `docs/ARCHITECTURE.md` (module map, data flow, subgraph mechanics, hard-won gotchas, open TODOs), `docs/PERMISSIONS.md` (which tools are read-only), `CHANGELOG.md`.
|
|
16
|
+
|
|
17
|
+
## Architecture in 60 seconds
|
|
18
|
+
|
|
19
|
+
- **Thin MCP wiring layer.** `server.py` exposes tools/prompts; all logic lives in tested modules underneath. Ground truth is the live ComfyUI instance's `/object_info`.
|
|
20
|
+
- **Graph model with schema 0.4/1.0 support.** `graph/` handles workflow ↔ internal model conversion, widget mapping, subgraph flattening, live-instance validation, and layout.
|
|
21
|
+
- **ComfyUI client layer.** `comfy/` provides the httpx REST client, object_info catalog, websocket progress tracker, and Comfy Registry lookups for missing node packs.
|
|
22
|
+
- **Two-layer knowledge system.** Per-family tuning floor (YAML) + persistent learned overlay. `record_learning` saves researched settings so future sessions start smarter. The floor also carries optional, sourced VRAM requirements behind `knowledge.fit_verdict`.
|
|
23
|
+
- **Gates that teach reactively.** The VRAM fit verdict and the partner-node spend gate both fire before anything irreversible, so each explains itself in its own response instead of in a docstring everyone pays for.
|
|
24
|
+
- **V3 dynamic combos first-class.** `COMFY_DYNAMICCOMBO_V3`, `COMFY_AUTOGROW_V3`, match types, and socketless widgets are handled natively — values round-trip through the API's dotted-key form.
|
|
25
|
+
- **Token discipline.** Every tool returns bounded lists; findings are severity-capped; summaries clip long strings. Full object_info is never returned to the model. The tool surface — the one budget paid on *every* request — is stripped of pydantic's auto-generated schema titles *and* of docstring indentation at import (`_trim_published_surface`), and held under a ceiling by `tests/test_round18_tokens.py`. Keep that ceiling interpreter-independent: only Python 3.13+ strips docstring indentation at compile time.
|
|
26
|
+
|
|
27
|
+
## Layout
|
|
28
|
+
|
|
29
|
+
| Directory | Purpose |
|
|
30
|
+
|-----------|---------|
|
|
31
|
+
| `src/comfy_draftsman/server.py` | MCP tools/prompts — thin wiring only, no logic |
|
|
32
|
+
| `src/comfy_draftsman/graph/` | Workflow model, widgets, subgraph flattening, validate, lint, annotate, layout, knobs, port, spend |
|
|
33
|
+
| `src/comfy_draftsman/comfy/` | httpx client, object_info catalog, websocket progress, Comfy Registry |
|
|
34
|
+
| `src/comfy_draftsman/knowledge/` | Per-family tuning floor (YAML) + learned overlay |
|
|
35
|
+
| `src/comfy_draftsman/config.py` | Env-driven config (COMFYUI_URL, DRAFTSMAN_SESSION_DIR, ...) |
|
|
36
|
+
| `src/comfy_draftsman/session.py` | workflow_id → Workflow store, persisted under session dir |
|
|
37
|
+
| `tests/` | pytest + pytest-asyncio; integration tests require running ComfyUI |
|
|
38
|
+
| `docs/` | Architecture (deep reference), images, showcase |
|
|
39
|
+
|
|
40
|
+
## Build / test / run
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Install (editable)
|
|
44
|
+
uv sync
|
|
45
|
+
|
|
46
|
+
# Run the MCP server
|
|
47
|
+
comfy-draftsman
|
|
48
|
+
# or: uv run comfy-draftsman
|
|
49
|
+
|
|
50
|
+
# Test (excludes integration tests by default)
|
|
51
|
+
uv run pytest
|
|
52
|
+
|
|
53
|
+
# Test with integration tests (requires COMFYUI_TEST_URL)
|
|
54
|
+
uv run pytest -m integration
|
|
55
|
+
|
|
56
|
+
# Lint
|
|
57
|
+
uv run ruff check src tests
|
|
58
|
+
|
|
59
|
+
# Typecheck
|
|
60
|
+
uv run mypy
|
|
61
|
+
|
|
62
|
+
# Wheel data check (CI's `package` job) - --refresh, or uv serves a cached
|
|
63
|
+
# extract of the previous build with the same version number
|
|
64
|
+
uv build && uv run --isolated --no-project --refresh --with "$(ls dist/*.whl)" python -c "
|
|
65
|
+
import comfy_draftsman.knowledge as k; assert k.get_guidance('flux')['sampling']"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Conventions & gotchas
|
|
69
|
+
|
|
70
|
+
- Dynamic nodes serialize only in-use widgets — never pad widgets_values with `None`.
|
|
71
|
+
- Frontend runs `.replace()` over every string widget at queue time — `null` crashes the editor.
|
|
72
|
+
- Seed control widgets are a name heuristic (INT literally named `seed`/`noise_seed`).
|
|
73
|
+
- Never default paths off `Path.cwd()` — MCP hosts launch from arbitrary directories. Session state lives under `~/.comfy-draftsman`.
|
|
74
|
+
- `object_info` is multi-megabyte — never return it or full combo lists to the model. Everything recurring must be capped or digested.
|
|
75
|
+
- Validation gates: `run_workflow` and `save_workflow` refuse on `validate()` errors unless `allow_invalid=True`.
|
|
76
|
+
- `lint()` is advisory only — it never blocks.
|
|
77
|
+
- `edit_workflow` ops deliberately do NOT reach inside subgraph definitions — rebuild flat to modify internals.
|
|
78
|
+
- `organize_workflow` never synthesizes a download URL or an alignment (`multiple_of`) requirement — both only ever come from a curated family YAML or a `record_learning` call; a guessed one is worse than none.
|
|
79
|
+
- **Never invent VRAM numbers.** `hardware.vram_gb` requires a `hardware.source` that states the figure; `tests/test_hardware_fit.py` fails the build otherwise.
|
|
80
|
+
- **Unknown is not paid.** No VRAM data, or an instance too old to flag `api_node`, means *say nothing* / *don't bill* — never a default, never a nag. A warning repeated on every call is one an agent learns to skip.
|
|
81
|
+
- **New behavior is taught by responses, not by docstrings.** The tool surface is re-sent on every request (`test_round18_tokens.py` caps it); a gate that fires before the irreversible act can explain itself in its own return value, paid for only by the caller who hit it.
|
|
82
|
+
|
|
83
|
+
## Security
|
|
84
|
+
|
|
85
|
+
This file is **public-safe by default**. Never add local paths, credentials, API keys, personal data, infrastructure details, or subscription info.
|
|
86
|
+
|
|
87
|
+
Before pushing, re-read this file and `CLAUDE.md` against that rule. Maintainers run a denylist checker over both; it lives outside the repo (it is a machine-local tool, and a `pwsh` script would not run on the Linux CI runner anyway), so there is deliberately no in-repo command to invoke here.
|
|
88
|
+
|
|
89
|
+
Deep architecture, data flow, subgraph mechanics, and gotchas: `docs/ARCHITECTURE.md`.
|
|
90
|
+
|
|
91
|
+
## Maintenance
|
|
92
|
+
|
|
93
|
+
**Update rule:** When you change the architecture, build/test commands, or conventions, update this AGENTS.md in the same commit. Keep under 200 lines. Link to `docs/ARCHITECTURE.md` for detail.
|
|
94
|
+
|
|
95
|
+
**CLAUDE.md:** One-line shim: `@AGENTS.md`.
|
|
96
|
+
|
|
97
|
+
**New-repo rule:** Create AGENTS.md in the first session a new repo is worked on.
|
|
98
|
+
|
|
99
|
+
**No-overlap rule:** Explanatory prose lives in one file. AGENTS.md = agent-facing summary; `docs/ARCHITECTURE.md` = deep reference. Identical build/test/run commands may be restated verbatim. Explanatory prose must not be duplicated — link instead.
|