From fcbac4ee24792db1b2ba41a702b911f38ace6ab3 Mon Sep 17 00:00:00 2001 From: Alistair Gray <131500831+ajgray-stripe@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:27:40 -0500 Subject: [PATCH] feat: recursively resolve `.goosehints` files (#358) --- src/goose/synopsis/moderator.py | 12 ++---------- src/goose/toolkit/developer.py | 15 +++++---------- src/goose/utils/goosehints.py | 21 +++++++++++++++++++++ tests/toolkit/test_developer.py | 14 +++++++++++++- 4 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 src/goose/utils/goosehints.py diff --git a/src/goose/synopsis/moderator.py b/src/goose/synopsis/moderator.py index 8b9dacca..b700f66f 100644 --- a/src/goose/synopsis/moderator.py +++ b/src/goose/synopsis/moderator.py @@ -1,6 +1,4 @@ import os -from goose.toolkit.utils import render_template -from pathlib import Path from exchange.content import Text from exchange.exchange import Exchange from exchange.message import Message @@ -8,6 +6,7 @@ from exchange.moderators import Moderator from exchange.moderators.passive import PassiveModerator from exchange.moderators.truncate import ContextTruncate from goose.synopsis.system import system +from goose.utils.goosehints import fetch_goosehints class Synopsis(Moderator): @@ -39,14 +38,7 @@ class Synopsis(Moderator): self.current_plan = "" self.originals = [] - hints = [] - hints_path = Path(".goosehints") - home_hints_path = Path.home() / ".config/goose/.goosehints" - if hints_path.is_file(): - hints.append(render_template(hints_path)) - if home_hints_path.is_file(): - hints.append(render_template(home_hints_path)) - self.hints = "\n".join(hints) + self.hints = fetch_goosehints() def rewrite(self, exchange: Exchange) -> None: # Get the last message, which would be either a user text or a user tool use diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 4bb40a15..adba64a0 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -7,7 +7,8 @@ from pathlib import Path from exchange import Message from goose.toolkit.base import Toolkit, tool -from goose.toolkit.utils import get_language, render_template, RULEPREFIX, RULESTYLE +from goose.toolkit.utils import get_language, RULEPREFIX, RULESTYLE +from goose.utils.goosehints import fetch_goosehints from goose.utils.shell import shell from rich.markdown import Markdown from rich.table import Table @@ -28,17 +29,11 @@ class Developer(Toolkit): def system(self) -> str: """Retrieve system configuration details for developer""" - hints_path = Path(".goosehints") system_prompt = Message.load("prompts/developer.jinja").text - home_hints_path = Path.home() / ".config/goose/.goosehints" - hints = [] - if hints_path.is_file(): - hints.append(render_template(hints_path)) - if home_hints_path.is_file(): - hints.append(render_template(home_hints_path)) + hints = fetch_goosehints() + if hints: - hints_text = "\n".join(hints) - system_prompt = f"{system_prompt}\n\nHints:\n{hints_text}" + system_prompt = f"{system_prompt}\n\nHints:\n{hints}" return system_prompt @tool diff --git a/src/goose/utils/goosehints.py b/src/goose/utils/goosehints.py new file mode 100644 index 00000000..a2acce76 --- /dev/null +++ b/src/goose/utils/goosehints.py @@ -0,0 +1,21 @@ +from pathlib import Path + +from goose.toolkit.utils import render_template + + +def fetch_goosehints() -> str: + hints = [] + dirs = [Path.cwd()] + list(Path.cwd().parents) + # reverse to go from parent to child + dirs.reverse() + + for dir in dirs: + hints_path = dir / ".goosehints" + if hints_path.is_file(): + hints.append(render_template(hints_path)) + + home_hints_path = Path.home() / ".config/goose/.goosehints" + if home_hints_path.is_file(): + hints.append(render_template(home_hints_path)) + + return "\n\n".join(hints) diff --git a/tests/toolkit/test_developer.py b/tests/toolkit/test_developer.py index d7e2c87f..8624ea54 100644 --- a/tests/toolkit/test_developer.py +++ b/tests/toolkit/test_developer.py @@ -75,6 +75,18 @@ def test_system_prompt_with_goosehints(temp_dir, developer_toolkit): assert system_prompt.endswith(expected_end) +def test_system_prompt_with_goosehints_from_parent_dir(temp_dir, developer_toolkit): + hints_file = temp_dir / ".goosehints" + hints_file.write_text("This is from the README.md file in parent.") + inner_temp_dir = temp_dir / "inner" + inner_temp_dir.mkdir(parents=True, exist_ok=True) + + with change_dir(inner_temp_dir): + system_prompt = developer_toolkit.system() + expected = "This is from the README.md file in parent." + assert system_prompt.endswith(expected) + + def test_system_prompt_with_goosehints_only_from_home_dir(temp_dir, developer_toolkit): readme_file_home = Path.home() / ".config/goose/README.md" readme_file_home.parent.mkdir(parents=True, exist_ok=True) @@ -113,7 +125,7 @@ def test_system_prompt_with_goosehints_only_from_home_dir(temp_dir, developer_to system_prompt = developer_toolkit.system() expected_content_local = "Hints from local:\n\nThis is from the README.md file.\nEnd." expected_content_home = "Hints from home:\n\nThis is from the README.md file in home.\nEnd." - expected_end = f"Hints:\n{expected_content_local}\n{expected_content_home}" + expected_end = f"Hints:\n{expected_content_local}\n\n{expected_content_home}" assert system_prompt.endswith(expected_end) finally: home_hints_file.unlink()