From eb1bdbf305c1cf979995a94c5ae9971649d8ebf4 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 27 Jun 2026 20:02:23 +0200 Subject: [PATCH] Audit registered node documentation --- docs/prompt-architecture-improvement-plan.md | 3 ++ docs/prompt-pool-routing-map.md | 12 ++++--- tools/prompt_map_audit.py | 35 ++++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index d771885..d024f53 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -39,6 +39,9 @@ The map audit currently sees: - 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 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 diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 39220d1..17f8d9d 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -871,14 +871,14 @@ These do not own prompt pool wording, but they affect execution and review: | Node family | Files | Purpose | | --- | --- | --- | -| Loop nodes | `loop_nodes.py`, `web/loop_slots.js` | While/for loop execution and carry values. | -| 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`. | +| 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` | `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. | | 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. | | 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`. | -| 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. | | 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. | @@ -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 map and the architecture plan, and registered in `SMOKE_CASES` by their 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 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 -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 diff --git a/tools/prompt_map_audit.py b/tools/prompt_map_audit.py index f008663..e618d8b 100644 --- a/tools/prompt_map_audit.py +++ b/tools/prompt_map_audit.py @@ -118,6 +118,10 @@ AUDIT_DOC_SNIPPETS: tuple[tuple[str, str], ...] = ( "docs/prompt-pool-routing-map.md", "multi-seed route sweeps", ), + ( + "docs/prompt-pool-routing-map.md", + "node documentation validation", + ), ) 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_POSITION_EXCLUSIONS = {"any"} +NODE_DOC_PATHS = ( + "docs/prompt-pool-routing-map.md", + "README.md", +) def _literal_or_none(node: ast.AST) -> Any: @@ -734,6 +742,26 @@ def _routing_doc_errors() -> list[tuple[str, str, str]]: 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): def __init__(self, path: Path) -> None: self.path = path @@ -1071,6 +1099,13 @@ def main() -> int: return 1 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") prompt_row_read_errors = _prompt_row_read_errors() if prompt_row_read_errors: