feat: make goosehints jinja templated (#43)

This commit is contained in:
Salman Mohammed
2024-09-04 11:44:34 -07:00
committed by GitHub
parent 7d5461d240
commit 72d927f84f
5 changed files with 86 additions and 20 deletions
+18
View File
@@ -126,10 +126,28 @@ Rules designed to control or manage the output of the model. Moderators that cur
`goose` can be extended with toolkits, and out of the box there are some available: `goose` can be extended with toolkits, and out of the box there are some available:
* `developer`: for general-purpose development capabilities, including plan management, shell execution, and file operations, with default shell strategies like using ripgrep.
* `screen`: for letting goose take a look at your screen to help debug or work on designs (gives goose eyes) * `screen`: for letting goose take a look at your screen to help debug or work on designs (gives goose eyes)
* `github`: for awareness and suggestions on how to use github * `github`: for awareness and suggestions on how to use github
* `repo_context`: for summarizing and understanding a repository you are working in. * `repo_context`: for summarizing and understanding a repository you are working in.
#### Configuring goose per repo
If you are using the `developer` toolkit, `goose` adds the content from `.goosehints`
file in working directory to the system prompt of the `developer` toolkit. The hints
file is meant to provide additional context about your project. The context can be
user-specific or at the project level in which case, you
can commit it to git. `.goosehints` file is Jinja templated so you could have something
like this:
```
Here is an overview of how to contribute:
{% include 'CONTRIBUTING.md' %}
The following justfile shows our common commands:
```just
{% include 'justfile' %}
```
### Examples ### Examples
#### provider as `anthropic` #### provider as `anthropic`
+2 -2
View File
@@ -10,8 +10,8 @@ integration *FLAGS:
uv run pytest tests -m integration {{FLAGS}} uv run pytest tests -m integration {{FLAGS}}
format: format:
ruff check . --fix uvx ruff check . --fix
ruff format . uvx ruff format .
coverage *FLAGS: coverage *FLAGS:
uv run coverage run -m pytest tests -m "not integration" {{FLAGS}} uv run coverage run -m pytest tests -m "not integration" {{FLAGS}}
+2 -2
View File
@@ -12,7 +12,7 @@ from rich.table import Table
from rich.text import Text from rich.text import Text
from goose.toolkit.base import Toolkit, tool from goose.toolkit.base import Toolkit, tool
from goose.toolkit.utils import get_language from goose.toolkit.utils import get_language, render_template
def keep_unsafe_command_prompt(command: str) -> PromptType: def keep_unsafe_command_prompt(command: str) -> PromptType:
@@ -37,7 +37,7 @@ class Developer(Toolkit):
hints_path = Path(".goosehints") hints_path = Path(".goosehints")
system_prompt = Message.load("prompts/developer.jinja").text system_prompt = Message.load("prompts/developer.jinja").text
if hints_path.is_file(): if hints_path.is_file():
goosehints = hints_path.read_text() goosehints = render_template(hints_path)
system_prompt = f"{system_prompt}\n\nHints:\n{goosehints}" system_prompt = f"{system_prompt}\n\nHints:\n{goosehints}"
return system_prompt return system_prompt
+23
View File
@@ -1,8 +1,11 @@
from pathlib import Path from pathlib import Path
from typing import Optional
from pygments.lexers import get_lexer_for_filename from pygments.lexers import get_lexer_for_filename
from pygments.util import ClassNotFound from pygments.util import ClassNotFound
from jinja2 import Environment, FileSystemLoader
def get_language(filename: Path) -> str: def get_language(filename: Path) -> str:
""" """
@@ -19,3 +22,23 @@ def get_language(filename: Path) -> str:
return lexer.name return lexer.name
except ClassNotFound: except ClassNotFound:
return "" return ""
def render_template(template_path: Path, context: Optional[dict] = None) -> str:
"""
Renders a Jinja2 template given a Pathlib path, with no context needed.
:param template_path: Path to the Jinja2 template file.
:param context: Optional dictionary containing the context for rendering the template.
:return: Rendered template as a string.
"""
# Ensure the path is absolute and exists
if not template_path.is_absolute():
template_path = template_path.resolve()
if not template_path.exists():
raise FileNotFoundError(f"Template file {template_path} does not exist.")
env = Environment(loader=FileSystemLoader(template_path.parent))
template = env.get_template(template_path.name)
return template.render(context or {})
+27 -2
View File
@@ -1,12 +1,23 @@
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from unittest.mock import MagicMock, Mock from unittest.mock import MagicMock, Mock
import pytest import pytest
from goose.toolkit.base import Requirements from goose.toolkit.base import Requirements
from goose.toolkit.developer import Developer from goose.toolkit.developer import Developer
from contextlib import contextmanager
import os
@contextmanager
def change_dir(new_dir):
"""Context manager to temporarily change the current working directory."""
original_dir = os.getcwd()
os.chdir(new_dir)
try:
yield
finally:
os.chdir(original_dir)
@pytest.fixture @pytest.fixture
@@ -30,6 +41,20 @@ def developer_toolkit():
return toolkit return toolkit
def test_system_prompt_with_goosehints(temp_dir, developer_toolkit):
readme_file = temp_dir / "README.md"
readme_file.write_text("This is from the README.md file.")
hints_file = temp_dir / ".goosehints"
jinja_template_content = "Hints:\n\n{% include 'README.md' %}\nEnd."
hints_file.write_text(jinja_template_content)
with change_dir(temp_dir):
system_prompt = developer_toolkit.system()
expected_end = "Hints:\n\nThis is from the README.md file.\nEnd."
assert system_prompt.endswith(expected_end)
def test_update_plan(developer_toolkit): def test_update_plan(developer_toolkit):
tasks = [ tasks = [
{"description": "Task 1", "status": "planned"}, {"description": "Task 1", "status": "planned"},