From 562555eacbdbc0e8f6f02d139c1e6cda7e049004 Mon Sep 17 00:00:00 2001 From: Paulo Date: Sun, 26 Jul 2026 02:09:25 +0200 Subject: [PATCH] Ship's templates travel with its package The prompts sat in a platform-level backend/templates/prompts/ that only ship used, kept in the wheel by a force-include. They move to druks/contrib/ship/templates/, the root every extension gets: the resolver walks each installed extension's package for one and mounts it under the extension's name, so ship/build/implement.md is build/implement.md inside ship's own tree. Names and the .druks/ship/prompts/ override path are unchanged, and an extension shipped on its own now carries its templates with it. That emptied backend/templates/, so the directory, the force-include, and the loader entry pointing at it go with it. --- .../ship/templates}/build/_contract.md | 0 .../ship/templates}/build/_github_review.md | 0 .../contrib/ship/templates}/build/_header.md | 0 .../ship/templates}/build/_related_repos.md | 0 .../contrib/ship/templates}/build/_skills.md | 0 .../build/evaluate_implementation.md | 0 .../ship/templates}/build/generate_plan.md | 0 .../ship/templates}/build/implement.md | 0 .../ship/templates}/build/review_code.md | 0 .../ship/templates}/build/review_plan.md | 0 .../ship/templates}/build/revise_contract.md | 0 .../templates}/build/triage_human_feedback.md | 0 .../ship/templates}/profile/repo_profiler.md | 0 .../ship/templates}/verification_block.md | 0 backend/druks/prompts/resolver.py | 26 +++++++++---------- backend/tests/test_build_prompts.py | 5 ++-- pyproject.toml | 7 ----- 17 files changed, 15 insertions(+), 23 deletions(-) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/_contract.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/_github_review.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/_header.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/_related_repos.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/_skills.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/evaluate_implementation.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/generate_plan.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/implement.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/review_code.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/review_plan.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/revise_contract.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/build/triage_human_feedback.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/profile/repo_profiler.md (100%) rename backend/{templates/prompts/ship => druks/contrib/ship/templates}/verification_block.md (100%) diff --git a/backend/templates/prompts/ship/build/_contract.md b/backend/druks/contrib/ship/templates/build/_contract.md similarity index 100% rename from backend/templates/prompts/ship/build/_contract.md rename to backend/druks/contrib/ship/templates/build/_contract.md diff --git a/backend/templates/prompts/ship/build/_github_review.md b/backend/druks/contrib/ship/templates/build/_github_review.md similarity index 100% rename from backend/templates/prompts/ship/build/_github_review.md rename to backend/druks/contrib/ship/templates/build/_github_review.md diff --git a/backend/templates/prompts/ship/build/_header.md b/backend/druks/contrib/ship/templates/build/_header.md similarity index 100% rename from backend/templates/prompts/ship/build/_header.md rename to backend/druks/contrib/ship/templates/build/_header.md diff --git a/backend/templates/prompts/ship/build/_related_repos.md b/backend/druks/contrib/ship/templates/build/_related_repos.md similarity index 100% rename from backend/templates/prompts/ship/build/_related_repos.md rename to backend/druks/contrib/ship/templates/build/_related_repos.md diff --git a/backend/templates/prompts/ship/build/_skills.md b/backend/druks/contrib/ship/templates/build/_skills.md similarity index 100% rename from backend/templates/prompts/ship/build/_skills.md rename to backend/druks/contrib/ship/templates/build/_skills.md diff --git a/backend/templates/prompts/ship/build/evaluate_implementation.md b/backend/druks/contrib/ship/templates/build/evaluate_implementation.md similarity index 100% rename from backend/templates/prompts/ship/build/evaluate_implementation.md rename to backend/druks/contrib/ship/templates/build/evaluate_implementation.md diff --git a/backend/templates/prompts/ship/build/generate_plan.md b/backend/druks/contrib/ship/templates/build/generate_plan.md similarity index 100% rename from backend/templates/prompts/ship/build/generate_plan.md rename to backend/druks/contrib/ship/templates/build/generate_plan.md diff --git a/backend/templates/prompts/ship/build/implement.md b/backend/druks/contrib/ship/templates/build/implement.md similarity index 100% rename from backend/templates/prompts/ship/build/implement.md rename to backend/druks/contrib/ship/templates/build/implement.md diff --git a/backend/templates/prompts/ship/build/review_code.md b/backend/druks/contrib/ship/templates/build/review_code.md similarity index 100% rename from backend/templates/prompts/ship/build/review_code.md rename to backend/druks/contrib/ship/templates/build/review_code.md diff --git a/backend/templates/prompts/ship/build/review_plan.md b/backend/druks/contrib/ship/templates/build/review_plan.md similarity index 100% rename from backend/templates/prompts/ship/build/review_plan.md rename to backend/druks/contrib/ship/templates/build/review_plan.md diff --git a/backend/templates/prompts/ship/build/revise_contract.md b/backend/druks/contrib/ship/templates/build/revise_contract.md similarity index 100% rename from backend/templates/prompts/ship/build/revise_contract.md rename to backend/druks/contrib/ship/templates/build/revise_contract.md diff --git a/backend/templates/prompts/ship/build/triage_human_feedback.md b/backend/druks/contrib/ship/templates/build/triage_human_feedback.md similarity index 100% rename from backend/templates/prompts/ship/build/triage_human_feedback.md rename to backend/druks/contrib/ship/templates/build/triage_human_feedback.md diff --git a/backend/templates/prompts/ship/profile/repo_profiler.md b/backend/druks/contrib/ship/templates/profile/repo_profiler.md similarity index 100% rename from backend/templates/prompts/ship/profile/repo_profiler.md rename to backend/druks/contrib/ship/templates/profile/repo_profiler.md diff --git a/backend/templates/prompts/ship/verification_block.md b/backend/druks/contrib/ship/templates/verification_block.md similarity index 100% rename from backend/templates/prompts/ship/verification_block.md rename to backend/druks/contrib/ship/templates/verification_block.md diff --git a/backend/druks/prompts/resolver.py b/backend/druks/prompts/resolver.py index 5eb9bfea..85959a43 100644 --- a/backend/druks/prompts/resolver.py +++ b/backend/druks/prompts/resolver.py @@ -2,21 +2,20 @@ import importlib.util from pathlib import Path -from jinja2 import Environment, FileSystemLoader, StrictUndefined +from jinja2 import Environment, FileSystemLoader, PrefixLoader, StrictUndefined from jinja2.sandbox import ImmutableSandboxedEnvironment from druks.extensions.fetcher import fetch_file from druks.extensions.loader import iter_extensions -PROMPTS_DIR = Path(__file__).resolve().parents[2] / "templates" / "prompts" - @functools.cache def _environment() -> Environment: - # One Jinja environment over the bundled core templates plus each installed - # extension's own ``templates/prompts`` root, so a separately-shipped extension carries - # its prompts in its package. Overrides resolved as strings via - # ``from_string`` still see the loader for ``{% include %}`` against partials. + # One Jinja environment over every installed extension's own ``templates`` root, + # each mounted under the extension's name: ``ship/build/implement.md`` is + # ``build/implement.md`` inside ship's package, so nothing repeats the extension in + # its own tree. Overrides resolved as strings via ``from_string`` still see the + # loader for ``{% include %}`` against partials. # # Sandboxed because a ``.druks//prompts/*`` override is authored by anyone with # push access to a monitored repo: the sandbox blocks the ``__globals__`` walk to @@ -24,7 +23,7 @@ def _environment() -> Environment: # ``workspace`` objects in context. Bundled templates only read public attributes, # so the sandbox is invisible to them. return ImmutableSandboxedEnvironment( - loader=FileSystemLoader([PROMPTS_DIR, *_extension_prompt_roots()]), + loader=PrefixLoader(_extension_template_roots()), autoescape=False, undefined=StrictUndefined, keep_trailing_newline=True, @@ -33,15 +32,15 @@ def _environment() -> Environment: ) -def _extension_prompt_roots() -> list[Path]: - roots: list[Path] = [] +def _extension_template_roots() -> dict[str, FileSystemLoader]: + roots: dict[str, FileSystemLoader] = {} for extension in iter_extensions(): spec = importlib.util.find_spec(extension.package) if not spec or not spec.submodule_search_locations: continue - root = Path(spec.submodule_search_locations[0]) / "templates" / "prompts" + root = Path(spec.submodule_search_locations[0]) / "templates" if root.is_dir(): - roots.append(root) + roots[extension.name] = FileSystemLoader(root) return roots @@ -58,8 +57,7 @@ async def render_prompt( 1. ``/.druks//prompts/`` — repo-specific tuning 2. ``/.druks`` repo ``/prompts/`` — org-wide tuning - 3. bundled ``backend/templates/prompts/`` and each installed extension's - own ``/templates/prompts`` root — built-in baseline + 3. ```` under the extension's own ``/templates`` root — built-in baseline A 404 at a tier silently falls through to the next. Auth or network failures propagate — those are real misconfigurations and the diff --git a/backend/tests/test_build_prompts.py b/backend/tests/test_build_prompts.py index 29ceb352..d60089f1 100644 --- a/backend/tests/test_build_prompts.py +++ b/backend/tests/test_build_prompts.py @@ -1,13 +1,14 @@ import re +from pathlib import Path from types import SimpleNamespace import pytest +from druks.contrib import ship from druks.contrib.ship.contracts import PlanData from druks.contrib.ship.journal import BuildJournal from druks.contrib.ship.models import Project, ProjectRepo from druks.contrib.ship.prompt_context import BuildPromptContext from druks.prompts import render_prompt -from druks.prompts.resolver import PROMPTS_DIR from druks.workflows import FatalError _OP_TEMPLATES = [ @@ -260,7 +261,7 @@ async def test_contract_context_is_omitted_only_from_code_review(): def test_build_prompt_context_covers_template_attrs(): # Every build prompt reads build.; assert BuildPromptContext carries them # all, so a template ref can never outrun the context contract. - prompts_dir = PROMPTS_DIR / "ship/build" + prompts_dir = Path(ship.__file__).parent / "templates/build" templates = sorted(prompts_dir.glob("*.md")) assert templates, f"no build prompts under {prompts_dir}" attrs: set[str] = set() diff --git a/pyproject.toml b/pyproject.toml index b89bea9e..5f780823 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,18 +77,11 @@ packages = ["backend/druks"] [tool.hatch.build.targets.sdist] include = [ "/backend/druks", - "/backend/templates", "/LICENSE", "/pyproject.toml", "/README.md", ] -# Ship the prompt templates dir alongside the wheel. ``druks.core.prompts`` -# resolves the path relative to ``backend/`` so the directory has to land -# at the same height as the ``druks`` package in the installed tree. -[tool.hatch.build.targets.wheel.force-include] -"backend/templates" = "templates" - [tool.ruff] line-length = 100 target-version = "py311"