mud/docs/plans/2026-02-14-toml-help-system.md

28 KiB

TOML Help System Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Replace hardcoded help text with TOML-driven help topics in content/help/, and add @help admin commands for creating, editing, and removing help topics in-game.

Architecture: Help topics are TOML files in content/help/ loaded at startup into a _help_topics dict. The help command checks topics before falling back to command detail. @help admin commands provide a guided creation/editing flow, using the existing Editor for body text only (not raw TOML). Metadata fields are prompted inline.

Tech Stack: Python 3.12+, tomllib for reading, tomli_w for writing TOML, existing Editor class for body editing.


Design

TOML format (content/help/<name>.toml)

name = "zones"
title = "zone building guide"
admin = false
body = """
zones are spatial containers - rooms, dungeons, overworld areas.
each zone has a terrain grid, spawn point, and optional portals.

  listing zones
    @zones              list all registered zones

  navigating
    @goto <zone>        teleport to a zone's spawn point
    enter <portal>      step through a portal to another zone

  building
    @dig <name> <w> <h> create a new blank zone
    @paint              toggle paint mode for terrain editing
    @save               save current zone to file

  see: @zones, @goto, @dig, @paint, @save
"""

Fields:

  • name (required) — topic name, matches filename stem
  • title (optional) — short description shown in listings, defaults to name
  • admin (optional) — if true, only admins can view. defaults to false
  • body (required) — the help text, multiline string

How help <topic> resolution works

  1. Check _help_topics dict for exact match
  2. If found and admin=True, check player.is_admin
  3. If found, display title + body
  4. If not found, fall through to existing command detail logic

@help subcommands

  • @help — list all help topics (name + title, admin-only topics marked)
  • @help create — guided creation: prompts for name, title, admin flag, then opens editor for body
  • @help edit <topic> — shows current metadata (enter to keep), opens editor for body
  • @help remove <topic> — deletes topic file + removes from dict (with confirmation)

Editor integration

The editor opens with ONLY the body text. When saved, the @help command reassembles the full TOML and writes it. The player never sees TOML syntax.


Task 1: Help topic dataclass and loader

Files:

  • Modify: src/mudlib/content.py — add HelpTopic dataclass and load_help_topics()

Step 1: Write the failing test

Create tests/test_help_topics.py:

"""Tests for TOML help topic loading."""

import textwrap
from pathlib import Path

import pytest

from mudlib.content import HelpTopic, load_help_topics


@pytest.fixture
def help_dir(tmp_path):
    """Create a temp directory with sample help TOML files."""
    topic = tmp_path / "combat.toml"
    topic.write_text(textwrap.dedent("""\
        name = "combat"
        title = "combat primer"
        body = \"\"\"
        combat is initiated when you attack another entity.
        use skills to learn your available moves.
        \"\"\"
    """))

    admin_topic = tmp_path / "building.toml"
    admin_topic.write_text(textwrap.dedent("""\
        name = "building"
        title = "builder's guide"
        admin = true
        body = \"\"\"
        use @dig to create zones and @paint to edit terrain.
        \"\"\"
    """))
    return tmp_path


def test_load_help_topics(help_dir):
    topics = load_help_topics(help_dir)
    assert "combat" in topics
    assert "building" in topics


def test_help_topic_fields(help_dir):
    topics = load_help_topics(help_dir)
    combat = topics["combat"]
    assert combat.name == "combat"
    assert combat.title == "combat primer"
    assert combat.admin is False
    assert "combat is initiated" in combat.body


def test_help_topic_admin_flag(help_dir):
    topics = load_help_topics(help_dir)
    building = topics["building"]
    assert building.admin is True


def test_help_topic_title_defaults_to_name(tmp_path):
    topic = tmp_path / "simple.toml"
    topic.write_text('name = "simple"\nbody = "just a test"\n')
    topics = load_help_topics(tmp_path)
    assert topics["simple"].title == "simple"


def test_load_help_topics_empty_dir(tmp_path):
    topics = load_help_topics(tmp_path)
    assert topics == {}


def test_load_help_topics_skips_bad_files(tmp_path):
    bad = tmp_path / "broken.toml"
    bad.write_text("not valid toml [[[")
    good = tmp_path / "good.toml"
    good.write_text('name = "good"\nbody = "works"\n')
    topics = load_help_topics(tmp_path)
    assert "good" in topics
    assert "broken" not in topics

Step 2: Run test to verify it fails

Run: pytest tests/test_help_topics.py -v Expected: ImportError — HelpTopic and load_help_topics don't exist yet.

Step 3: Write minimal implementation

Add to src/mudlib/content.py:

@dataclass
class HelpTopic:
    """A help topic loaded from a TOML file."""

    name: str
    body: str
    title: str = ""
    admin: bool = False

    def __post_init__(self):
        if not self.title:
            self.title = self.name


def load_help_topic(path: Path) -> HelpTopic:
    """Load a single help topic from a TOML file."""
    with open(path, "rb") as f:
        data = tomllib.load(f)

    return HelpTopic(
        name=data["name"],
        body=data["body"].strip(),
        title=data.get("title", ""),
        admin=data.get("admin", False),
    )


def load_help_topics(directory: Path) -> dict[str, HelpTopic]:
    """Load all help topics from a directory of TOML files."""
    topics: dict[str, HelpTopic] = {}
    for path in directory.glob("*.toml"):
        try:
            topic = load_help_topic(path)
            topics[topic.name] = topic
        except Exception as e:
            log.warning("failed to load help topic from %s: %s", path, e)
    return topics

Also add from dataclasses import dataclass to imports in content.py.

Step 4: Run test to verify it passes

Run: pytest tests/test_help_topics.py -v Expected: all PASS.

Step 5: Commit

Message: Add HelpTopic dataclass and TOML loader


Task 2: Wire help topics into the help command

Files:

  • Modify: src/mudlib/commands/help.py — add topic lookup, topic listing
  • Modify: src/mudlib/server.py — load help topics at startup

Step 1: Write the failing tests

Add to tests/test_help_topics.py:

from unittest.mock import AsyncMock, MagicMock

from mudlib import commands
from mudlib.commands import help as help_mod  # noqa: F401
from mudlib.commands.help import _help_topics


@pytest.fixture
def mock_writer():
    writer = MagicMock()
    writer.write = MagicMock()
    writer.drain = AsyncMock()
    return writer


@pytest.fixture
def player(mock_writer):
    from mudlib.player import Player

    return Player(name="Tester", x=0, y=0, reader=MagicMock(), writer=mock_writer)


@pytest.fixture
def admin_player(mock_writer):
    from mudlib.player import Player

    p = Player(name="Admin", x=0, y=0, reader=MagicMock(), writer=mock_writer)
    p.is_admin = True
    return p


@pytest.fixture(autouse=True)
def _clear_topics():
    _help_topics.clear()
    yield
    _help_topics.clear()


@pytest.mark.asyncio
async def test_help_shows_toml_topic(player):
    from mudlib.content import HelpTopic

    _help_topics["combat"] = HelpTopic(
        name="combat", body="fight stuff", title="combat primer"
    )
    await commands.dispatch(player, "help combat")
    output = "".join(c[0][0] for c in player.writer.write.call_args_list)
    assert "combat primer" in output
    assert "fight stuff" in output


@pytest.mark.asyncio
async def test_help_admin_topic_hidden_from_players(player):
    from mudlib.content import HelpTopic

    _help_topics["secret"] = HelpTopic(
        name="secret", body="hidden", title="secret stuff", admin=True
    )
    await commands.dispatch(player, "help secret")
    output = "".join(c[0][0] for c in player.writer.write.call_args_list)
    assert "hidden" not in output
    assert "unknown" in output.lower()


@pytest.mark.asyncio
async def test_help_admin_topic_visible_to_admins(admin_player):
    from mudlib.content import HelpTopic

    _help_topics["secret"] = HelpTopic(
        name="secret", body="hidden", title="secret stuff", admin=True
    )
    await commands.dispatch(admin_player, "help secret")
    output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
    assert "hidden" in output

Step 2: Run test to verify it fails

Run: pytest tests/test_help_topics.py -v -k "test_help_shows or test_help_admin" Expected: ImportError on _help_topics.

Step 3: Write implementation

In src/mudlib/commands/help.py, add a module-level dict and modify cmd_help:

# At module level, after imports
from mudlib.content import HelpTopic

_help_topics: dict[str, HelpTopic] = {}

Modify cmd_help to check topics BEFORE the existing hidden-command check:

async def cmd_help(player: Player, args: str) -> None:
    args = args.strip()
    if not args:
        await player.send(
            "type help <command> for details. see also: commands, skills, client\r\n"
        )
        return

    # Check help topics first
    topic = _help_topics.get(args)
    if topic is not None:
        if topic.admin and not player.is_admin:
            await player.send(f"Unknown command: {args}\r\n")
            return
        await player.send(f"{topic.title}\r\n{topic.body}\r\n")
        return

    # Existing hidden command check...
    defn = _registry.get(args)
    if defn is not None and defn.hidden:
        if defn.admin and not player.is_admin:
            await player.send(f"Unknown command: {args}\r\n")
            return
        await defn.handler(player, "")
        return

    await _show_command_detail(player, args)

In src/mudlib/server.py, add loading after the content commands block:

from mudlib.content import load_help_topics
from mudlib.commands.help import _help_topics

# Load help topics from content/help/
help_dir = pathlib.Path(__file__).resolve().parents[2] / "content" / "help"
if help_dir.exists():
    loaded_topics = load_help_topics(help_dir)
    _help_topics.update(loaded_topics)
    log.info("loaded %d help topics from %s", len(loaded_topics), help_dir)

Step 4: Run test to verify it passes

Run: pytest tests/test_help_topics.py -v Expected: all PASS.

Step 5: Commit

Message: Wire TOML help topics into help command


Task 3: Migrate hardcoded zones help to TOML

Files:

  • Create: content/help/zones.toml
  • Modify: src/mudlib/commands/help.py — remove cmd_zones_help function and its registration
  • Modify: tests/test_help_command.py — adjust zones tests

Step 1: Create the TOML file

name = "zones"
title = "zones"
admin = true
body = """
  zones are spatial containers - rooms, dungeons, overworld areas.
  each zone has a terrain grid, spawn point, and optional portals.

  listing zones
    @zones              list all registered zones

  navigating
    @goto <zone>        teleport to a zone's spawn point
    enter <portal>      step through a portal to another zone

  building
    @dig <name> <w> <h> create a new blank zone
    @paint              toggle paint mode for terrain editing
    @save               save current zone to file

  see: @zones, @goto, @dig, @paint, @save
"""

Step 2: Remove cmd_zones_help and its registration from help.py

Delete the cmd_zones_help function (lines 357-378) and its register() call (lines 415-424).

Step 3: Update tests

The existing test_help_zones_* tests in test_help_command.py need to load the topic into _help_topics instead of relying on the hidden command. Update them to populate _help_topics with the zones topic, and remove the build import that was only needed for the old hidden command.

Also update test_help_topics.py to add a test that verifies the zones TOML file loads correctly from content/help/.

Step 4: Run all tests

Run: pytest tests/test_help_command.py tests/test_help_topics.py -v Expected: all PASS.

Step 5: Commit

Message: Migrate zones help from hardcoded to TOML


Task 4: @help list command

Files:

  • Create: src/mudlib/commands/helpadmin.py
  • Modify: src/mudlib/server.py — import the module

Step 1: Write the failing test

Add to tests/test_help_topics.py:

from mudlib.commands import helpadmin  # noqa: F401


@pytest.mark.asyncio
async def test_at_help_lists_topics(admin_player):
    from mudlib.content import HelpTopic

    _help_topics["combat"] = HelpTopic(
        name="combat", body="fight", title="combat primer"
    )
    _help_topics["zones"] = HelpTopic(
        name="zones", body="build", title="zone guide", admin=True
    )
    await commands.dispatch(admin_player, "@help")
    output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
    assert "combat" in output
    assert "combat primer" in output
    assert "zones" in output
    assert "[admin]" in output


@pytest.mark.asyncio
async def test_at_help_requires_admin(player):
    await commands.dispatch(player, "@help")
    output = "".join(c[0][0] for c in player.writer.write.call_args_list)
    # Should be rejected by dispatch (admin=True check)
    assert "unknown" in output.lower() or output == ""

Step 2: Run test to verify it fails

Run: pytest tests/test_help_topics.py -v -k "at_help" Expected: ImportError on helpadmin.

Step 3: Write implementation

Create src/mudlib/commands/helpadmin.py:

"""Admin help topic management commands (@help)."""

from mudlib.commands import CommandDefinition, register
from mudlib.commands.help import _help_topics
from mudlib.player import Player


async def cmd_at_help(player: Player, args: str) -> None:
    """List all help topics or dispatch to subcommands."""
    args = args.strip()

    if args.startswith("create"):
        await _cmd_create(player, args[6:].strip())
        return
    if args.startswith("edit"):
        await _cmd_edit(player, args[4:].strip())
        return
    if args.startswith("remove"):
        await _cmd_remove(player, args[6:].strip())
        return

    # Default: list all topics
    if not _help_topics:
        await player.send("No help topics defined.\r\n")
        return

    lines = ["help topics"]
    for name in sorted(_help_topics):
        topic = _help_topics[name]
        admin_tag = " [admin]" if topic.admin else ""
        lines.append(f"  {name:<20} {topic.title}{admin_tag}")
    lines.append("")
    lines.append("  @help create          create a new topic")
    lines.append("  @help edit <topic>    edit an existing topic")
    lines.append("  @help remove <topic>  remove a topic")
    await player.send("\r\n".join(lines) + "\r\n")


async def _cmd_create(player: Player, args: str) -> None:
    """Placeholder for guided topic creation."""
    await player.send("Not yet implemented.\r\n")


async def _cmd_edit(player: Player, args: str) -> None:
    """Placeholder for topic editing."""
    await player.send("Not yet implemented.\r\n")


async def _cmd_remove(player: Player, args: str) -> None:
    """Placeholder for topic removal."""
    await player.send("Not yet implemented.\r\n")


register(
    CommandDefinition(
        "@help", cmd_at_help, admin=True, mode="*", help="manage help topics"
    )
)

Add import mudlib.commands.helpadmin to server.py with the other command imports.

Step 4: Run test to verify it passes

Run: pytest tests/test_help_topics.py -v Expected: all PASS.

Step 5: Commit

Message: Add @help list command for admin topic management


Task 5: @help create with guided prompts and editor

Files:

  • Modify: src/mudlib/commands/helpadmin.py — implement _cmd_create

This is the most involved task. The guided flow needs to:

  1. Prompt for topic name (if not provided as arg)
  2. Prompt for title
  3. Prompt for admin flag (y/n)
  4. Open the editor for body text
  5. On editor save, assemble TOML and write to content/help/<name>.toml

Step 1: Write the failing tests

Add to tests/test_help_topics.py:

@pytest.mark.asyncio
async def test_at_help_create_with_all_args_opens_editor(admin_player):
    """@help create <name> should set up prompting state."""
    await commands.dispatch(admin_player, "@help create combat")
    output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
    assert "title" in output.lower()


@pytest.mark.asyncio
async def test_at_help_create_no_args_prompts_name(admin_player):
    """@help create with no args prompts for the topic name."""
    await commands.dispatch(admin_player, "@help create")
    output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
    assert "name" in output.lower()

Step 2: Run test to verify it fails

Expected: "Not yet implemented" in output, assertions fail.

Step 3: Write implementation

The create flow uses player.pending_input — a callback that intercepts the next line of input. This is the same pattern MUD systems use for multi-step prompts. We'll need to add this to the Player class if it doesn't exist, or use an equivalent mechanism.

Implementation approach for prompted input:

async def _cmd_create(player: Player, args: str) -> None:
    """Guided topic creation."""
    if args:
        # Name provided, skip to title prompt
        await _prompt_title(player, name=args)
    else:
        await player.send("topic name: ")
        player.pending_input = _on_name_input


async def _on_name_input(player: Player, line: str) -> None:
    """Handle name input."""
    name = line.strip()
    if not name:
        await player.send("Cancelled.\r\n")
        return
    if name in _help_topics:
        await player.send(f"Topic '{name}' already exists. Use @help edit.\r\n")
        return
    await _prompt_title(player, name=name)


async def _prompt_title(player: Player, name: str) -> None:
    await player.send(f"title [{name}]: ")
    player.pending_input = lambda p, line: _on_title_input(p, line, name)


async def _on_title_input(player: Player, line: str, name: str) -> None:
    title = line.strip() or name
    await player.send("admin only? [y/N]: ")
    player.pending_input = lambda p, line: _on_admin_input(p, line, name, title)


async def _on_admin_input(
    player: Player, line: str, name: str, title: str
) -> None:
    admin = line.strip().lower() in ("y", "yes")
    # Open editor for body
    await _open_body_editor(player, name=name, title=title, admin=admin, body="")


async def _open_body_editor(
    player: Player, name: str, title: str, admin: bool, body: str
) -> None:
    from mudlib.editor import Editor

    save_cb = _make_help_save_callback(player, name, title, admin)
    player.editor = Editor(
        save_callback=save_cb,
        content_type="text",
        color_depth=player.color_depth or "16",
        initial_content=body,
    )
    player.mode_stack.append("editor")
    await player.send("Entering editor for body text. Type :h for help.\r\n")

The save callback writes the TOML file:

def _make_help_save_callback(
    player: Player, name: str, title: str, admin: bool
):
    async def save_callback(content: str) -> None:
        _write_help_toml(name, title, admin, content)
        # Update in-memory dict
        from mudlib.content import HelpTopic
        _help_topics[name] = HelpTopic(
            name=name, body=content.strip(), title=title, admin=admin
        )
        await player.send(f"Help topic '{name}' saved.\r\n")

    return save_callback


def _write_help_toml(name: str, title: str, admin: bool, body: str) -> None:
    """Write a help topic TOML file."""
    from pathlib import Path

    help_dir = Path(__file__).resolve().parents[2] / "content" / "help"
    help_dir.mkdir(parents=True, exist_ok=True)
    path = help_dir / f"{name}.toml"

    lines = [f'name = "{name}"']
    lines.append(f'title = "{title}"')
    if admin:
        lines.append("admin = true")
    lines.append(f'body = """\n{body.strip()}\n"""')
    path.write_text("\n".join(lines) + "\n")

Important: Check whether player.pending_input exists. If not, this needs to be added to the Player class and the server's input loop. Look for existing prompt patterns — if there's already a confirmation prompt somewhere, follow that pattern. If not, pending_input is a Callable | None on Player, and the server input loop checks it before dispatch:

# In server.py input loop, before command dispatch:
if player.pending_input is not None:
    callback = player.pending_input
    player.pending_input = None
    await callback(player, inp)
    continue

Step 4: Run tests

Run: pytest tests/test_help_topics.py -v Expected: all PASS.

Step 5: Commit

Message: Add @help create with guided prompts and editor


Task 6: @help edit <topic>

Files:

  • Modify: src/mudlib/commands/helpadmin.py

Step 1: Write the failing tests

@pytest.mark.asyncio
async def test_at_help_edit_unknown_topic(admin_player):
    await commands.dispatch(admin_player, "@help edit bogus")
    output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
    assert "not found" in output.lower()


@pytest.mark.asyncio
async def test_at_help_edit_existing_opens_editor(admin_player):
    from mudlib.content import HelpTopic

    _help_topics["combat"] = HelpTopic(
        name="combat", body="old body", title="combat primer"
    )
    await commands.dispatch(admin_player, "@help edit combat")
    output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
    # Should prompt for title with current value as default
    assert "combat primer" in output
    # Should eventually open editor
    assert admin_player.editor is not None
    assert "old body" in "\n".join(admin_player.editor.buffer)

Step 2: Run test to verify it fails

Expected: "Not yet implemented" output.

Step 3: Write implementation

async def _cmd_edit(player: Player, args: str) -> None:
    """Edit an existing help topic."""
    name = args.strip()
    if not name:
        await player.send("Usage: @help edit <topic>\r\n")
        return

    topic = _help_topics.get(name)
    if topic is None:
        await player.send(f"Topic '{name}' not found.\r\n")
        return

    await player.send(f"title [{topic.title}]: ")
    player.pending_input = lambda p, line: _on_edit_title(
        p, line, name, topic
    )


async def _on_edit_title(player, line, name, topic):
    title = line.strip() or topic.title
    await player.send(f"admin only? [{'Y' if topic.admin else 'y'}/{'N' if not topic.admin else 'n'}]: ")
    player.pending_input = lambda p, line: _on_edit_admin(
        p, line, name, title, topic
    )


async def _on_edit_admin(player, line, name, title, topic):
    if line.strip():
        admin = line.strip().lower() in ("y", "yes")
    else:
        admin = topic.admin
    await _open_body_editor(player, name=name, title=title, admin=admin, body=topic.body)

Step 4: Run tests

Run: pytest tests/test_help_topics.py -v Expected: all PASS.

Step 5: Commit

Message: Add @help edit for modifying existing topics


Task 7: @help remove <topic>

Files:

  • Modify: src/mudlib/commands/helpadmin.py

Step 1: Write the failing tests

@pytest.mark.asyncio
async def test_at_help_remove_unknown_topic(admin_player):
    await commands.dispatch(admin_player, "@help remove bogus")
    output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
    assert "not found" in output.lower()


@pytest.mark.asyncio
async def test_at_help_remove_prompts_confirmation(admin_player):
    from mudlib.content import HelpTopic

    _help_topics["combat"] = HelpTopic(
        name="combat", body="fight", title="combat primer"
    )
    await commands.dispatch(admin_player, "@help remove combat")
    output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list)
    assert "confirm" in output.lower() or "y/n" in output.lower()

Step 2: Run test to verify it fails

Expected: "Not yet implemented" output.

Step 3: Write implementation

async def _cmd_remove(player: Player, args: str) -> None:
    """Remove a help topic."""
    name = args.strip()
    if not name:
        await player.send("Usage: @help remove <topic>\r\n")
        return

    if name not in _help_topics:
        await player.send(f"Topic '{name}' not found.\r\n")
        return

    await player.send(f"Remove topic '{name}'? [y/N]: ")
    player.pending_input = lambda p, line: _on_remove_confirm(p, line, name)


async def _on_remove_confirm(player: Player, line: str, name: str) -> None:
    if line.strip().lower() not in ("y", "yes"):
        await player.send("Cancelled.\r\n")
        return

    # Remove from memory
    _help_topics.pop(name, None)

    # Remove file
    from pathlib import Path
    path = Path(__file__).resolve().parents[2] / "content" / "help" / f"{name}.toml"
    if path.exists():
        path.unlink()

    await player.send(f"Topic '{name}' removed.\r\n")

Step 4: Run tests

Run: pytest tests/test_help_topics.py -v Expected: all PASS.

Step 5: Commit

Message: Add @help remove with confirmation prompt


Task 8: Add pending_input to Player and server loop

This task may need to be done BEFORE task 5 if pending_input doesn't exist on Player yet. Check first — if there's already a prompt/callback mechanism, use that instead.

Files:

  • Modify: src/mudlib/player.py — add pending_input field
  • Modify: src/mudlib/server.py — check pending_input before dispatch

Step 1: Write the failing test

def test_player_has_pending_input():
    from mudlib.player import Player
    from unittest.mock import MagicMock

    p = Player(name="Test", x=0, y=0, reader=MagicMock(), writer=MagicMock())
    assert p.pending_input is None

Step 2: Run test to verify it fails

Expected: AttributeError.

Step 3: Write implementation

In Player dataclass, add:

pending_input: Callable | None = None

In server.py input loop (find where dispatch is called), add before it:

if player.pending_input is not None:
    callback = player.pending_input
    player.pending_input = None
    await callback(player, inp)
    continue

Step 4: Run tests

Run: pytest tests/test_help_topics.py -v Expected: all PASS.

Step 5: Commit

Message: Add pending_input callback to Player for prompted input


Task 9: Create help @help topic

Files:

  • Create: content/help/@help.toml

Step 1: Create the file

name = "@help"
title = "help system administration"
admin = true
body = """
  the help system stores topics as TOML files in content/help/.
  each topic has a name, title, optional admin flag, and body text.

  commands
    @help               list all help topics
    @help create        create a new topic (guided)
    @help create <name> create a new topic with the given name
    @help edit <topic>  edit an existing topic
    @help remove <topic> remove a topic

  creating topics
    @help create walks you through each field:
      name        the topic name (what players type after 'help')
      title       short description shown in listings
      admin only  whether only admins can view it
      body        the help text (opens in the editor)

  editing topics
    @help edit shows current values for each field.
    press enter to keep the current value.
    the editor opens with the existing body text.

  files
    topics are stored in content/help/<name>.toml
    they can also be edited directly with any text editor.
    changes are loaded at server startup.
"""

Step 2: Write test

def test_at_help_toml_loads_from_content():
    from pathlib import Path
    from mudlib.content import load_help_topics

    help_dir = Path(__file__).resolve().parents[1] / "content" / "help"
    if help_dir.exists():
        topics = load_help_topics(help_dir)
        assert "@help" in topics
        assert topics["@help"].admin is True

Step 3: Run tests

Run: pytest tests/test_help_topics.py -v Expected: all PASS.

Step 4: Commit

Message: Add help @help topic for admin documentation


Task execution order

Tasks 1-3 are sequential (each builds on the last). Task 8 (pending_input) must be done before Task 5. Task 4 can happen after Task 2. Tasks 5-7 are sequential. Task 9 can happen any time after Task 2.

Suggested order: 1 → 2 → 3 → 8 → 4 → 5 → 6 → 7 → 9

Parallelizable after Task 2: Tasks 3 and 8 can be done in parallel. After Task 8: Tasks 4 and 5 can start (4 is simpler, do first).