Audit registered node documentation
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user