diff --git a/nanobot/command/builtin.py b/nanobot/command/builtin.py index 1258bc80..1aa2d648 100644 --- a/nanobot/command/builtin.py +++ b/nanobot/command/builtin.py @@ -419,20 +419,28 @@ async def cmd_dream_prompt(ctx: CommandContext) -> OutboundMessage: """Show or set up the workspace Dream memory instructions.""" store = ctx.loop.context.memory path = store.dream_prompt_file + display_path = path.relative_to(store.workspace).as_posix() args = ctx.args.strip().lower() if args == "init": - if path.exists(): + try: + prompt_exists_with_content = path.exists() and ( + not path.is_file() or bool(path.read_text(encoding="utf-8").strip()) + ) + except OSError: + prompt_exists_with_content = True + if prompt_exists_with_content: content = ( - f"Dream memory instructions already exist at `{path}`.\n\n" + f"Dream memory instructions already exist at `{display_path}`.\n\n" "Edit that file, or delete/empty it to return to nanobot's default." ) else: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(store.default_dream_prompt() + "\n", encoding="utf-8") content = ( - f"Created Dream memory instructions at `{path}`.\n\n" + f"Created Dream memory instructions at `{display_path}`.\n\n" "Edit that file to teach Dream how to organize memory. " + "This fully replaces nanobot's default Dream guide for this workspace. " "Delete or empty it to return to nanobot's default." ) elif args: @@ -440,13 +448,13 @@ async def cmd_dream_prompt(ctx: CommandContext) -> OutboundMessage: elif store.has_dream_prompt_override(): content = ( "Dream memory instructions: custom for this workspace\n\n" - f"- Path: `{path}`\n" + f"- Path: `{display_path}`\n" "- Delete or empty this file to return to nanobot's default." ) else: content = ( "Dream memory instructions: nanobot default\n\n" - f"- Editable file: `{path}`\n" + f"- Editable file: `{display_path}`\n" "- Run `/dream-prompt init` to create an editable copy." ) diff --git a/tests/command/test_builtin_dream.py b/tests/command/test_builtin_dream.py index b634b303..7b814434 100644 --- a/tests/command/test_builtin_dream.py +++ b/tests/command/test_builtin_dream.py @@ -218,8 +218,8 @@ async def test_dream_prompt_reports_default_prompt(tmp_path) -> None: out = await cmd_dream_prompt(_make_dream_prompt_ctx(tmp_path)) assert "Dream memory instructions: nanobot default" in out.content - assert "prompts" in out.content - assert "dream.md" in out.content + assert "prompts/dream.md" in out.content + assert str(tmp_path) not in out.content assert "/dream-prompt init" in out.content @@ -231,6 +231,9 @@ async def test_dream_prompt_init_copies_default_prompt(tmp_path) -> None: prompt_file = tmp_path / "prompts" / "dream.md" assert "Created Dream memory instructions" in out.content + assert "prompts/dream.md" in out.content + assert str(tmp_path) not in out.content + assert "fully replaces nanobot's default Dream guide" in out.content assert prompt_file.read_text(encoding="utf-8") == MemoryStore.default_dream_prompt() + "\n" @@ -244,9 +247,24 @@ async def test_dream_prompt_init_does_not_overwrite_existing_prompt(tmp_path) -> out = await cmd_dream_prompt(ctx) assert "already exist" in out.content + assert "prompts/dream.md" in out.content + assert str(tmp_path) not in out.content assert prompt_file.read_text(encoding="utf-8") == "custom" +@pytest.mark.asyncio +async def test_dream_prompt_init_recreates_empty_prompt(tmp_path) -> None: + prompt_file = tmp_path / "prompts" / "dream.md" + prompt_file.parent.mkdir() + prompt_file.write_text(" \n", encoding="utf-8") + ctx = _make_dream_prompt_ctx(tmp_path, "/dream-prompt init", "init") + + out = await cmd_dream_prompt(ctx) + + assert "Created Dream memory instructions" in out.content + assert prompt_file.read_text(encoding="utf-8") == MemoryStore.default_dream_prompt() + "\n" + + def test_dream_prompt_command_in_help_and_palette() -> None: palette = builtin_command_palette()