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.
Files changed (98) hide show
  1. comfy_draftsman-0.15.0/.github/workflows/ci.yml +59 -0
  2. comfy_draftsman-0.15.0/.github/workflows/release.yml +140 -0
  3. comfy_draftsman-0.15.0/.gitignore +38 -0
  4. comfy_draftsman-0.15.0/AGENTS.md +99 -0
  5. comfy_draftsman-0.15.0/CHANGELOG.md +927 -0
  6. comfy_draftsman-0.15.0/CLAUDE.md +1 -0
  7. comfy_draftsman-0.15.0/LICENSE +21 -0
  8. comfy_draftsman-0.15.0/PKG-INFO +311 -0
  9. comfy_draftsman-0.15.0/README.md +282 -0
  10. comfy_draftsman-0.15.0/docs/ARCHITECTURE.md +958 -0
  11. comfy_draftsman-0.15.0/docs/PERMISSIONS.md +89 -0
  12. comfy_draftsman-0.15.0/docs/images/sample-render.jpg +0 -0
  13. comfy_draftsman-0.15.0/docs/images/showcase-closeup.png +0 -0
  14. comfy_draftsman-0.15.0/docs/images/showcase-overview.png +0 -0
  15. comfy_draftsman-0.15.0/pyproject.toml +84 -0
  16. comfy_draftsman-0.15.0/src/comfy_draftsman/__init__.py +3 -0
  17. comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/__init__.py +0 -0
  18. comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/catalog.py +254 -0
  19. comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/client.py +413 -0
  20. comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/progress.py +131 -0
  21. comfy_draftsman-0.15.0/src/comfy_draftsman/comfy/registry.py +104 -0
  22. comfy_draftsman-0.15.0/src/comfy_draftsman/config.py +53 -0
  23. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/__init__.py +0 -0
  24. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/annotate.py +769 -0
  25. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/knobs.py +172 -0
  26. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/layout.py +370 -0
  27. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/lint.py +280 -0
  28. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/model.py +1274 -0
  29. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/port.py +254 -0
  30. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/spend.py +54 -0
  31. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/subgraph.py +261 -0
  32. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/validate.py +757 -0
  33. comfy_draftsman-0.15.0/src/comfy_draftsman/graph/widgets.py +651 -0
  34. comfy_draftsman-0.15.0/src/comfy_draftsman/imaging.py +57 -0
  35. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/__init__.py +428 -0
  36. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/chroma.yaml +40 -0
  37. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/flux.yaml +124 -0
  38. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/krea2.yaml +58 -0
  39. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/ltx.yaml +27 -0
  40. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/qwen_image.yaml +37 -0
  41. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/sd15.yaml +58 -0
  42. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/sd35.yaml +33 -0
  43. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/sdxl.yaml +124 -0
  44. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/families/wan.yaml +47 -0
  45. comfy_draftsman-0.15.0/src/comfy_draftsman/knowledge/techniques.yaml +34 -0
  46. comfy_draftsman-0.15.0/src/comfy_draftsman/server.py +2862 -0
  47. comfy_draftsman-0.15.0/src/comfy_draftsman/session.py +105 -0
  48. comfy_draftsman-0.15.0/tests/conftest.py +28 -0
  49. comfy_draftsman-0.15.0/tests/fixtures/object_info_trimmed.json +4645 -0
  50. comfy_draftsman-0.15.0/tests/fixtures/sdxl_simple_example.json +1346 -0
  51. comfy_draftsman-0.15.0/tests/fixtures/subgraph_real_template.json +1071 -0
  52. comfy_draftsman-0.15.0/tests/test_annotate.py +271 -0
  53. comfy_draftsman-0.15.0/tests/test_catalog.py +117 -0
  54. comfy_draftsman-0.15.0/tests/test_client.py +165 -0
  55. comfy_draftsman-0.15.0/tests/test_dynamic_combo.py +207 -0
  56. comfy_draftsman-0.15.0/tests/test_hardware_fit.py +211 -0
  57. comfy_draftsman-0.15.0/tests/test_integration_live.py +264 -0
  58. comfy_draftsman-0.15.0/tests/test_knowledge.py +145 -0
  59. comfy_draftsman-0.15.0/tests/test_layout.py +224 -0
  60. comfy_draftsman-0.15.0/tests/test_lint.py +82 -0
  61. comfy_draftsman-0.15.0/tests/test_mcp_e2e.py +179 -0
  62. comfy_draftsman-0.15.0/tests/test_mcp_offline.py +550 -0
  63. comfy_draftsman-0.15.0/tests/test_model.py +323 -0
  64. comfy_draftsman-0.15.0/tests/test_output_relocation.py +269 -0
  65. comfy_draftsman-0.15.0/tests/test_packaging.py +103 -0
  66. comfy_draftsman-0.15.0/tests/test_port.py +115 -0
  67. comfy_draftsman-0.15.0/tests/test_registry.py +76 -0
  68. comfy_draftsman-0.15.0/tests/test_round10_subgraphs.py +1266 -0
  69. comfy_draftsman-0.15.0/tests/test_round12_api_fixes.py +310 -0
  70. comfy_draftsman-0.15.0/tests/test_round13_fixes.py +192 -0
  71. comfy_draftsman-0.15.0/tests/test_round14_layout_queue.py +318 -0
  72. comfy_draftsman-0.15.0/tests/test_round15_cowork.py +223 -0
  73. comfy_draftsman-0.15.0/tests/test_round16_find_workflow.py +245 -0
  74. comfy_draftsman-0.15.0/tests/test_round17_audit.py +355 -0
  75. comfy_draftsman-0.15.0/tests/test_round18_tokens.py +362 -0
  76. comfy_draftsman-0.15.0/tests/test_round19_primitives_and_types.py +655 -0
  77. comfy_draftsman-0.15.0/tests/test_round1_fixes.py +203 -0
  78. comfy_draftsman-0.15.0/tests/test_round20_v3_types_and_subgraph_edits.py +440 -0
  79. comfy_draftsman-0.15.0/tests/test_round21_widget_flags_and_autogrow.py +380 -0
  80. comfy_draftsman-0.15.0/tests/test_round22_optional_source_and_lookalikes.py +59 -0
  81. comfy_draftsman-0.15.0/tests/test_round23_family_and_layout.py +183 -0
  82. comfy_draftsman-0.15.0/tests/test_round23_force_socket.py +179 -0
  83. comfy_draftsman-0.15.0/tests/test_round23_knob_cards.py +149 -0
  84. comfy_draftsman-0.15.0/tests/test_round23_layout_ops.py +221 -0
  85. comfy_draftsman-0.15.0/tests/test_round23_note_honesty.py +137 -0
  86. comfy_draftsman-0.15.0/tests/test_round23_reader_priority.py +232 -0
  87. comfy_draftsman-0.15.0/tests/test_round23_resolution_alignment.py +158 -0
  88. comfy_draftsman-0.15.0/tests/test_round23_sources.py +128 -0
  89. comfy_draftsman-0.15.0/tests/test_round45_fixes.py +262 -0
  90. comfy_draftsman-0.15.0/tests/test_round6_feedback.py +201 -0
  91. comfy_draftsman-0.15.0/tests/test_round7_fixes.py +109 -0
  92. comfy_draftsman-0.15.0/tests/test_round8_execution.py +588 -0
  93. comfy_draftsman-0.15.0/tests/test_round9_feedback.py +258 -0
  94. comfy_draftsman-0.15.0/tests/test_server_save.py +186 -0
  95. comfy_draftsman-0.15.0/tests/test_session.py +43 -0
  96. comfy_draftsman-0.15.0/tests/test_spend_gate.py +453 -0
  97. comfy_draftsman-0.15.0/tests/test_validate.py +306 -0
  98. 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.