Audit registered node documentation

This commit is contained in:
2026-06-27 20:02:23 +02:00
parent 1ca9c95bfe
commit eb1bdbf305
3 changed files with 46 additions and 4 deletions
@@ -39,6 +39,9 @@ The map audit currently sees:
- Route simulation now has an opt-in multi-seed sweep, and the smoke suite runs - Route simulation now has an opt-in multi-seed sweep, and the smoke suite runs
a three-seed sweep so representative route/noise checks are not proven by one a three-seed sweep so representative route/noise checks are not proven by one
lucky seed only. lucky seed only.
- Map audit now fails when a registered ComfyUI node display name is missing
from the route map or README, so utility nodes cannot silently drift out of
user-facing documentation.
## Architectural Finding ## Architectural Finding
+8 -4
View File
@@ -871,14 +871,14 @@ These do not own prompt pool wording, but they affect execution and review:
| Node family | Files | Purpose | | Node family | Files | Purpose |
| --- | --- | --- | | --- | --- | --- |
| Loop nodes | `loop_nodes.py`, `web/loop_slots.js` | While/for loop execution and carry values. | | Loop nodes | `loop_nodes.py`, `web/loop_slots.js` | While/for loop execution and carry values. Includes `SxCP While Loop Start`, `SxCP While Loop End`, `SxCP For Loop Start`, `SxCP For Loop End`, `SxCP Loop Int Add`, `SxCP Loop Less Than`, and `SxCP Loop Less Than Or Equal`. |
| Index switch | `loop_nodes.py`, `index_switch_policy.py`, `web/index_switch_slots.js` | Multi-input to selected output, and selected input to multi-output routing. Pure index-base, missing-input, route-output, status, and lazy-input policy lives in `index_switch_policy.py`. | | Index switch | `loop_nodes.py`, `index_switch_policy.py`, `web/index_switch_slots.js` | `SxCP Index Switch`: multi-input to selected output, and selected input to multi-output routing. Pure index-base, missing-input, route-output, status, and lazy-input policy lives in `index_switch_policy.py`. |
| Accumulator | `loop_nodes.py`, `web/accumulator_preview.js` | Stores generated values/images during workflow execution and previews/reorders/deletes them. | | Accumulator | `loop_nodes.py`, `web/accumulator_preview.js` | Stores generated values/images during workflow execution and previews/reorders/deletes them. |
| Persistent text preview | `loop_nodes.py`, `web/preview_any_text.js` | Stores any value as text and keeps it after workflow reload. | | Persistent text preview | `loop_nodes.py`, `web/preview_any_text.js` | Stores any value as text and keeps it after workflow reload. |
| Builder node wrappers | `node_builder.py`, imported by `__init__.py` | Direct prompt builder and config-driven prompt builder ComfyUI declarations. | | Builder node wrappers | `node_builder.py`, imported by `__init__.py` | Direct prompt builder and config-driven prompt builder ComfyUI declarations. |
| Seed and resolution utility nodes | `node_seed_resolution.py`, imported by `__init__.py` | UI wrappers for global/per-axis seed configs via `seed_config.py`, plus SDXL/Krea width/height helpers. | | Seed and resolution utility nodes | `node_seed_resolution.py`, imported by `__init__.py` | UI wrappers for global/per-axis seed configs via `seed_config.py`, plus SDXL/Krea width/height helpers. |
| Camera utility nodes | `node_camera.py`, imported by `__init__.py` | UI wrappers for direct camera config, orbit-to-camera config, and Qwen MultiAngle camera translation via `camera_config.py`. | | Camera utility nodes | `node_camera.py`, imported by `__init__.py` | UI wrappers for direct camera config, orbit-to-camera config, and Qwen MultiAngle camera translation via `camera_config.py`. |
| Character utility nodes | `node_character.py`, imported by `__init__.py` | Hair, age/body/eyes/clothing pools, manual details, character slots, and profile save/load nodes. | | Character utility nodes | `node_character.py`, imported by `__init__.py` | Hair, age/body/eyes/clothing pools, manual details, character slots, and profile save/load nodes. Includes `SxCP Character Age Range`, `SxCP Character Body Pool`, `SxCP Woman Body Pool`, `SxCP Man Body Pool`, `SxCP Eye Color Pool`, and `SxCP Character Clothing`. |
| Hardcore position utility nodes | `node_hardcore_position.py`, imported by `__init__.py` | Position-family pool and action/filter gates for hardcore routes. | | Hardcore position utility nodes | `node_hardcore_position.py`, imported by `__init__.py` | Position-family pool and action/filter gates for hardcore routes. |
| Formatter utility nodes | `node_formatter.py`, imported by `__init__.py` | Caption naturalizer, Krea2 formatter, and SDXL formatter node wrappers. | | Formatter utility nodes | `node_formatter.py`, imported by `__init__.py` | Caption naturalizer, Krea2 formatter, and SDXL formatter node wrappers. |
| Insta/OF utility nodes | `node_insta.py`, imported by `__init__.py` | Insta/OF option config and dual prompt-pair node wrappers. | | Insta/OF utility nodes | `node_insta.py`, imported by `__init__.py` | Insta/OF option config and dual prompt-pair node wrappers. |
@@ -914,11 +914,15 @@ The script does not import ComfyUI. It parses the repo and prints:
- route documentation validation so critical route modules are listed in this - route documentation validation so critical route modules are listed in this
map and the architecture plan, and registered in `SMOKE_CASES` by their map and the architecture plan, and registered in `SMOKE_CASES` by their
expected smoke cases. expected smoke cases.
- node documentation validation so every registered ComfyUI display name appears
in this route map or the README before the node can silently drift out of
user-facing docs.
Use its output to spot doc drift after adding a new node or pool. If a new node Use its output to spot doc drift after adding a new node or pool. If a new node
or pool appears there but not in this map, update the relevant route table. The or pool appears there but not in this map, update the relevant route table. The
script exits nonzero when JSON pool references, item template axes, critical script exits nonzero when JSON pool references, item template axes, critical
route docs, or critical route smoke registrations do not resolve. route docs, critical route smoke registrations, or registered node display
names do not resolve.
## Behavioral Smoke Helper ## Behavioral Smoke Helper
+35
View File
@@ -118,6 +118,10 @@ AUDIT_DOC_SNIPPETS: tuple[tuple[str, str], ...] = (
"docs/prompt-pool-routing-map.md", "docs/prompt-pool-routing-map.md",
"multi-seed route sweeps", "multi-seed route sweeps",
), ),
(
"docs/prompt-pool-routing-map.md",
"node documentation validation",
),
) )
PROMPT_ROW_READ_SCAN_GLOBS: tuple[str, ...] = ( PROMPT_ROW_READ_SCAN_GLOBS: tuple[str, ...] = (
@@ -137,6 +141,10 @@ ALLOWED_PROMPT_ROW_READS: set[tuple[str, str]] = {
ROUTE_POLICY_ACTION_EXCLUSIONS = {"default"} ROUTE_POLICY_ACTION_EXCLUSIONS = {"default"}
ROUTE_POLICY_POSITION_EXCLUSIONS = {"any"} ROUTE_POLICY_POSITION_EXCLUSIONS = {"any"}
NODE_DOC_PATHS = (
"docs/prompt-pool-routing-map.md",
"README.md",
)
def _literal_or_none(node: ast.AST) -> Any: def _literal_or_none(node: ast.AST) -> Any:
@@ -734,6 +742,26 @@ def _routing_doc_errors() -> list[tuple[str, str, str]]:
return errors return errors
def _node_documentation_errors(display_names: dict[str, Any]) -> list[tuple[str, str, str]]:
doc_parts = []
for doc_name in NODE_DOC_PATHS:
path = ROOT / doc_name
if path.exists():
doc_parts.append(path.read_text(encoding="utf-8"))
doc_text = "\n".join(doc_parts)
errors: list[tuple[str, str, str]] = []
if not doc_text:
return [("(node docs)", ", ".join(NODE_DOC_PATHS), "no node documentation sources found")]
for class_name, display_name in sorted(display_names.items(), key=lambda item: str(item[1])):
display_text = str(display_name or "").strip()
if not display_text:
errors.append((str(class_name), "display name", "registered node has empty display name"))
continue
if display_text not in doc_text:
errors.append((str(class_name), "node documentation", f"missing display name in docs: {display_text}"))
return errors
class _PromptRowReadVisitor(ast.NodeVisitor): class _PromptRowReadVisitor(ast.NodeVisitor):
def __init__(self, path: Path) -> None: def __init__(self, path: Path) -> None:
self.path = path self.path = path
@@ -1071,6 +1099,13 @@ def main() -> int:
return 1 return 1
print("OK: critical route modules are documented and covered by smoke cases.") print("OK: critical route modules are documented and covered by smoke cases.")
print("\n# Node Documentation Validation")
node_doc_errors = _node_documentation_errors(display)
if node_doc_errors:
print_table(("Node", "Location", "Issue"), node_doc_errors)
return 1
print("OK: registered node display names are documented in the route map or README.")
print("\n# Metadata Prompt Fallback Validation") print("\n# Metadata Prompt Fallback Validation")
prompt_row_read_errors = _prompt_row_read_errors() prompt_row_read_errors = _prompt_row_read_errors()
if prompt_row_read_errors: if prompt_row_read_errors: