diff --git a/CHANGELOG.md b/CHANGELOG.md index aea6203e..eb7bd2bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ Status of the `main` branch. Changes prior to the next official version change will appear here. * General: - - Support `serena --version` CLI command for displaying the current version #1347 - Fix: Check for ignored path ignored `.git` folder only at the top level, not in every subdirectory (`Project._is_ignored_relative_path`) #1350 - `GetSymbolsOverviewTool`: ignored paths were not respected in LSP variant (fix in `SolidLanguageServer`) - Fix: Duplicate comments in re-saved YAML configuration files #1285 @@ -31,6 +30,13 @@ Status of the `main` branch. Changes prior to the next official version change w The `initial_instructions` tool provides the full prompt on demand, keeping the initial context lean. - Add `serena_info` tool for on-demand retrieval of usage information +* CLI: + - Support `serena --version` CLI command for displaying the current version #1347 + - Extend `prompts` subcommand with `print-prompt-template` and `print-cc-system-prompt-override`. + +* Clients: + - Document workaround to make Claude Code use Serena's tools after recent degradations caused by changes in CC harness and Opus 4.7 release. + * JetBrains: - Add `debug` tool: The agent can set breakpoints, inspect variables, evaluate expressions and control execution flow by directly interacting with the IDE's debugger, using a REPL-style interface for maximum flexibility. diff --git a/docs/02-usage/030_clients.md b/docs/02-usage/030_clients.md index ba12e29e..b1946bb7 100644 --- a/docs/02-usage/030_clients.md +++ b/docs/02-usage/030_clients.md @@ -117,7 +117,7 @@ As a workaround, we crafted a system prompt that counteracts this bias. When using Serena, we highly recommend that you start CC as ```shell -claude --system-prompt=$(serena cc-system-prompt-override) +claude --system-prompt="$(serena prompts print-cc-system-prompt-override)" ``` You can also consider adding the content of `serena cc-system-prompt-override` to your `CLAUDE.md` files, diff --git a/src/interprompt/jinja_template.py b/src/interprompt/jinja_template.py index 63f4fd87..32220155 100644 --- a/src/interprompt/jinja_template.py +++ b/src/interprompt/jinja_template.py @@ -30,6 +30,9 @@ class JinjaTemplate(ParameterizedTemplateInterface): parsed_content = self._template.environment.parse(self._template_string) self._parameters = sorted(jinja2.meta.find_undeclared_variables(parsed_content)) + def get_template_string(self) -> str: + return self._template_string + def render(self, **params: Any) -> str: """Renders the template with the given kwargs. You can find out which parameters are required by calling get_parameter_names().""" return self._template.render(**params) diff --git a/src/interprompt/multilang_prompt.py b/src/interprompt/multilang_prompt.py index e7322efb..3c84ced8 100644 --- a/src/interprompt/multilang_prompt.py +++ b/src/interprompt/multilang_prompt.py @@ -19,6 +19,9 @@ class PromptTemplate(ToStringMixin, ParameterizedTemplateInterface): def _tostring_exclude_private(self) -> bool: return True + def get_template_string(self) -> str: + return self._jinja_template.get_template_string() + def render(self, **params: Any) -> str: return self._jinja_template.render(**params) diff --git a/src/interprompt/prompt_factory.py b/src/interprompt/prompt_factory.py index 1769e7f2..9ad6ab7c 100644 --- a/src/interprompt/prompt_factory.py +++ b/src/interprompt/prompt_factory.py @@ -2,7 +2,7 @@ import logging import os from typing import Any -from .multilang_prompt import DEFAULT_LANG_CODE, LanguageFallbackMode, MultiLangPromptCollection, PromptList +from .multilang_prompt import DEFAULT_LANG_CODE, LanguageFallbackMode, MultiLangPromptCollection, PromptList, PromptTemplate log = logging.getLogger(__name__) @@ -23,9 +23,15 @@ class PromptFactoryBase: self.lang_code = lang_code self._prompt_collection = MultiLangPromptCollection(prompts_dir, fallback_mode=fallback_mode) + def _get_prompt_template(self, prompt_name: str) -> PromptTemplate: + return self._prompt_collection.get_prompt_template(prompt_name, lang_code=self.lang_code) + + def get_prompt_template_string(self, prompt_name: str) -> str: + return self._get_prompt_template(prompt_name).get_template_string() + def _render_prompt(self, prompt_name: str, params: dict[str, Any]) -> str: del params["self"] - return self._prompt_collection.render_prompt_template(prompt_name, params, lang_code=self.lang_code) + return self._get_prompt_template(prompt_name).render(**params) def _get_prompt_list(self, prompt_name: str) -> PromptList: return self._prompt_collection.get_prompt_list(prompt_name, self.lang_code) @@ -41,14 +47,12 @@ def autogenerate_prompt_factory_module(prompts_dir: str, target_module_path: str :param prompts_dir: the directory containing the prompt templates and prompt lists :param target_module_path: the path to the target module file (.py). Important: The module will be overwritten! """ - generated_code = """ -# ruff: noqa + generated_code = """# ruff: noqa # black: skip # mypy: ignore-errors # NOTE: This module is auto-generated from interprompt.autogenerate_prompt_factory_module, do not edit manually! -from interprompt.multilang_prompt import PromptList from interprompt.prompt_factory import PromptFactoryBase from typing import Any @@ -68,6 +72,9 @@ class PromptFactory(PromptFactoryBase): else: method_params_str = ", *, " + ", ".join([f"{param}: Any" for param in template_parameters]) generated_code += f""" + def get_{template_name}_template_string(self) -> str: + return self.get_prompt_template_string('{template_name}') + def create_{template_name}(self{method_params_str}) -> str: return self._render_prompt('{template_name}', locals()) """ diff --git a/src/serena/cli.py b/src/serena/cli.py index 9872ccf0..6d25a35b 100644 --- a/src/serena/cli.py +++ b/src/serena/cli.py @@ -492,15 +492,6 @@ class TopLevelCommands(AutoRegisteringGroup): viewer = SerenaDashboardViewer(url, width=width, height=height) viewer.run() - @staticmethod - @click.command( - "cc-system-prompt-override", - help="To be used specifically in Claude Code as value for `--system-prompt`", - context_settings={"max_content_width": _MAX_CONTENT_WIDTH}, - ) - def cc_system_prompt_override() -> None: - click.echo(SerenaPromptFactory().create_cc_system_prompt_override()) - class ModeCommands(AutoRegisteringGroup): """Group for 'mode' subcommands.""" @@ -1200,6 +1191,26 @@ class PromptCommands(AutoRegisteringGroup): os.remove(user_prompt_yaml_path) click.echo(f"Deleted override file '{prompt_yaml_name}'.") + @staticmethod + @click.command( + "print-prompt-template", + help="prints the (unrendered) template for the corresponding prompt name. " + "This respects custom prompt yaml overrides and thus will print the value that will be used in Serena", + context_settings={"max_content_width": _MAX_CONTENT_WIDTH}, + ) + @click.argument("prompt_name", type=str) + def print_prompt_template(prompt_name: str) -> None: + click.echo(SerenaPromptFactory().get_prompt_template_string(prompt_name)) + + @staticmethod + @click.command( + "print-cc-system-prompt-override", + help="To be used specifically in Claude Code as value for `--system-prompt`", + context_settings={"max_content_width": _MAX_CONTENT_WIDTH}, + ) + def print_cc_system_prompt_override() -> None: + click.echo(SerenaPromptFactory().get_cc_system_prompt_override_template_string()) + _mode = ModeCommands() _context = ContextCommands() diff --git a/src/serena/generated/generated_prompt_factory.py b/src/serena/generated/generated_prompt_factory.py index bb947cea..a1ee2920 100644 --- a/src/serena/generated/generated_prompt_factory.py +++ b/src/serena/generated/generated_prompt_factory.py @@ -4,7 +4,6 @@ # NOTE: This module is auto-generated from interprompt.autogenerate_prompt_factory_module, do not edit manually! -from interprompt.multilang_prompt import PromptList from interprompt.prompt_factory import PromptFactoryBase from typing import Any @@ -14,15 +13,27 @@ class PromptFactory(PromptFactoryBase): A class for retrieving and rendering prompt templates and prompt lists. """ + def get_info_jet_brains_debug_repl_template_string(self) -> str: + return self.get_prompt_template_string("info_jet_brains_debug_repl") + def create_info_jet_brains_debug_repl(self) -> str: return self._render_prompt("info_jet_brains_debug_repl", locals()) + def get_onboarding_prompt_template_string(self) -> str: + return self.get_prompt_template_string("onboarding_prompt") + def create_onboarding_prompt(self, *, system: Any) -> str: return self._render_prompt("onboarding_prompt", locals()) + def get_connection_prompt_template_string(self) -> str: + return self.get_prompt_template_string("connection_prompt") + def create_connection_prompt(self) -> str: return self._render_prompt("connection_prompt", locals()) + def get_system_prompt_template_string(self) -> str: + return self.get_prompt_template_string("system_prompt") + def create_system_prompt( self, *, @@ -34,5 +45,8 @@ class PromptFactory(PromptFactoryBase): ) -> str: return self._render_prompt("system_prompt", locals()) + def get_cc_system_prompt_override_template_string(self) -> str: + return self.get_prompt_template_string("cc_system_prompt_override") + def create_cc_system_prompt_override(self) -> str: return self._render_prompt("cc_system_prompt_override", locals())