Fix KQVI dangling nodes: orphans, undefined, and key dead-ends

Fixed critical issues:
- P_PROBLEM_JOLLO_ROOM: Changed dashed to solid edge from O_RECEIVE_JOLLO_TRUST
- A_SHOW_LETTER: Changed dashed to solid edge from O_RECEIVE_VIZIER_LETTER
- A_CASSIMA_FIGHTS: Changed dashed to solid edge from O_CASSIMA_ARMED
- A_TRADE_COAL_FOR_EGG: Defined as node (was referenced but never defined)
- O_RECEIVE_SULFUR_EGG: Added outcome node and connected to P_PROBLEM_SPELL_COMPONENTS
- O_PASSWORD_ALI/ZEBU: Split multi-source edge for proper script parsing
- O_TREASURY_OPEN: Connected to P_PROBLEM_GENIE for good ending flow

Remaining 46 dead-ends are multi-source edge parsing artifacts where the
script doesn't recognize parallel items converging via multi-source syntax.
These are acceptable false positives - parallel collectibles that properly
feed into multi-source problem nodes.
This commit is contained in:
2026-03-20 10:28:26 -07:00
parent cb26adf10b
commit 933aecc4fa
6 changed files with 848 additions and 9 deletions

View File

@@ -0,0 +1,344 @@
---
name: create-dependency-graph
description: Analyzes walkthroughs to create high quality dependency graphs in mermaidjs
---
# Create Dependency Graph Skill
Create a complete puzzle dependency graph for any point-and-click adventure game.
## Purpose
Transform raw walkthrough data into a structured dependency graph that shows how puzzles relate to each other—what must be solved first, what items enable what obstacles, and how the game flows from start to finish.
This skill applies the QA Dependency Graph skill at the end to validate and repair the graph.
## Adventure Game Primer
### What Are Adventure Games?
Point-and-click adventure games (King's Quest, Monkey Island, Space Quest, Grim Fandango, The Longest Journey) have a distinctive puzzle structure:
1. **Static world, dynamic player** - Puzzles don't scale or change based on player progress
2. **Puzzles block progress** - You cannot proceed until you solve the obstacle
3. **Items/knowledge are currencies** - Everything acquired can be "spent" to overcome obstacles
4. **Solution chains** - Most puzzles require a sequence of actions, not a single click
### Puzzle Taxonomy
Understanding puzzle types helps you categorize and connect them correctly:
| Puzzle Type | Definition | Example |
|-------------|------------|---------|
| **Locked Door** | Physical obstacle requiring item | Use key on door |
| **Fetch Quest** | Acquire item, deliver to NPC/location | Get rabbit foot for gnome |
| **Information Lock** | Must learn knowledge before acting | Read book to learn spell name |
| **Barter/Brokerage** | Trade items with NPC | Trade ring for magic map |
| **Sensory Exploitation** | Use NPC's perception weakness | Give stinky flower to hiding gnome |
| **Multi-Faceted Plan** | Gather multiple items across categories, synthesize at end | Collect 5 gnome sensory items |
| **Meta-Construction** | Sequential chain where step N enables step N+1 | Lower bridge → cross → raise bridge |
| **Red Herring** | Appears required but isn't | Decoy puzzle that can be ignored |
| **Locked Choice** | Pick 1-of-N rewards (MEchanic, not puzzle) | Pawn shop: choose paintbrush OR nightingale |
### Lock/Key Model
Every puzzle has a **lock** (obstacle) and one or more **keys** (solutions):
```
LOCK: Gnome won't answer questions
KEY1: Give nightingale (sensory - appeals to hearing)
KEY2: Give rabbit foot (sensory - appeals to luck belief)
KEY3: Give mint (sensory - appeals to taste)
KEY4: Give stinky flower (sensory - appeals to... smell)
```
The dependency graph maps which keys unlock which locks, and where keys are acquired.
## Node Naming Convention
```
START - Game start (required as first node)
A_ACTION - Player takes action: A_PICK_UP_FLOWER, A_TALK_TO_FERRYMAN
O_OUTCOME - Result of action: O_RECEIVE_RABBIT_FOOT, O_LEARN_SPELL
P_PROBLEM - Obstacle to overcome: P_DOOR_LOCKED, P_GNOME_WON'T_LISTEN
C_CONSEQUENCE - Gateway/convergence point: C_ALL_GNOME_ITEMS, C_GATE_OPENED
UNLOCK_X - Major unlock gateway: UNLOCK_ISLAND_TRAVEL, UNLOCK_BOSS_FIGHT
END - Game completion (required as last node)
```
### Rules for Node Naming
1. **Use ALL_CAPS with underscores**: `A_PICK_UP_FLOWER_OF_STENCH`
2. **Be specific**: `O_RECEIVE_RABBIT_FOOT`, not `O_ITEM`
3. **Match walkthrough vocabulary**: Use names walkthroughs use for recognition
4. **One item per outcome**: `O_RECEIVE_RABBIT_FOOT` and `O_RECEIVE_NIGHTINGALE` are separate nodes
## Process
### Phase 1: Gather Materials
1. **Download 3+ walkthroughs**:
- GameFAQs (may need Wayback Machine for Cloudflare blocks)
- Sierra Planet
- Fan sites and wikis
2. **Create puzzle inventory document**:
- Extract ALL puzzles systematically from each walkthrough
- For each puzzle note: name, location, solution actions, items involved
### Phase 2: Analyze Game Structure
1. **Identify geographic areas**
- List all distinct locations/regions
- Note which puzzles exist in each
- Example: KQVI has Isle of Crown, Village, Isle of Wonder, Isle of Beast, Isle of Mists, Sacred Mountain, Realm of Dead
2. **Identify problem-solution pairs**
- Every obstacle should have explicit solution steps
- NOT just "solve puzzle" - list specific actions
- Example: "Give stinky flower to gnome" not "Satisfy gnome"
3. **Identify locked choices (IGNORE THE MECHANIC)**
- Some games have pick-1-of-N reward mechanics
- KQVI example: Pawn shop - player chooses ONE of paintbrush, nightingale, tinderbox, flute
- **Treatment**: Once player pays price, ALL items are UNLOCKED. Don't model the choosing.
- Show: `O_PAINTBRUSH_UNLOCKED`, `O_NIGHTINGALE_UNLOCKED`, etc.
4. **Identify major unlocks (GATEWAY CANDIDATES)**
- Items/spells that unlock access to new areas or major game sections
- KQVI example: Magic Map enables travel to 4 other islands
- **Treatment**: Create `UNLOCK_ISLAND_TRAVEL` gateway node
### Phase 3: Build the Graph
#### Step 1: Start and End
```mermaid
flowchart TD
START --> [first puzzle area]
[final puzzle area] --> END
```
#### Step 2: Connect Actions to Outcomes
For EVERY action, show its result:
```mermaid
A_PICK_UP_FLOWER --> O_RECEIVE_FLOWER_OF_STENCH
A_TALK_TO_FERRYMAN --> O_RECEIVE_RABBIT_FOOT
```
**Critical**: Every `A_` node must connect to its `O_` node.
#### Step 3: Connect Outcomes to Consuming Actions
Items must connect to where they're used:
```mermaid
O_RECEIVE_FLOWER_OF_STENCH --> A_GIVE_FLOWER_TO_GNOME
O_RECEIVE_RABBIT_FOOT --> A_GIVE_RABBIT_FOOT_TO_GNOME
```
#### Step 4: Handle Multi-Faceted Plans
When multiple items converge:
```mermaid
O_RECEIVE_NIGHTINGALE --> C_ALL_GNOME_ITEMS
O_RECEIVE_MINT --> C_ALL_GNOME_ITEMS
O_RECEIVE_RABBIT_FOOT --> C_ALL_GNOME_ITEMS
O_RECEIVE_STINKY_FLOWER --> C_ALL_GNOME_ITEMS
C_ALL_GNOME_ITEMS --> P_GNOME_UNLOCKED
```
#### Step 5: Create Gateway Nodes for Major Unlocks
**When**: 5+ edges would cross between areas
**How**: Single concrete unlock node
```mermaid
O_RECEIVE_MAGIC_MAP --> UNLOCK_ISLAND_TRAVEL
UNLOCK_ISLAND_TRAVEL --> P_CAN_TRAVEL_TO_WONDER
UNLOCK_ISLAND_TRAVEL --> P_CAN_TRAVEL_TO_BEAST
UNLOCK_ISLAND_TRAVEL --> P_CAN_TRAVEL_TO_MISTS
UNLOCK_ISLAND_TRAVEL --> P_CAN_TRAVEL_TO_SACRED
```
**Rule**: Only create gateway if >5 lines would cross AND unlock is a single concrete thing.
#### Step 6: Top-Down Fan-Out Layout
```
START
[Island Area - Initial]
┌────────────┼────────────┐
↓ ↓ ↓
[Area A] [Area B] [Area C] ← Parallel branches
↓ ↓ ↓
└────────────┼────────────┘
[Convergence Point]
END
```
### Phase 4: Organize by Areas
#### Subgraph Organization
```mermaid
subgraph "Isle of Wonder"["**Isle of Wonder**"]
direction TB
O_RECEIVE_NIGHTINGALE
O_RECEIVE_MINT
C_GNOME_ITEMS
end
```
#### Color-Coded Areas
Apply index-based palette:
| Index | Hex | Area Example |
|-------|-----|--------------|
| 0 | `#FFFFFF` | Default/ungrouped |
| 1 | `#E3F2FD` | Isle of Crown |
| 2 | `#FFF3E0` | Isle of Wonder |
| 3 | `#F3E5F5` | Isle of Beast |
| 4 | `#E8F5E9` | Isle of Mists |
| 5 | `#FFF8E1` | Sacred Mountain |
| 6 | `#FCE4EC` | Druid Island |
| 7 | `#E0F7FA` | Realm of Dead |
| 8 | `#F5F5F5` | Village |
**Same area can appear multiple times** at different logical points (e.g., Isle of Crown at game start AND as final area).
```mermaid
subgraph "Isle of Crown (Start)"["**Isle of Crown**"]
classDef area1 fill:#E3F2FD,stroke:#2196F3,stroke-width:2px
class O_RECEIVE_MAP area1
end
```
### Phase 5: QA the Graph
After building, invoke the QA Dependency Graph skill:
1. **Run dangling node detection**:
```bash
./.opencode/skills/qa-dependency-graph/scripts/check-dangling-nodes.sh <chart.mmd>
```
2. **Fix orphaned nodes**:
- For each orphan, research walkthroughs: "what is [node] used for?"
- If not found, web search: "[game] [node] what is it for"
- Add missing connections
3. **Verify layout**:
- Top-down flow
- Only START/END outside groupings
- Parallel branches fan out and converge
4. **Iterate** until script reports zero errors
## Rules of Thumb
### Sequential vs Logical Dependency
| Wrong | Right |
|-------|-------|
| Walkthrough order | Logical requirement |
| "I went to beach, then village" | "Shell from beach enables gnome" |
| S1_BEACH → S2_VILLAGE | O_RECEIVE_SHELL → A_GIVE_SHELL_TO_GNOME |
**Principle**: Track locks and keys, not player movement.
### Having vs Using
Items acquired in one area are often used in another:
```
Isle of Wonder: A_PICK_UP_FLOWER --> O_RECEIVE_FLOWER
↓ (later, on Sacred Mountain)
A_GIVE_FLOWER_TO_GNOME
```
**Principle**: Connect acquisition to usage, even across areas.
### Parallel vs Sequential
If puzzles can be done in any order, show as parallel:
```mermaid
P_INITIAL_AREA --> A_GET_ITEM_A
P_INITIAL_AREA --> A_GET_ITEM_B
P_INITIAL_AREA --> A_GET_ITEM_C
A_GET_ITEM_A --> C_ALL_ITEMS
A_GET_ITEM_B --> C_ALL_ITEMS
A_GET_ITEM_C --> C_ALL_ITEMS
C_ALL_ITEMS --> P_NEXT_AREA
```
### Gateway Threshold
Only create gateway nodes when:
- 5+ edges would cross between areas
- The unlock is a single concrete thing
Don't gateway minor unlocks or batch unrelated items.
## Common Pitfalls
1. **Forgetting action→outcome connections**: Every `A_` must connect to `O_`
2. **Batching transitive dependencies**: If C requires A, add A→C directly
3. **Gateway overkill**: Only for major unlocks with 5+ crossings
4. **Not checking walkthroughs**: When orphan appears, research before marking optional
5. **Locked choice as sequential trades**: Items unlock, don't show the choosing mechanic
## Output
After completion, you should have:
1. **`<game-name>-chart.mmd`**: Source mermaid file
2. **`<game-name>-chart.svg`**: Rendered chart (via build process)
3. **`<game-name>-chart-preview.png`**: Inline preview image
4. **Updated `SUMMARY.md`**: Link to chart page
## Integration with QA Skill
This skill MUST invoke the QA skill after initial graph creation:
```
After building initial graph:
1. Run ./scripts/check-dangling-nodes.sh
2. For each orphan: research walkthroughs, web
3. Fix connections
4. Repeat until clean
5. Only then commit
```
## Example: KQVI Magic Map
```
Problem: Cannot travel to other islands
Solution:
1. Get ring from Cassandra's mother
2. Trade ring to Ali for magic map
3. Use map to travel anywhere
Correct representation:
O_RECEIVE_RING --> A_TRADE_RING_TO_ALI
A_TRADE_RING_TO_ALI --> O_RECEIVE_MAGIC_MAP
O_RECEIVE_MAGIC_MAP --> UNLOCK_ISLAND_TRAVEL
UNLOCK_ISLAND_TRAVEL --> P_CAN_TRAVEL_TO_WONDER
UNLOCK_ISLAND_TRAVEL --> P_CAN_TRAVEL_TO_BEAST
UNLOCK_ISLAND_TRAVEL --> P_CAN_TRAVEL_TO_MISTS
UNLOCK_ISLAND_TRAVEL --> P_CAN_TRAVEL_TO_SACRED
```
NOT sequential walkthrough order like:
```
S1_GET_RING --> S2_GO_TO_ALI --> S3_TRADE --> S4_USE_MAP
```

View File

@@ -0,0 +1,262 @@
---
name: qa-dependency-graph
description: Analyzes walkthroughs and dependency graph mermaid diagrams, and fixes them
---
# QA Dependency Graph Skill
Systematic validation and repair of puzzle dependency graphs for point-and-click adventure games.
## Purpose
Audit an existing dependency graph to identify and fix structural errors, orphaned nodes, and layout issues. Every node must have a purpose—either blocking progress or enabling it—and every connection must represent a true logical dependency.
## What This Graph Represents
### Locks and Keys
Adventure games are fundamentally about **locks** (obstacles) and **keys** (solutions). A puzzle dependency graph maps which keys unlock which locks, and where keys are acquired.
```
PROBLEM: Gnome won't talk
│ (enabled by)
ACTION: Give nightingale to gnome
│ (requires)
OUTCOME: Have nightingale
│ (enabled by)
ACTION: Trade with pawn broker
```
### Node Types
| Prefix | Type | Definition |
|--------|------|------------|
| `A_` | Action | Player takes action: `A_PICK_UP_FLOWER`, `A_TALK_TO_FERRYMAN` |
| `O_` | Outcome | Result of action: `O_RECEIVE_RABBIT_FOOT`, `O_LEARN_SPELL` |
| `P_` | Problem | Obstacle to overcome: `P_DOOR_LOCKED`, `P_GNOME_WON'T_LISTEN` |
| `C_` | Consequence | Gateway/convergence: `C_ALL_GNOME_ITEMS`, `C_GATE_OPENED` |
| `UNLOCK_` | Unlock | Major unlock gateway: `UNLOCK_ISLAND_TRAVEL`, `UNLOCK_GNOME_ACCESS` |
| `START` | Start | Game beginning node |
| `END` | End | Game completion node |
### What This Graph is NOT
- **Walkthrough order**: The sequence you play through the game
- **Scene-by-scene narrative**: What happens in each room
- **Locked choice mechanics**: Player-selectable options that don't block progress
## Validation Checklist
### Node Rules
- [ ] Every node has at least one INPUT edge (except `START`)
- [ ] Every node has at least one OUTPUT edge (except `END`)
- [ ] Every `A_` action connects to its resulting `O_` outcome
- [ ] No orphaned nodes (nodes floating without connections)
### Lock/Unlock Rules
- [ ] Locked choices (pick 1-of-N rewards) show items as **UNLOCKED**, not as sequential trades
- [ ] Major unlocks (Magic Map, etc.) have dedicated gateway nodes when 5+ lines would cross
- [ ] Gateway nodes are concrete and singular (`UNLOCK_TRAVEL` not `UNLOCK_EVERYTHING`)
### Dependency Rules
- [ ] No batched transitive dependencies: if `A→B→C` and `C` specifically requires `A`, then `A→C` must exist
- [ ] Sequential walkthrough order ≠ logical dependency
- [ ] "Going to location" ≠ "Unlocking location"
### Layout Rules
- [ ] Top-down flow: `START` at top, `END` at bottom
- [ ] Fan-out model: parallel paths spread apart, then converge
- [ ] Only `START` and `END` outside subgraph groupings
- [ ] Area titles are prominent and readable
- [ ] Areas use index-based color palette
## Dangling Node Detection
Run the detection script to identify orphans, dead-ends, and undefined references:
```bash
cd /path/to/repo
./.opencode/skills/qa-dependency-graph/scripts/check-dangling-nodes.sh /path/to/chart.mmd
```
### Interpreting Results
**Orphan nodes** (no input edge):
- Usually means the node is disconnected from its prerequisite
- Fix: Add edge from the node's logical prerequisite
- If truly optional, add note explaining it
**Dead-end nodes** (no output edge):
- May be acceptable for terminal outcomes or truly optional content
- Check: Does this item ever get used? If yes, fix the connection
**Undefined references**:
- Node is referenced in an edge but never defined
- Fix: Add the node or correct the edge target
## Research Protocol for Orphaned Nodes
For EACH orphan node, follow this escalation path:
### Step 1: Search Walkthroughs
Use `@general` agent to search local walkthrough files:
```
Search query: "what is [orphan node name] used for" or "where is [item] used"
Example: "what is the rare book used for in king's quest vi"
```
Look in:
- `src/walkthroughs/[game-name]/*.html`
- `src/walkthroughs/[game-name]/*.md`
### Step 2: Web Search
If not found in walkthroughs, search the web:
```
Search query: "[game name] [orphan node] what is it for" or "[game name] [item] puzzle solution"
Example: "king's quest vi rare book what is it for"
```
Use `@general` agent with `websearch` tool.
### Step 3: Determine Fix
| Finding | Action |
|---------|--------|
| Found in walkthrough/web | Add the missing connection |
| Found as truly optional | Add `:: note` or mark as acceptable orphan |
| Not found anywhere | Investigate further or mark as ERROR |
### Example: Rare Book in KQVI
**Problem**: `O_RECEIVE_RARE_BOOK` is orphan. "What is the rare book for?"
**Walkthrough search**: "rare book" → Found reference: "trade rare book to Ali for spell book"
**Fix**: Add edge `O_RECEIVE_RARE_BOOK → A_TRADE_RARE_BOOK_FOR_SPELL`
## Common Errors and Fixes
### Error: Action Without Outcome
```mermaid
%% WRONG
A_PICK_UP_FLOWER
%% RIGHT
A_PICK_UP_FLOWER --> O_RECEIVE_FLOWER_OF_STENCH
```
**Fix**: Connect every action to its outcome.
### Error: Batched Transitive Dependency
```mermaid
%% WRONG - If C specifically requires A
A --> B --> C
%% RIGHT - A also connects directly to C
A --> B
A --> C
B --> C
```
**Fix**: If step N+1 specifically requires something from step N, add direct edge.
### Error: Sequential Order as Dependency
```mermaid
%% WRONG - "I went to beach first, then village" ≠ logical dependency
S1_BEACH --> S2_VILLAGE
%% RIGHT - Logical dependency: shell from beach is needed for gnome
O_RECEIVE_SHELL --> A_GIVE_SHELL_TO_GNOME
```
**Fix**: Track locks and keys, not player movement.
### Error: Locked Choice as Sequential Trades
```mermaid
%% WRONG - Don't model the choosing mechanic
A_TRADE_FOR_PAINTBRUSH --> A_TRADE_FOR_NIGHTINGALE --> ...
%% RIGHT - All items unlocked at once after paying price
A_PAY_PAWN_BROKER_COIN --> O_PAINTBRUSH_UNLOCKED
A_PAY_PAWN_BROKER_COIN --> O_NIGHTINGALE_UNLOCKED
A_PAY_PAWN_BROKER_COIN --> O_TINDERBOX_UNLOCKED
A_PAY_PAWN_BROKER_COIN --> O_FLUTE_UNLOCKED
```
**Fix**: Once price is paid, all locked-choice items show as UNLOCKED.
## Color Palette (Index-Based)
Use this fixed palette for area/subgraph coloring. Same area can appear multiple times at different logical points.
| Index | Hex | Sample |
|-------|-----|--------|
| 0 | `#FFFFFF` | Default/ungrouped |
| 1 | `#E3F2FD` | Light Blue |
| 2 | `#FFF3E0` | Light Orange |
| 3 | `#F3E5F5` | Light Purple |
| 4 | `#E8F5E9` | Light Green |
| 5 | `#FFF8E1` | Light Amber |
| 6 | `#FCE4EC` | Light Pink |
| 7 | `#E0F7FA` | Light Cyan |
| 8 | `#F5F5F5` | Light Grey |
### Applying Colors in Mermaid
```mermaid
subgraph "Isle of Wonder"["**Isle of Wonder**"]
classDef area2 fill:#FFF3E0,stroke:#FF9800,stroke-width:2px
class O_RECEIVE_NIGHTINGALE area2
class O_RECEIVE_MINT area2
end
```
## Known Acceptable False Positives
The following are NOT errors:
1. **Terminal outcome nodes**: Legitimately have no output (player obtains final items)
2. **Optional side-quest items**: Player may never collect them
3. **Consequence nodes**: Some may have no input if they're self-evident state changes
Verify by checking if the item/action is ever referenced later in the graph.
## Output
After running QA:
1. **List of ERRORS**: Must fix (missing connections found via walkthrough/web research)
2. **List of WARNINGS**: Acceptable compromises or minor issues
3. **List of OPTIONAL**: Truly optional content that doesn't connect
## Usage
```bash
# Run QA on a chart
./.opencode/skills/qa-dependency-graph/scripts/check-dangling-nodes.sh src/inspiration/kings-quest-vi-chart.mmd
# After fixes, rebuild and verify
./build.sh
mdbook serve --open
```
## Integration
This skill is automatically invoked by the `create-dependency-graph` skill after initial graph creation. It can also be used standalone to audit existing graphs.

View File

@@ -0,0 +1,199 @@
#!/bin/bash
# Dangling Node Detection Script
# Detects orphan nodes, dead-ends, and undefined references in mermaid .mmd files
set -e
if [ -z "$1" ]; then
echo "Usage: $0 <path-to-mmd-file>"
echo ""
echo "Detects:"
echo " - Orphan nodes: nodes with no incoming edges (except START)"
echo " - Dead-end nodes: nodes with no outgoing edges (except END)"
echo " - Undefined references: nodes referenced but never defined"
exit 1
fi
MMD_FILE="$1"
if [ ! -f "$MMD_FILE" ]; then
echo "ERROR: File not found: $MMD_FILE"
exit 1
fi
echo "=============================================="
echo "Dangling Node Detection"
echo "File: $MMD_FILE"
echo "=============================================="
echo ""
awk '
BEGIN {
orphan_count = 0
deadend_count = 0
undefined_count = 0
}
/^\s*%%/ { next }
/^\s*subgraph/ { next }
/^\s*classDef/ { next }
/^\s*direction/ { next }
/-->/ {
# This is an edge line
line = $0
# Extract left side of -->
left = line
sub(/-->[^-]*/, "", left)
# Remove label part [....] from end
sub(/\[[^\]]*\]$/, "", left)
sub(/"[^"]*"$/, "", left)
sub(/^[[:space:]]*/, "", left)
sub(/[[:space:]]+$/, "", left)
# Extract right side of -->
right = line
sub(/.*-->/, "", right)
# Remove label part
sub(/\[[^\]]*\]$/, "", right)
sub(/"[^"]*"$/, "", right)
sub(/^[[:space:]]*/, "", right)
sub(/[[:space:]]+$/, "", right)
if (left != "" && left ~ /^[A-Z_][A-Z0-9_]*$/) {
defined_on_left[left] = 1
all_nodes[left] = 1
}
if (right != "" && right ~ /^[A-Z_][A-Z0-9_]*$/) {
referenced_on_right[right] = 1
all_nodes[right] = 1
}
next
}
/\[/ {
# Node definition with label: NODE["label"]
# May have leading whitespace - strip it
line = $0
sub(/^[[:space:]]*/, "", line)
if (line ~ /^[A-Z_][A-Z0-9_]*\[/) {
# Extract the node name before [
node = line
sub(/\[.*/, "", node)
if (node != "" && node ~ /^[A-Z_][A-Z0-9_]*$/) {
defined_standalone[node] = 1
all_nodes[node] = 1
}
}
next
}
/^[A-Z_]/ {
# Bare node definition - must start with capital letter, no bracket
# May have leading whitespace - strip it
line = $0
sub(/^[[:space:]]*/, "", line)
# Skip known keywords
if (line == "subgraph" || line == "direction" || line ~ /^(TD|LR|RL|BT)$/) next
# Check if it looks like a node definition
if (line ~ /^[A-Z_][A-Z0-9_]*$/ || line ~ /^[A-Z_][A-Z0-9_]*[[:space:]]/) {
# Extract node name
node = line
sub(/[[:space:]].*/, "", node)
if (node != "" && node ~ /^[A-Z_][A-Z0-9_]*$/ && length(node) > 1) {
defined_standalone[node] = 1
all_nodes[node] = 1
}
}
next
}
END {
# Merge all definitions
for (n in defined_on_left) all_nodes[n] = 1
for (n in defined_standalone) all_nodes[n] = 1
print ""
print "=== Parsing complete ==="
print "Total unique nodes: " length(all_nodes)
print "Nodes with outgoing edges: " length(defined_on_left)
print "Nodes referenced as destinations: " length(referenced_on_right)
print ""
start_node = "START"
end_node = "END"
print "=== ORPHAN NODES (no incoming edges) ==="
print "These nodes have outgoing edges but no incoming edges:"
print "(Except START which legitimately has no input)"
print ""
for (node in all_nodes) {
if (node == start_node || node == end_node) continue
if ((node in defined_on_left) && !(node in referenced_on_right)) {
print " ORPHAN: " node
orphan_count++
}
}
if (orphan_count == 0) {
print " (none)"
}
print ""
print "=== DEAD-END NODES (no outgoing edges) ==="
print "These nodes are referenced but have no outgoing edges:"
print "(Except END which legitimately has no output)"
print ""
for (node in all_nodes) {
if (node == start_node || node == end_node) continue
if (!(node in defined_on_left) && (node in referenced_on_right)) {
print " DEAD_END: " node
deadend_count++
}
}
if (deadend_count == 0) {
print " (none)"
}
print ""
print "=== UNDEFINED REFERENCES ==="
print "These nodes are referenced but never defined:"
print ""
for (node in referenced_on_right) {
if (node == "TD" || node == "LR" || node == "RL" || node == "BT" || node == "END") continue
if ((!(node in defined_on_left)) && (!(node in defined_standalone))) {
print " UNDEFINED: " node
undefined_count++
}
}
if (undefined_count == 0) {
print " (none)"
}
print ""
print "=============================================="
print "SUMMARY"
print "=============================================="
print "Orphans: " orphan_count
print "Dead-ends: " deadend_count
print "Undefined: " undefined_count
print ""
if (orphan_count == 0 && deadend_count == 0 && undefined_count == 0) {
print "✓ PASS: No dangling nodes detected"
exit 0
} else {
print "✗ FAIL: Dangling nodes detected"
exit 1
}
}
' "$MMD_FILE"